@ox-content/vite-plugin 1.0.0-alpha.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/chunk.cjs +39 -49
- package/dist/github.cjs +323 -3
- package/dist/github.cjs.map +1 -0
- package/dist/github.mjs +2 -0
- package/dist/{github2.js → github2.mjs} +2 -3
- package/dist/github2.mjs.map +1 -0
- package/dist/index.cjs +1371 -598
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +114 -69
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +1960 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/{index.js → index.mjs} +1383 -623
- package/dist/index.mjs.map +1 -0
- package/dist/mermaid.cjs +115 -3
- package/dist/mermaid.cjs.map +1 -0
- package/dist/{mermaid2.js → mermaid.mjs} +14 -6
- package/dist/mermaid.mjs.map +1 -0
- package/dist/mermaid2.mjs +2 -0
- package/dist/ogp.cjs +316 -3
- package/dist/ogp.cjs.map +1 -0
- package/dist/ogp.mjs +2 -0
- package/dist/{ogp2.js → ogp2.mjs} +2 -3
- package/dist/ogp2.mjs.map +1 -0
- package/dist/tabs.cjs +212 -3
- package/dist/tabs.cjs.map +1 -0
- package/dist/tabs.mjs +2 -0
- package/dist/{tabs2.js → tabs2.mjs} +2 -3
- package/dist/tabs2.mjs.map +1 -0
- package/dist/youtube.cjs +135 -3
- package/dist/youtube.cjs.map +1 -0
- package/dist/youtube.mjs +2 -0
- package/dist/{youtube2.js → youtube2.mjs} +2 -3
- package/dist/youtube2.mjs.map +1 -0
- package/package.json +61 -56
- package/dist/github.js +0 -3
- package/dist/github2.cjs +0 -313
- package/dist/github2.cjs.map +0 -1
- package/dist/github2.js.map +0 -1
- package/dist/index.d.ts +0 -1915
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/mermaid.js +0 -3
- package/dist/mermaid2.cjs +0 -92
- package/dist/mermaid2.cjs.map +0 -1
- package/dist/mermaid2.js.map +0 -1
- package/dist/ogp.js +0 -3
- package/dist/ogp2.cjs +0 -306
- package/dist/ogp2.cjs.map +0 -1
- package/dist/ogp2.js.map +0 -1
- package/dist/tabs.js +0 -3
- package/dist/tabs2.cjs +0 -203
- package/dist/tabs2.cjs.map +0 -1
- package/dist/tabs2.js.map +0 -1
- package/dist/youtube.js +0 -3
- package/dist/youtube2.cjs +0 -127
- package/dist/youtube2.cjs.map +0 -1
- package/dist/youtube2.js.map +0 -1
package/dist/tabs.cjs
CHANGED
|
@@ -1,4 +1,213 @@
|
|
|
1
|
-
const require_chunk = require(
|
|
2
|
-
|
|
1
|
+
const require_chunk = require("./chunk.cjs");
|
|
2
|
+
let unified = require("unified");
|
|
3
|
+
let rehype_parse = require("rehype-parse");
|
|
4
|
+
rehype_parse = require_chunk.__toESM(rehype_parse);
|
|
5
|
+
let rehype_stringify = require("rehype-stringify");
|
|
6
|
+
rehype_stringify = require_chunk.__toESM(rehype_stringify);
|
|
7
|
+
//#region src/plugins/tabs.ts
|
|
8
|
+
/**
|
|
9
|
+
* Tabs Plugin - Pure CSS implementation
|
|
10
|
+
*
|
|
11
|
+
* Transforms <Tabs>/<Tab> components into accessible HTML
|
|
12
|
+
* with CSS :has() based tab switching (no JavaScript required).
|
|
13
|
+
*/
|
|
14
|
+
var tabs_exports = /* @__PURE__ */ require_chunk.__exportAll({
|
|
15
|
+
generateTabsCSS: () => generateTabsCSS,
|
|
16
|
+
resetTabGroupCounter: () => resetTabGroupCounter,
|
|
17
|
+
transformTabs: () => transformTabs
|
|
18
|
+
});
|
|
19
|
+
let tabGroupCounter = 0;
|
|
20
|
+
/**
|
|
21
|
+
* Reset tab group counter (for testing).
|
|
22
|
+
*/
|
|
23
|
+
function resetTabGroupCounter() {
|
|
24
|
+
tabGroupCounter = 0;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Get element attribute value.
|
|
28
|
+
*/
|
|
29
|
+
function getAttribute(el, name) {
|
|
30
|
+
const value = el.properties?.[name];
|
|
31
|
+
if (typeof value === "string") return value;
|
|
32
|
+
if (Array.isArray(value)) return value.join(" ");
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Parse Tab elements from Tabs children.
|
|
36
|
+
*/
|
|
37
|
+
function parseTabChildren(children) {
|
|
38
|
+
const tabs = [];
|
|
39
|
+
for (const child of children) {
|
|
40
|
+
if (child.type !== "element") continue;
|
|
41
|
+
if (child.tagName.toLowerCase() === "tab") {
|
|
42
|
+
const label = getAttribute(child, "label") || `Tab ${tabs.length + 1}`;
|
|
43
|
+
tabs.push({
|
|
44
|
+
label,
|
|
45
|
+
content: child.children.filter((c) => c.type === "element" || c.type === "text")
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return tabs;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Create the HTML structure for tabs.
|
|
53
|
+
*/
|
|
54
|
+
function createTabsElement(tabs, groupId) {
|
|
55
|
+
const children = [];
|
|
56
|
+
const headerChildren = [];
|
|
57
|
+
tabs.forEach((tab, index) => {
|
|
58
|
+
const inputId = `ox-tab-${groupId}-${index}`;
|
|
59
|
+
headerChildren.push({
|
|
60
|
+
type: "element",
|
|
61
|
+
tagName: "input",
|
|
62
|
+
properties: {
|
|
63
|
+
type: "radio",
|
|
64
|
+
name: `ox-tabs-${groupId}`,
|
|
65
|
+
id: inputId,
|
|
66
|
+
checked: index === 0 ? true : void 0
|
|
67
|
+
},
|
|
68
|
+
children: []
|
|
69
|
+
});
|
|
70
|
+
headerChildren.push({
|
|
71
|
+
type: "element",
|
|
72
|
+
tagName: "label",
|
|
73
|
+
properties: { htmlFor: inputId },
|
|
74
|
+
children: [{
|
|
75
|
+
type: "text",
|
|
76
|
+
value: tab.label
|
|
77
|
+
}]
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
children.push({
|
|
81
|
+
type: "element",
|
|
82
|
+
tagName: "div",
|
|
83
|
+
properties: { className: ["ox-tabs-header"] },
|
|
84
|
+
children: headerChildren
|
|
85
|
+
});
|
|
86
|
+
tabs.forEach((tab, index) => {
|
|
87
|
+
children.push({
|
|
88
|
+
type: "element",
|
|
89
|
+
tagName: "div",
|
|
90
|
+
properties: {
|
|
91
|
+
className: ["ox-tab-panel"],
|
|
92
|
+
"data-tab": String(index)
|
|
93
|
+
},
|
|
94
|
+
children: tab.content
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
return {
|
|
98
|
+
type: "element",
|
|
99
|
+
tagName: "div",
|
|
100
|
+
properties: {
|
|
101
|
+
className: ["ox-tabs"],
|
|
102
|
+
"data-group": groupId
|
|
103
|
+
},
|
|
104
|
+
children
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Create fallback HTML using <details> elements.
|
|
109
|
+
*/
|
|
110
|
+
function createFallbackElement(tabs) {
|
|
111
|
+
const children = [];
|
|
112
|
+
tabs.forEach((tab, index) => {
|
|
113
|
+
children.push({
|
|
114
|
+
type: "element",
|
|
115
|
+
tagName: "details",
|
|
116
|
+
properties: { open: index === 0 ? true : void 0 },
|
|
117
|
+
children: [{
|
|
118
|
+
type: "element",
|
|
119
|
+
tagName: "summary",
|
|
120
|
+
properties: {},
|
|
121
|
+
children: [{
|
|
122
|
+
type: "text",
|
|
123
|
+
value: tab.label
|
|
124
|
+
}]
|
|
125
|
+
}, {
|
|
126
|
+
type: "element",
|
|
127
|
+
tagName: "div",
|
|
128
|
+
properties: { className: ["ox-tabs-fallback-content"] },
|
|
129
|
+
children: tab.content
|
|
130
|
+
}]
|
|
131
|
+
});
|
|
132
|
+
});
|
|
133
|
+
return {
|
|
134
|
+
type: "element",
|
|
135
|
+
tagName: "noscript",
|
|
136
|
+
properties: {},
|
|
137
|
+
children: [{
|
|
138
|
+
type: "element",
|
|
139
|
+
tagName: "div",
|
|
140
|
+
properties: { className: ["ox-tabs-fallback"] },
|
|
141
|
+
children
|
|
142
|
+
}]
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Rehype plugin to transform Tabs components.
|
|
147
|
+
*/
|
|
148
|
+
function rehypeTabs() {
|
|
149
|
+
return (tree) => {
|
|
150
|
+
const visit = (node) => {
|
|
151
|
+
if ("children" in node) for (let i = 0; i < node.children.length; i++) {
|
|
152
|
+
const child = node.children[i];
|
|
153
|
+
if (child.type === "element") if (child.tagName.toLowerCase() === "tabs") {
|
|
154
|
+
const tabs = parseTabChildren(child.children);
|
|
155
|
+
if (tabs.length > 0) {
|
|
156
|
+
const wrapper = {
|
|
157
|
+
type: "element",
|
|
158
|
+
tagName: "div",
|
|
159
|
+
properties: { className: ["ox-tabs-container"] },
|
|
160
|
+
children: [createTabsElement(tabs, String(tabGroupCounter++)), createFallbackElement(tabs)]
|
|
161
|
+
};
|
|
162
|
+
node.children[i] = wrapper;
|
|
163
|
+
}
|
|
164
|
+
} else visit(child);
|
|
165
|
+
}
|
|
166
|
+
};
|
|
167
|
+
visit(tree);
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Transform Tabs components in HTML.
|
|
172
|
+
*/
|
|
173
|
+
async function transformTabs(html) {
|
|
174
|
+
const result = await (0, unified.unified)().use(rehype_parse.default, { fragment: true }).use(rehypeTabs).use(rehype_stringify.default).process(html);
|
|
175
|
+
return String(result);
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Generate dynamic CSS for :has() based tab switching.
|
|
179
|
+
* This is needed because :has() selectors need unique IDs.
|
|
180
|
+
*/
|
|
181
|
+
function generateTabsCSS(groupCount) {
|
|
182
|
+
if (groupCount === 0) return "";
|
|
183
|
+
let css = "/* Dynamic Tabs CSS */\n";
|
|
184
|
+
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`;
|
|
185
|
+
return css;
|
|
186
|
+
}
|
|
187
|
+
//#endregion
|
|
188
|
+
Object.defineProperty(exports, "generateTabsCSS", {
|
|
189
|
+
enumerable: true,
|
|
190
|
+
get: function() {
|
|
191
|
+
return generateTabsCSS;
|
|
192
|
+
}
|
|
193
|
+
});
|
|
194
|
+
Object.defineProperty(exports, "resetTabGroupCounter", {
|
|
195
|
+
enumerable: true,
|
|
196
|
+
get: function() {
|
|
197
|
+
return resetTabGroupCounter;
|
|
198
|
+
}
|
|
199
|
+
});
|
|
200
|
+
Object.defineProperty(exports, "tabs_exports", {
|
|
201
|
+
enumerable: true,
|
|
202
|
+
get: function() {
|
|
203
|
+
return tabs_exports;
|
|
204
|
+
}
|
|
205
|
+
});
|
|
206
|
+
Object.defineProperty(exports, "transformTabs", {
|
|
207
|
+
enumerable: true,
|
|
208
|
+
get: function() {
|
|
209
|
+
return transformTabs;
|
|
210
|
+
}
|
|
211
|
+
});
|
|
3
212
|
|
|
4
|
-
|
|
213
|
+
//# sourceMappingURL=tabs.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tabs.cjs","names":["rehypeParse","rehypeStringify"],"sources":["../src/plugins/tabs.ts"],"sourcesContent":["/**\n * Tabs Plugin - Pure CSS implementation\n *\n * Transforms <Tabs>/<Tab> components into accessible HTML\n * with CSS :has() based tab switching (no JavaScript required).\n */\n\nimport { unified } from \"unified\";\nimport rehypeParse from \"rehype-parse\";\nimport rehypeStringify from \"rehype-stringify\";\nimport type { Root, Element } from \"hast\";\n\nlet tabGroupCounter = 0;\n\n/**\n * Reset tab group counter (for testing).\n */\nexport function resetTabGroupCounter(): void {\n tabGroupCounter = 0;\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\ninterface TabData {\n label: string;\n content: Element[];\n}\n\n/**\n * Parse Tab elements from Tabs children.\n */\nfunction parseTabChildren(children: Element[\"children\"]): TabData[] {\n const tabs: TabData[] = [];\n\n for (const child of children) {\n if (child.type !== \"element\") continue;\n\n // Handle <Tab label=\"...\">\n if (child.tagName.toLowerCase() === \"tab\") {\n const label = getAttribute(child, \"label\") || `Tab ${tabs.length + 1}`;\n tabs.push({\n label,\n content: child.children.filter(\n (c): c is Element => c.type === \"element\" || c.type === \"text\",\n ) as Element[],\n });\n }\n }\n\n return tabs;\n}\n\n/**\n * Create the HTML structure for tabs.\n */\nfunction createTabsElement(tabs: TabData[], groupId: string): Element {\n const children: Element[\"children\"] = [];\n\n // Create header with radio inputs and labels\n const headerChildren: Element[\"children\"] = [];\n\n tabs.forEach((tab, index) => {\n const inputId = `ox-tab-${groupId}-${index}`;\n\n // Radio input\n headerChildren.push({\n type: \"element\",\n tagName: \"input\",\n properties: {\n type: \"radio\",\n name: `ox-tabs-${groupId}`,\n id: inputId,\n checked: index === 0 ? true : undefined,\n },\n children: [],\n });\n\n // Label\n headerChildren.push({\n type: \"element\",\n tagName: \"label\",\n properties: {\n htmlFor: inputId,\n },\n children: [{ type: \"text\", value: tab.label }],\n });\n });\n\n // Tabs header\n children.push({\n type: \"element\",\n tagName: \"div\",\n properties: { className: [\"ox-tabs-header\"] },\n children: headerChildren,\n });\n\n // Tab panels\n tabs.forEach((tab, index) => {\n children.push({\n type: \"element\",\n tagName: \"div\",\n properties: {\n className: [\"ox-tab-panel\"],\n \"data-tab\": String(index),\n },\n children: tab.content,\n });\n });\n\n return {\n type: \"element\",\n tagName: \"div\",\n properties: {\n className: [\"ox-tabs\"],\n \"data-group\": groupId,\n },\n children,\n };\n}\n\n/**\n * Create fallback HTML using <details> elements.\n */\nfunction createFallbackElement(tabs: TabData[]): Element {\n const children: Element[\"children\"] = [];\n\n tabs.forEach((tab, index) => {\n children.push({\n type: \"element\",\n tagName: \"details\",\n properties: {\n open: index === 0 ? true : undefined,\n },\n children: [\n {\n type: \"element\",\n tagName: \"summary\",\n properties: {},\n children: [{ type: \"text\", value: tab.label }],\n },\n {\n type: \"element\",\n tagName: \"div\",\n properties: { className: [\"ox-tabs-fallback-content\"] },\n children: tab.content,\n },\n ],\n });\n });\n\n return {\n type: \"element\",\n tagName: \"noscript\",\n properties: {},\n children: [\n {\n type: \"element\",\n tagName: \"div\",\n properties: { className: [\"ox-tabs-fallback\"] },\n children,\n },\n ],\n };\n}\n\n/**\n * Rehype plugin to transform Tabs components.\n */\nfunction rehypeTabs() {\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 <Tabs> component\n if (child.tagName.toLowerCase() === \"tabs\") {\n const tabs = parseTabChildren(child.children);\n\n if (tabs.length > 0) {\n const groupId = String(tabGroupCounter++);\n const tabsElement = createTabsElement(tabs, groupId);\n const fallbackElement = createFallbackElement(tabs);\n\n // Replace <Tabs> with new structure\n // Keep main tabs and add noscript fallback\n const wrapper: Element = {\n type: \"element\",\n tagName: \"div\",\n properties: { className: [\"ox-tabs-container\"] },\n children: [tabsElement, fallbackElement],\n };\n\n node.children[i] = wrapper;\n }\n } else {\n visit(child);\n }\n }\n }\n }\n };\n\n visit(tree);\n };\n}\n\n/**\n * Transform Tabs components in HTML.\n */\nexport async function transformTabs(html: string): Promise<string> {\n const result = await unified()\n .use(rehypeParse, { fragment: true })\n .use(rehypeTabs)\n .use(rehypeStringify)\n .process(html);\n\n return String(result);\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":"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"}
|
package/dist/tabs.mjs
ADDED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { unified } from "unified";
|
|
2
2
|
import rehypeParse from "rehype-parse";
|
|
3
3
|
import rehypeStringify from "rehype-stringify";
|
|
4
|
-
|
|
5
4
|
//#region src/plugins/tabs.ts
|
|
6
5
|
/**
|
|
7
6
|
* Tabs Plugin - Pure CSS implementation
|
|
@@ -177,7 +176,7 @@ function generateTabsCSS(groupCount) {
|
|
|
177
176
|
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`;
|
|
178
177
|
return css;
|
|
179
178
|
}
|
|
180
|
-
|
|
181
179
|
//#endregion
|
|
182
180
|
export { resetTabGroupCounter as n, transformTabs as r, generateTabsCSS as t };
|
|
183
|
-
|
|
181
|
+
|
|
182
|
+
//# sourceMappingURL=tabs2.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
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\n * with CSS :has() based tab switching (no JavaScript required).\n */\n\nimport { unified } from \"unified\";\nimport rehypeParse from \"rehype-parse\";\nimport rehypeStringify from \"rehype-stringify\";\nimport type { Root, Element } from \"hast\";\n\nlet tabGroupCounter = 0;\n\n/**\n * Reset tab group counter (for testing).\n */\nexport function resetTabGroupCounter(): void {\n tabGroupCounter = 0;\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\ninterface TabData {\n label: string;\n content: Element[];\n}\n\n/**\n * Parse Tab elements from Tabs children.\n */\nfunction parseTabChildren(children: Element[\"children\"]): TabData[] {\n const tabs: TabData[] = [];\n\n for (const child of children) {\n if (child.type !== \"element\") continue;\n\n // Handle <Tab label=\"...\">\n if (child.tagName.toLowerCase() === \"tab\") {\n const label = getAttribute(child, \"label\") || `Tab ${tabs.length + 1}`;\n tabs.push({\n label,\n content: child.children.filter(\n (c): c is Element => c.type === \"element\" || c.type === \"text\",\n ) as Element[],\n });\n }\n }\n\n return tabs;\n}\n\n/**\n * Create the HTML structure for tabs.\n */\nfunction createTabsElement(tabs: TabData[], groupId: string): Element {\n const children: Element[\"children\"] = [];\n\n // Create header with radio inputs and labels\n const headerChildren: Element[\"children\"] = [];\n\n tabs.forEach((tab, index) => {\n const inputId = `ox-tab-${groupId}-${index}`;\n\n // Radio input\n headerChildren.push({\n type: \"element\",\n tagName: \"input\",\n properties: {\n type: \"radio\",\n name: `ox-tabs-${groupId}`,\n id: inputId,\n checked: index === 0 ? true : undefined,\n },\n children: [],\n });\n\n // Label\n headerChildren.push({\n type: \"element\",\n tagName: \"label\",\n properties: {\n htmlFor: inputId,\n },\n children: [{ type: \"text\", value: tab.label }],\n });\n });\n\n // Tabs header\n children.push({\n type: \"element\",\n tagName: \"div\",\n properties: { className: [\"ox-tabs-header\"] },\n children: headerChildren,\n });\n\n // Tab panels\n tabs.forEach((tab, index) => {\n children.push({\n type: \"element\",\n tagName: \"div\",\n properties: {\n className: [\"ox-tab-panel\"],\n \"data-tab\": String(index),\n },\n children: tab.content,\n });\n });\n\n return {\n type: \"element\",\n tagName: \"div\",\n properties: {\n className: [\"ox-tabs\"],\n \"data-group\": groupId,\n },\n children,\n };\n}\n\n/**\n * Create fallback HTML using <details> elements.\n */\nfunction createFallbackElement(tabs: TabData[]): Element {\n const children: Element[\"children\"] = [];\n\n tabs.forEach((tab, index) => {\n children.push({\n type: \"element\",\n tagName: \"details\",\n properties: {\n open: index === 0 ? true : undefined,\n },\n children: [\n {\n type: \"element\",\n tagName: \"summary\",\n properties: {},\n children: [{ type: \"text\", value: tab.label }],\n },\n {\n type: \"element\",\n tagName: \"div\",\n properties: { className: [\"ox-tabs-fallback-content\"] },\n children: tab.content,\n },\n ],\n });\n });\n\n return {\n type: \"element\",\n tagName: \"noscript\",\n properties: {},\n children: [\n {\n type: \"element\",\n tagName: \"div\",\n properties: { className: [\"ox-tabs-fallback\"] },\n children,\n },\n ],\n };\n}\n\n/**\n * Rehype plugin to transform Tabs components.\n */\nfunction rehypeTabs() {\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 <Tabs> component\n if (child.tagName.toLowerCase() === \"tabs\") {\n const tabs = parseTabChildren(child.children);\n\n if (tabs.length > 0) {\n const groupId = String(tabGroupCounter++);\n const tabsElement = createTabsElement(tabs, groupId);\n const fallbackElement = createFallbackElement(tabs);\n\n // Replace <Tabs> with new structure\n // Keep main tabs and add noscript fallback\n const wrapper: Element = {\n type: \"element\",\n tagName: \"div\",\n properties: { className: [\"ox-tabs-container\"] },\n children: [tabsElement, fallbackElement],\n };\n\n node.children[i] = wrapper;\n }\n } else {\n visit(child);\n }\n }\n }\n }\n };\n\n visit(tree);\n };\n}\n\n/**\n * Transform Tabs components in HTML.\n */\nexport async function transformTabs(html: string): Promise<string> {\n const result = await unified()\n .use(rehypeParse, { fragment: true })\n .use(rehypeTabs)\n .use(rehypeStringify)\n .process(html);\n\n return String(result);\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":"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"}
|
package/dist/youtube.cjs
CHANGED
|
@@ -1,4 +1,136 @@
|
|
|
1
|
-
const require_chunk = require(
|
|
2
|
-
|
|
1
|
+
const require_chunk = require("./chunk.cjs");
|
|
2
|
+
let unified = require("unified");
|
|
3
|
+
let rehype_parse = require("rehype-parse");
|
|
4
|
+
rehype_parse = require_chunk.__toESM(rehype_parse);
|
|
5
|
+
let rehype_stringify = require("rehype-stringify");
|
|
6
|
+
rehype_stringify = require_chunk.__toESM(rehype_stringify);
|
|
7
|
+
//#region src/plugins/youtube.ts
|
|
8
|
+
/**
|
|
9
|
+
* YouTube Plugin - Privacy-enhanced iframe embedding
|
|
10
|
+
*
|
|
11
|
+
* Transforms <YouTube> components into responsive iframe embeds
|
|
12
|
+
* using youtube-nocookie.com for enhanced privacy.
|
|
13
|
+
*/
|
|
14
|
+
var youtube_exports = /* @__PURE__ */ require_chunk.__exportAll({
|
|
15
|
+
extractVideoId: () => extractVideoId,
|
|
16
|
+
transformYouTube: () => transformYouTube
|
|
17
|
+
});
|
|
18
|
+
const defaultOptions = {
|
|
19
|
+
privacyEnhanced: true,
|
|
20
|
+
aspectRatio: "16/9",
|
|
21
|
+
allowFullscreen: true,
|
|
22
|
+
lazyLoad: true
|
|
23
|
+
};
|
|
24
|
+
/**
|
|
25
|
+
* Get element attribute value.
|
|
26
|
+
*/
|
|
27
|
+
function getAttribute(el, name) {
|
|
28
|
+
const value = el.properties?.[name];
|
|
29
|
+
if (typeof value === "string") return value;
|
|
30
|
+
if (Array.isArray(value)) return value.join(" ");
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Extract YouTube video ID from various URL formats.
|
|
34
|
+
*/
|
|
35
|
+
function extractVideoId(input) {
|
|
36
|
+
if (/^[a-zA-Z0-9_-]{11}$/.test(input)) return input;
|
|
37
|
+
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})/]) {
|
|
38
|
+
const match = input.match(pattern);
|
|
39
|
+
if (match) return match[1];
|
|
40
|
+
}
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Build YouTube embed URL with parameters.
|
|
45
|
+
*/
|
|
46
|
+
function buildEmbedUrl(videoId, options, params) {
|
|
47
|
+
const domain = options.privacyEnhanced ? "www.youtube-nocookie.com" : "www.youtube.com";
|
|
48
|
+
const url = new URL(`https://${domain}/embed/${videoId}`);
|
|
49
|
+
if (params) for (const [key, value] of Object.entries(params)) url.searchParams.set(key, value);
|
|
50
|
+
return url.toString();
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Create YouTube embed element.
|
|
54
|
+
*/
|
|
55
|
+
function createYouTubeElement(videoId, options, title, start) {
|
|
56
|
+
const params = {};
|
|
57
|
+
if (start) params.start = start;
|
|
58
|
+
const iframe = {
|
|
59
|
+
type: "element",
|
|
60
|
+
tagName: "iframe",
|
|
61
|
+
properties: {
|
|
62
|
+
src: buildEmbedUrl(videoId, options, params),
|
|
63
|
+
title: title || `YouTube video ${videoId}`,
|
|
64
|
+
allow: "accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share",
|
|
65
|
+
referrerpolicy: "strict-origin-when-cross-origin",
|
|
66
|
+
allowfullscreen: options.allowFullscreen || void 0,
|
|
67
|
+
loading: options.lazyLoad ? "lazy" : void 0
|
|
68
|
+
},
|
|
69
|
+
children: []
|
|
70
|
+
};
|
|
71
|
+
return {
|
|
72
|
+
type: "element",
|
|
73
|
+
tagName: "div",
|
|
74
|
+
properties: {
|
|
75
|
+
className: ["ox-youtube"],
|
|
76
|
+
style: `aspect-ratio: ${options.aspectRatio};`
|
|
77
|
+
},
|
|
78
|
+
children: [iframe]
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Rehype plugin to transform YouTube components.
|
|
83
|
+
*/
|
|
84
|
+
function rehypeYouTube(options) {
|
|
85
|
+
return (tree) => {
|
|
86
|
+
const visit = (node) => {
|
|
87
|
+
if ("children" in node) for (let i = 0; i < node.children.length; i++) {
|
|
88
|
+
const child = node.children[i];
|
|
89
|
+
if (child.type === "element") if (child.tagName.toLowerCase() === "youtube") {
|
|
90
|
+
const id = getAttribute(child, "id");
|
|
91
|
+
const url = getAttribute(child, "url");
|
|
92
|
+
const title = getAttribute(child, "title");
|
|
93
|
+
const start = getAttribute(child, "start");
|
|
94
|
+
const videoId = id ? extractVideoId(id) : url ? extractVideoId(url) : null;
|
|
95
|
+
if (videoId) {
|
|
96
|
+
const youtubeElement = createYouTubeElement(videoId, options, title, start);
|
|
97
|
+
node.children[i] = youtubeElement;
|
|
98
|
+
}
|
|
99
|
+
} else visit(child);
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
visit(tree);
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Transform YouTube components in HTML.
|
|
107
|
+
*/
|
|
108
|
+
async function transformYouTube(html, options) {
|
|
109
|
+
const mergedOptions = {
|
|
110
|
+
...defaultOptions,
|
|
111
|
+
...options
|
|
112
|
+
};
|
|
113
|
+
const result = await (0, unified.unified)().use(rehype_parse.default, { fragment: true }).use(rehypeYouTube, mergedOptions).use(rehype_stringify.default).process(html);
|
|
114
|
+
return String(result);
|
|
115
|
+
}
|
|
116
|
+
//#endregion
|
|
117
|
+
Object.defineProperty(exports, "extractVideoId", {
|
|
118
|
+
enumerable: true,
|
|
119
|
+
get: function() {
|
|
120
|
+
return extractVideoId;
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
Object.defineProperty(exports, "transformYouTube", {
|
|
124
|
+
enumerable: true,
|
|
125
|
+
get: function() {
|
|
126
|
+
return transformYouTube;
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
Object.defineProperty(exports, "youtube_exports", {
|
|
130
|
+
enumerable: true,
|
|
131
|
+
get: function() {
|
|
132
|
+
return youtube_exports;
|
|
133
|
+
}
|
|
134
|
+
});
|
|
3
135
|
|
|
4
|
-
|
|
136
|
+
//# sourceMappingURL=youtube.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"youtube.cjs","names":["rehypeParse","rehypeStringify"],"sources":["../src/plugins/youtube.ts"],"sourcesContent":["/**\n * YouTube Plugin - Privacy-enhanced iframe embedding\n *\n * Transforms <YouTube> components into responsive iframe embeds\n * using youtube-nocookie.com for enhanced privacy.\n */\n\nimport { unified } from \"unified\";\nimport rehypeParse from \"rehype-parse\";\nimport rehypeStringify from \"rehype-stringify\";\nimport type { Root, Element, Properties } from \"hast\";\n\nexport interface YouTubeOptions {\n /** Use privacy-enhanced mode (youtube-nocookie.com). Default: true */\n privacyEnhanced?: boolean;\n /** Default aspect ratio. Default: \"16/9\" */\n aspectRatio?: string;\n /** Allow fullscreen. Default: true */\n allowFullscreen?: boolean;\n /** Lazy load iframe. Default: true */\n lazyLoad?: boolean;\n}\n\nconst defaultOptions: Required<YouTubeOptions> = {\n privacyEnhanced: true,\n aspectRatio: \"16/9\",\n allowFullscreen: true,\n lazyLoad: true,\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 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 * Build YouTube embed URL with parameters.\n */\nfunction buildEmbedUrl(\n videoId: string,\n options: Required<YouTubeOptions>,\n params?: Record<string, string>,\n): string {\n const domain = options.privacyEnhanced ? \"www.youtube-nocookie.com\" : \"www.youtube.com\";\n const url = new URL(`https://${domain}/embed/${videoId}`);\n\n // Add any custom parameters\n if (params) {\n for (const [key, value] of Object.entries(params)) {\n url.searchParams.set(key, value);\n }\n }\n\n return url.toString();\n}\n\n/**\n * Create YouTube embed element.\n */\nfunction createYouTubeElement(\n videoId: string,\n options: Required<YouTubeOptions>,\n title?: string,\n start?: string,\n): Element {\n const params: Record<string, string> = {};\n if (start) {\n params.start = start;\n }\n\n const embedUrl = buildEmbedUrl(videoId, options, params);\n\n const iframeProps: Properties = {\n src: embedUrl,\n title: title || `YouTube video ${videoId}`,\n allow:\n \"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share\",\n referrerpolicy: \"strict-origin-when-cross-origin\",\n allowfullscreen: options.allowFullscreen || undefined,\n loading: options.lazyLoad ? \"lazy\" : undefined,\n };\n\n const iframe: Element = {\n type: \"element\",\n tagName: \"iframe\",\n properties: iframeProps,\n children: [],\n };\n\n return {\n type: \"element\",\n tagName: \"div\",\n properties: {\n className: [\"ox-youtube\"],\n style: `aspect-ratio: ${options.aspectRatio};`,\n },\n children: [iframe],\n };\n}\n\n/**\n * Rehype plugin to transform YouTube components.\n */\nfunction rehypeYouTube(options: Required<YouTubeOptions>) {\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 <YouTube> component\n if (child.tagName.toLowerCase() === \"youtube\") {\n const id = getAttribute(child, \"id\");\n const url = getAttribute(child, \"url\");\n const title = getAttribute(child, \"title\");\n const start = getAttribute(child, \"start\");\n\n // Extract video ID from id or url attribute\n const videoId = id ? extractVideoId(id) : url ? extractVideoId(url) : null;\n\n if (videoId) {\n const youtubeElement = createYouTubeElement(videoId, options, title, start);\n node.children[i] = youtubeElement;\n }\n } else {\n visit(child);\n }\n }\n }\n }\n };\n\n visit(tree);\n };\n}\n\n/**\n * Transform YouTube components in HTML.\n */\nexport async function transformYouTube(html: string, options?: YouTubeOptions): Promise<string> {\n const mergedOptions = { ...defaultOptions, ...options };\n\n const result = await unified()\n .use(rehypeParse, { fragment: true })\n .use(rehypeYouTube, mergedOptions)\n .use(rehypeStringify)\n .process(html);\n\n return String(result);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;AAuBA,MAAM,iBAA2C;CAC/C,iBAAiB;CACjB,aAAa;CACb,iBAAiB;CACjB,UAAU;CACX;;;;AAKD,SAAS,aAAa,IAAa,MAAkC;CACnE,MAAM,QAAQ,GAAG,aAAa;AAC9B,KAAI,OAAO,UAAU,SAAU,QAAO;AACtC,KAAI,MAAM,QAAQ,MAAM,CAAE,QAAO,MAAM,KAAK,IAAI;;;;;AAOlD,SAAgB,eAAe,OAA8B;AAE3D,KAAI,sBAAsB,KAAK,MAAM,CACnC,QAAO;AAST,MAAK,MAAM,WALM,CACf,sGACA,4CACD,EAE+B;EAC9B,MAAM,QAAQ,MAAM,MAAM,QAAQ;AAClC,MAAI,MAAO,QAAO,MAAM;;AAG1B,QAAO;;;;;AAMT,SAAS,cACP,SACA,SACA,QACQ;CACR,MAAM,SAAS,QAAQ,kBAAkB,6BAA6B;CACtE,MAAM,MAAM,IAAI,IAAI,WAAW,OAAO,SAAS,UAAU;AAGzD,KAAI,OACF,MAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,OAAO,CAC/C,KAAI,aAAa,IAAI,KAAK,MAAM;AAIpC,QAAO,IAAI,UAAU;;;;;AAMvB,SAAS,qBACP,SACA,SACA,OACA,OACS;CACT,MAAM,SAAiC,EAAE;AACzC,KAAI,MACF,QAAO,QAAQ;CAejB,MAAM,SAAkB;EACtB,MAAM;EACN,SAAS;EACT,YAb8B;GAC9B,KAHe,cAAc,SAAS,SAAS,OAAO;GAItD,OAAO,SAAS,iBAAiB;GACjC,OACE;GACF,gBAAgB;GAChB,iBAAiB,QAAQ,mBAAmB,KAAA;GAC5C,SAAS,QAAQ,WAAW,SAAS,KAAA;GACtC;EAMC,UAAU,EAAE;EACb;AAED,QAAO;EACL,MAAM;EACN,SAAS;EACT,YAAY;GACV,WAAW,CAAC,aAAa;GACzB,OAAO,iBAAiB,QAAQ,YAAY;GAC7C;EACD,UAAU,CAAC,OAAO;EACnB;;;;;AAMH,SAAS,cAAc,SAAmC;AACxD,SAAQ,SAAe;EACrB,MAAM,SAAS,SAAyB;AACtC,OAAI,cAAc,KAChB,MAAK,IAAI,IAAI,GAAG,IAAI,KAAK,SAAS,QAAQ,KAAK;IAC7C,MAAM,QAAQ,KAAK,SAAS;AAE5B,QAAI,MAAM,SAAS,UAEjB,KAAI,MAAM,QAAQ,aAAa,KAAK,WAAW;KAC7C,MAAM,KAAK,aAAa,OAAO,KAAK;KACpC,MAAM,MAAM,aAAa,OAAO,MAAM;KACtC,MAAM,QAAQ,aAAa,OAAO,QAAQ;KAC1C,MAAM,QAAQ,aAAa,OAAO,QAAQ;KAG1C,MAAM,UAAU,KAAK,eAAe,GAAG,GAAG,MAAM,eAAe,IAAI,GAAG;AAEtE,SAAI,SAAS;MACX,MAAM,iBAAiB,qBAAqB,SAAS,SAAS,OAAO,MAAM;AAC3E,WAAK,SAAS,KAAK;;UAGrB,OAAM,MAAM;;;AAOtB,QAAM,KAAK;;;;;;AAOf,eAAsB,iBAAiB,MAAc,SAA2C;CAC9F,MAAM,gBAAgB;EAAE,GAAG;EAAgB,GAAG;EAAS;CAEvD,MAAM,SAAS,OAAA,GAAA,QAAA,UAAe,CAC3B,IAAIA,aAAAA,SAAa,EAAE,UAAU,MAAM,CAAC,CACpC,IAAI,eAAe,cAAc,CACjC,IAAIC,iBAAAA,QAAgB,CACpB,QAAQ,KAAK;AAEhB,QAAO,OAAO,OAAO"}
|
package/dist/youtube.mjs
ADDED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { unified } from "unified";
|
|
2
2
|
import rehypeParse from "rehype-parse";
|
|
3
3
|
import rehypeStringify from "rehype-stringify";
|
|
4
|
-
|
|
5
4
|
//#region src/plugins/youtube.ts
|
|
6
5
|
/**
|
|
7
6
|
* YouTube Plugin - Privacy-enhanced iframe embedding
|
|
@@ -107,7 +106,7 @@ async function transformYouTube(html, options) {
|
|
|
107
106
|
const result = await unified().use(rehypeParse, { fragment: true }).use(rehypeYouTube, mergedOptions).use(rehypeStringify).process(html);
|
|
108
107
|
return String(result);
|
|
109
108
|
}
|
|
110
|
-
|
|
111
109
|
//#endregion
|
|
112
110
|
export { transformYouTube as n, extractVideoId as t };
|
|
113
|
-
|
|
111
|
+
|
|
112
|
+
//# sourceMappingURL=youtube2.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
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\n * using youtube-nocookie.com for enhanced privacy.\n */\n\nimport { unified } from \"unified\";\nimport rehypeParse from \"rehype-parse\";\nimport rehypeStringify from \"rehype-stringify\";\nimport type { Root, Element, Properties } from \"hast\";\n\nexport interface YouTubeOptions {\n /** Use privacy-enhanced mode (youtube-nocookie.com). Default: true */\n privacyEnhanced?: boolean;\n /** Default aspect ratio. Default: \"16/9\" */\n aspectRatio?: string;\n /** Allow fullscreen. Default: true */\n allowFullscreen?: boolean;\n /** Lazy load iframe. Default: true */\n lazyLoad?: boolean;\n}\n\nconst defaultOptions: Required<YouTubeOptions> = {\n privacyEnhanced: true,\n aspectRatio: \"16/9\",\n allowFullscreen: true,\n lazyLoad: true,\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 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 * Build YouTube embed URL with parameters.\n */\nfunction buildEmbedUrl(\n videoId: string,\n options: Required<YouTubeOptions>,\n params?: Record<string, string>,\n): string {\n const domain = options.privacyEnhanced ? \"www.youtube-nocookie.com\" : \"www.youtube.com\";\n const url = new URL(`https://${domain}/embed/${videoId}`);\n\n // Add any custom parameters\n if (params) {\n for (const [key, value] of Object.entries(params)) {\n url.searchParams.set(key, value);\n }\n }\n\n return url.toString();\n}\n\n/**\n * Create YouTube embed element.\n */\nfunction createYouTubeElement(\n videoId: string,\n options: Required<YouTubeOptions>,\n title?: string,\n start?: string,\n): Element {\n const params: Record<string, string> = {};\n if (start) {\n params.start = start;\n }\n\n const embedUrl = buildEmbedUrl(videoId, options, params);\n\n const iframeProps: Properties = {\n src: embedUrl,\n title: title || `YouTube video ${videoId}`,\n allow:\n \"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share\",\n referrerpolicy: \"strict-origin-when-cross-origin\",\n allowfullscreen: options.allowFullscreen || undefined,\n loading: options.lazyLoad ? \"lazy\" : undefined,\n };\n\n const iframe: Element = {\n type: \"element\",\n tagName: \"iframe\",\n properties: iframeProps,\n children: [],\n };\n\n return {\n type: \"element\",\n tagName: \"div\",\n properties: {\n className: [\"ox-youtube\"],\n style: `aspect-ratio: ${options.aspectRatio};`,\n },\n children: [iframe],\n };\n}\n\n/**\n * Rehype plugin to transform YouTube components.\n */\nfunction rehypeYouTube(options: Required<YouTubeOptions>) {\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 <YouTube> component\n if (child.tagName.toLowerCase() === \"youtube\") {\n const id = getAttribute(child, \"id\");\n const url = getAttribute(child, \"url\");\n const title = getAttribute(child, \"title\");\n const start = getAttribute(child, \"start\");\n\n // Extract video ID from id or url attribute\n const videoId = id ? extractVideoId(id) : url ? extractVideoId(url) : null;\n\n if (videoId) {\n const youtubeElement = createYouTubeElement(videoId, options, title, start);\n node.children[i] = youtubeElement;\n }\n } else {\n visit(child);\n }\n }\n }\n }\n };\n\n visit(tree);\n };\n}\n\n/**\n * Transform YouTube components in HTML.\n */\nexport async function transformYouTube(html: string, options?: YouTubeOptions): Promise<string> {\n const mergedOptions = { ...defaultOptions, ...options };\n\n const result = await unified()\n .use(rehypeParse, { fragment: true })\n .use(rehypeYouTube, mergedOptions)\n .use(rehypeStringify)\n .process(html);\n\n return String(result);\n}\n"],"mappings":";;;;;;;;;;AAuBA,MAAM,iBAA2C;CAC/C,iBAAiB;CACjB,aAAa;CACb,iBAAiB;CACjB,UAAU;CACX;;;;AAKD,SAAS,aAAa,IAAa,MAAkC;CACnE,MAAM,QAAQ,GAAG,aAAa;AAC9B,KAAI,OAAO,UAAU,SAAU,QAAO;AACtC,KAAI,MAAM,QAAQ,MAAM,CAAE,QAAO,MAAM,KAAK,IAAI;;;;;AAOlD,SAAgB,eAAe,OAA8B;AAE3D,KAAI,sBAAsB,KAAK,MAAM,CACnC,QAAO;AAST,MAAK,MAAM,WALM,CACf,sGACA,4CACD,EAE+B;EAC9B,MAAM,QAAQ,MAAM,MAAM,QAAQ;AAClC,MAAI,MAAO,QAAO,MAAM;;AAG1B,QAAO;;;;;AAMT,SAAS,cACP,SACA,SACA,QACQ;CACR,MAAM,SAAS,QAAQ,kBAAkB,6BAA6B;CACtE,MAAM,MAAM,IAAI,IAAI,WAAW,OAAO,SAAS,UAAU;AAGzD,KAAI,OACF,MAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,OAAO,CAC/C,KAAI,aAAa,IAAI,KAAK,MAAM;AAIpC,QAAO,IAAI,UAAU;;;;;AAMvB,SAAS,qBACP,SACA,SACA,OACA,OACS;CACT,MAAM,SAAiC,EAAE;AACzC,KAAI,MACF,QAAO,QAAQ;CAejB,MAAM,SAAkB;EACtB,MAAM;EACN,SAAS;EACT,YAb8B;GAC9B,KAHe,cAAc,SAAS,SAAS,OAAO;GAItD,OAAO,SAAS,iBAAiB;GACjC,OACE;GACF,gBAAgB;GAChB,iBAAiB,QAAQ,mBAAmB,KAAA;GAC5C,SAAS,QAAQ,WAAW,SAAS,KAAA;GACtC;EAMC,UAAU,EAAE;EACb;AAED,QAAO;EACL,MAAM;EACN,SAAS;EACT,YAAY;GACV,WAAW,CAAC,aAAa;GACzB,OAAO,iBAAiB,QAAQ,YAAY;GAC7C;EACD,UAAU,CAAC,OAAO;EACnB;;;;;AAMH,SAAS,cAAc,SAAmC;AACxD,SAAQ,SAAe;EACrB,MAAM,SAAS,SAAyB;AACtC,OAAI,cAAc,KAChB,MAAK,IAAI,IAAI,GAAG,IAAI,KAAK,SAAS,QAAQ,KAAK;IAC7C,MAAM,QAAQ,KAAK,SAAS;AAE5B,QAAI,MAAM,SAAS,UAEjB,KAAI,MAAM,QAAQ,aAAa,KAAK,WAAW;KAC7C,MAAM,KAAK,aAAa,OAAO,KAAK;KACpC,MAAM,MAAM,aAAa,OAAO,MAAM;KACtC,MAAM,QAAQ,aAAa,OAAO,QAAQ;KAC1C,MAAM,QAAQ,aAAa,OAAO,QAAQ;KAG1C,MAAM,UAAU,KAAK,eAAe,GAAG,GAAG,MAAM,eAAe,IAAI,GAAG;AAEtE,SAAI,SAAS;MACX,MAAM,iBAAiB,qBAAqB,SAAS,SAAS,OAAO,MAAM;AAC3E,WAAK,SAAS,KAAK;;UAGrB,OAAM,MAAM;;;AAOtB,QAAM,KAAK;;;;;;AAOf,eAAsB,iBAAiB,MAAc,SAA2C;CAC9F,MAAM,gBAAgB;EAAE,GAAG;EAAgB,GAAG;EAAS;CAEvD,MAAM,SAAS,MAAM,SAAS,CAC3B,IAAI,aAAa,EAAE,UAAU,MAAM,CAAC,CACpC,IAAI,eAAe,cAAc,CACjC,IAAI,gBAAgB,CACpB,QAAQ,KAAK;AAEhB,QAAO,OAAO,OAAO"}
|
package/package.json
CHANGED
|
@@ -1,26 +1,70 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ox-content/vite-plugin",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"description": "Vite plugin for Ox Content - High-performance Markdown processing with Environment API",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"environment-api",
|
|
7
|
+
"markdown",
|
|
8
|
+
"ox-content",
|
|
9
|
+
"ssg",
|
|
10
|
+
"vite",
|
|
11
|
+
"vite-plugin"
|
|
12
|
+
],
|
|
13
|
+
"license": "MIT",
|
|
14
|
+
"author": "ubugeeei",
|
|
15
|
+
"repository": {
|
|
16
|
+
"type": "git",
|
|
17
|
+
"url": "https://github.com/ubugeeei/ox-content.git",
|
|
18
|
+
"directory": "npm/vite-plugin-ox-content"
|
|
19
|
+
},
|
|
20
|
+
"files": [
|
|
21
|
+
"dist"
|
|
22
|
+
],
|
|
5
23
|
"type": "module",
|
|
6
|
-
"main": "./dist/index.
|
|
7
|
-
"types": "./dist/index.d.
|
|
24
|
+
"main": "./dist/index.cjs",
|
|
25
|
+
"types": "./dist/index.d.mts",
|
|
8
26
|
"exports": {
|
|
9
27
|
".": {
|
|
10
|
-
"import": "./dist/index.
|
|
11
|
-
"
|
|
28
|
+
"import": "./dist/index.mjs",
|
|
29
|
+
"require": "./dist/index.cjs",
|
|
30
|
+
"types": "./dist/index.d.mts"
|
|
12
31
|
}
|
|
13
32
|
},
|
|
14
|
-
"
|
|
15
|
-
"
|
|
16
|
-
|
|
33
|
+
"publishConfig": {
|
|
34
|
+
"access": "public",
|
|
35
|
+
"provenance": true
|
|
36
|
+
},
|
|
37
|
+
"dependencies": {
|
|
38
|
+
"@mermaid-js/mermaid-cli": "^11.12.0",
|
|
39
|
+
"glob": "^11.0.0",
|
|
40
|
+
"playwright": "^1.59.1",
|
|
41
|
+
"puppeteer": "^23.11.1",
|
|
42
|
+
"rehype-parse": "^9.0.1",
|
|
43
|
+
"rehype-stringify": "^10.0.1",
|
|
44
|
+
"rolldown": "^1.0.0-rc.1",
|
|
45
|
+
"shiki": "^1.24.0",
|
|
46
|
+
"typescript": "^5.7.0",
|
|
47
|
+
"unified": "^11.0.5",
|
|
48
|
+
"@ox-content/napi": "2.0.0"
|
|
49
|
+
},
|
|
50
|
+
"devDependencies": {
|
|
51
|
+
"@playwright/test": "^1.59.1",
|
|
52
|
+
"@types/hast": "^3.0.4",
|
|
53
|
+
"@types/node": "^22.0.0",
|
|
54
|
+
"@types/react": "^19.0.0",
|
|
55
|
+
"@types/react-dom": "^19.0.0",
|
|
56
|
+
"@typescript/native-preview": "^7.0.0-dev.20250601",
|
|
57
|
+
"vite": "npm:@voidzero-dev/vite-plus-core@0.1.11",
|
|
58
|
+
"vite-plus": "0.1.11",
|
|
59
|
+
"yaml": "^2.8.2"
|
|
60
|
+
},
|
|
17
61
|
"peerDependencies": {
|
|
18
|
-
"vite": "^8.0.0",
|
|
19
|
-
"vue": "^3.4.0",
|
|
20
62
|
"@vue/compiler-sfc": "^3.4.0",
|
|
21
|
-
"svelte": "^5.0.0",
|
|
22
63
|
"react": "^19.0.0",
|
|
23
|
-
"react-dom": "^19.0.0"
|
|
64
|
+
"react-dom": "^19.0.0",
|
|
65
|
+
"svelte": "^5.0.0",
|
|
66
|
+
"vite": "npm:@voidzero-dev/vite-plus-core@0.1.11",
|
|
67
|
+
"vue": "^3.4.0"
|
|
24
68
|
},
|
|
25
69
|
"peerDependenciesMeta": {
|
|
26
70
|
"vue": {
|
|
@@ -39,50 +83,11 @@
|
|
|
39
83
|
"optional": true
|
|
40
84
|
}
|
|
41
85
|
},
|
|
42
|
-
"dependencies": {
|
|
43
|
-
"@mermaid-js/mermaid-cli": "^11.12.0",
|
|
44
|
-
"playwright": "^1.50.0",
|
|
45
|
-
"rolldown": "^1.0.0-rc.1",
|
|
46
|
-
"glob": "^11.0.0",
|
|
47
|
-
"rehype-parse": "^9.0.1",
|
|
48
|
-
"rehype-stringify": "^10.0.1",
|
|
49
|
-
"shiki": "^1.24.0",
|
|
50
|
-
"unified": "^11.0.5",
|
|
51
|
-
"@ox-content/napi": "1.0.0-alpha.0"
|
|
52
|
-
},
|
|
53
|
-
"devDependencies": {
|
|
54
|
-
"@types/hast": "^3.0.4",
|
|
55
|
-
"@types/node": "^22.0.0",
|
|
56
|
-
"@types/react": "^19.0.0",
|
|
57
|
-
"@types/react-dom": "^19.0.0",
|
|
58
|
-
"@typescript/native-preview": "^7.0.0-dev.20250601",
|
|
59
|
-
"tsdown": "^0.12.0",
|
|
60
|
-
"typescript": "^5.7.0",
|
|
61
|
-
"vite": "^8.0.0",
|
|
62
|
-
"yaml": "^2.8.2"
|
|
63
|
-
},
|
|
64
|
-
"keywords": [
|
|
65
|
-
"vite",
|
|
66
|
-
"vite-plugin",
|
|
67
|
-
"markdown",
|
|
68
|
-
"ox-content",
|
|
69
|
-
"ssg",
|
|
70
|
-
"environment-api"
|
|
71
|
-
],
|
|
72
|
-
"license": "MIT",
|
|
73
|
-
"author": "ubugeeei",
|
|
74
|
-
"repository": {
|
|
75
|
-
"type": "git",
|
|
76
|
-
"url": "https://github.com/ubugeeei/ox-content.git",
|
|
77
|
-
"directory": "npm/vite-plugin-ox-content"
|
|
78
|
-
},
|
|
79
|
-
"publishConfig": {
|
|
80
|
-
"provenance": true,
|
|
81
|
-
"access": "public"
|
|
82
|
-
},
|
|
83
86
|
"scripts": {
|
|
84
|
-
"build": "
|
|
85
|
-
"dev": "
|
|
86
|
-
"typecheck": "tsgo --noEmit"
|
|
87
|
+
"build": "vp pack",
|
|
88
|
+
"dev": "vp pack --watch",
|
|
89
|
+
"typecheck": "tsgo --noEmit",
|
|
90
|
+
"test:vrt": "playwright test",
|
|
91
|
+
"test:vrt:update": "playwright test --update-snapshots"
|
|
87
92
|
}
|
|
88
93
|
}
|
package/dist/github.js
DELETED