@ox-content/vite-plugin 2.69.0 → 2.71.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/github.cjs +270 -290
- package/dist/github.cjs.map +1 -1
- package/dist/github.mjs +622 -2
- package/dist/github.mjs.map +1 -0
- package/dist/github2.mjs +2 -632
- package/dist/index.cjs +126 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +56 -18
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +56 -18
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +126 -4
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
- package/dist/github2.mjs.map +0 -1
package/dist/github.mjs
CHANGED
|
@@ -1,2 +1,622 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
1
|
+
import { unified } from "unified";
|
|
2
|
+
import rehypeParse from "rehype-parse";
|
|
3
|
+
import rehypeStringify from "rehype-stringify";
|
|
4
|
+
import { Buffer } from "node:buffer";
|
|
5
|
+
//#region src/plugins/github/validation.ts
|
|
6
|
+
const GITHUB_REPO_RE = /^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/;
|
|
7
|
+
function hasControlChar(value) {
|
|
8
|
+
for (let index = 0; index < value.length; index++) {
|
|
9
|
+
const code = value.charCodeAt(index);
|
|
10
|
+
if (code <= 31 || code === 127) return true;
|
|
11
|
+
}
|
|
12
|
+
return false;
|
|
13
|
+
}
|
|
14
|
+
function isSafeGitHubRepo(repo) {
|
|
15
|
+
return GITHUB_REPO_RE.test(repo) && !repo.split("/").some((part) => part === "." || part === "..");
|
|
16
|
+
}
|
|
17
|
+
function isSafeGitHubRef(ref) {
|
|
18
|
+
return Boolean(ref) && !hasControlChar(ref) && !hasUnsafePathSegment(ref);
|
|
19
|
+
}
|
|
20
|
+
function isSafeGitHubPath(path) {
|
|
21
|
+
return Boolean(path) && !hasControlChar(path) && !hasUnsafePathSegment(path);
|
|
22
|
+
}
|
|
23
|
+
function hasUnsafePathSegment(value) {
|
|
24
|
+
return value.split("/").some((part) => !part || part === "." || part === ".." || part.includes("\\"));
|
|
25
|
+
}
|
|
26
|
+
function encodePath(path) {
|
|
27
|
+
return path.split("/").map(encodeURIComponent).join("/");
|
|
28
|
+
}
|
|
29
|
+
//#endregion
|
|
30
|
+
//#region src/plugins/github/source.ts
|
|
31
|
+
const EXTENSION_LANGUAGE_MAP = new Map([
|
|
32
|
+
["cjs", "javascript"],
|
|
33
|
+
["css", "css"],
|
|
34
|
+
["go", "go"],
|
|
35
|
+
["html", "html"],
|
|
36
|
+
["js", "javascript"],
|
|
37
|
+
["json", "json"],
|
|
38
|
+
["jsx", "jsx"],
|
|
39
|
+
["md", "markdown"],
|
|
40
|
+
["mdx", "mdx"],
|
|
41
|
+
["mjs", "javascript"],
|
|
42
|
+
["py", "python"],
|
|
43
|
+
["rb", "ruby"],
|
|
44
|
+
["rs", "rust"],
|
|
45
|
+
["sh", "shell"],
|
|
46
|
+
["svelte", "svelte"],
|
|
47
|
+
["toml", "toml"],
|
|
48
|
+
["ts", "typescript"],
|
|
49
|
+
["tsx", "tsx"],
|
|
50
|
+
["vue", "vue"],
|
|
51
|
+
["yaml", "yaml"],
|
|
52
|
+
["yml", "yaml"]
|
|
53
|
+
]);
|
|
54
|
+
function sourceKey(source) {
|
|
55
|
+
return `${source.repo}@${source.ref}:${source.path}`;
|
|
56
|
+
}
|
|
57
|
+
function formatLineRange(lines) {
|
|
58
|
+
return lines.start === lines.end ? `L${lines.start}` : `L${lines.start}-L${lines.end}`;
|
|
59
|
+
}
|
|
60
|
+
function parseGitHubLineRange(value) {
|
|
61
|
+
if (!value) return void 0;
|
|
62
|
+
const match = value.trim().match(/^#?L?(\d+)(?:-L?(\d+))?$/i);
|
|
63
|
+
if (!match) return void 0;
|
|
64
|
+
const start = Number.parseInt(match[1], 10);
|
|
65
|
+
const end = match[2] ? Number.parseInt(match[2], 10) : start;
|
|
66
|
+
if (!Number.isSafeInteger(start) || !Number.isSafeInteger(end) || start < 1 || end < start) return;
|
|
67
|
+
return {
|
|
68
|
+
start,
|
|
69
|
+
end
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
function createGitHubPermalink(source) {
|
|
73
|
+
const fragment = source.lines ? `#${formatLineRange(source.lines)}` : "";
|
|
74
|
+
return `https://github.com/${source.repo}/blob/${encodeURIComponent(source.ref)}/${encodePath(source.path)}${fragment}`;
|
|
75
|
+
}
|
|
76
|
+
function parseGitHubPermalink(value) {
|
|
77
|
+
let url;
|
|
78
|
+
try {
|
|
79
|
+
url = new URL(value);
|
|
80
|
+
} catch {
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
if (url.protocol !== "https:" || url.hostname !== "github.com") return null;
|
|
84
|
+
let parts;
|
|
85
|
+
try {
|
|
86
|
+
parts = url.pathname.split("/").filter(Boolean).map((part) => decodeURIComponent(part));
|
|
87
|
+
} catch {
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
if (parts.length < 5 || parts[2] !== "blob") return null;
|
|
91
|
+
const repo = `${parts[0]}/${parts[1]}`;
|
|
92
|
+
const ref = parts[3];
|
|
93
|
+
const path = parts.slice(4).join("/");
|
|
94
|
+
if (!isSafeGitHubRepo(repo) || !isSafeGitHubRef(ref) || !isSafeGitHubPath(path)) return null;
|
|
95
|
+
const source = {
|
|
96
|
+
repo,
|
|
97
|
+
ref,
|
|
98
|
+
path,
|
|
99
|
+
lines: parseGitHubLineRange(url.hash)
|
|
100
|
+
};
|
|
101
|
+
return {
|
|
102
|
+
...source,
|
|
103
|
+
permalink: createGitHubPermalink(source)
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
function inferLanguage(path) {
|
|
107
|
+
const fileName = path.split("/").at(-1)?.toLowerCase() ?? "";
|
|
108
|
+
if (fileName === "dockerfile") return "dockerfile";
|
|
109
|
+
if (fileName === "makefile") return "makefile";
|
|
110
|
+
const extension = fileName.includes(".") ? fileName.split(".").at(-1) : void 0;
|
|
111
|
+
return extension ? EXTENSION_LANGUAGE_MAP.get(extension) ?? extension : null;
|
|
112
|
+
}
|
|
113
|
+
//#endregion
|
|
114
|
+
//#region src/plugins/github/types.ts
|
|
115
|
+
const defaultOptions = {
|
|
116
|
+
token: "",
|
|
117
|
+
cache: true,
|
|
118
|
+
cacheTTL: 36e5,
|
|
119
|
+
maxSourceBytes: 2e5,
|
|
120
|
+
maxSourceLines: 120
|
|
121
|
+
};
|
|
122
|
+
//#endregion
|
|
123
|
+
//#region src/plugins/github/api.ts
|
|
124
|
+
const repoCache = /* @__PURE__ */ new Map();
|
|
125
|
+
const sourceCache = /* @__PURE__ */ new Map();
|
|
126
|
+
function githubHeaders(options) {
|
|
127
|
+
const headers = {
|
|
128
|
+
Accept: "application/vnd.github.v3+json",
|
|
129
|
+
"User-Agent": "ox-content-github-plugin"
|
|
130
|
+
};
|
|
131
|
+
if (options.token) headers.Authorization = `Bearer ${options.token}`;
|
|
132
|
+
return headers;
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Fetch repository data from GitHub API.
|
|
136
|
+
*/
|
|
137
|
+
async function fetchRepoData(repo, options) {
|
|
138
|
+
if (!isSafeGitHubRepo(repo)) return null;
|
|
139
|
+
if (options.cache) {
|
|
140
|
+
const cached = repoCache.get(repo);
|
|
141
|
+
if (cached && Date.now() - cached.timestamp < options.cacheTTL) return cached.data;
|
|
142
|
+
}
|
|
143
|
+
try {
|
|
144
|
+
const response = await fetch(`https://api.github.com/repos/${repo}`, { headers: githubHeaders(options) });
|
|
145
|
+
if (!response.ok) {
|
|
146
|
+
console.warn(`Failed to fetch GitHub repo ${repo}: ${response.status}`);
|
|
147
|
+
return null;
|
|
148
|
+
}
|
|
149
|
+
const data = await response.json();
|
|
150
|
+
if (options.cache) repoCache.set(repo, {
|
|
151
|
+
data,
|
|
152
|
+
timestamp: Date.now()
|
|
153
|
+
});
|
|
154
|
+
return data;
|
|
155
|
+
} catch (error) {
|
|
156
|
+
console.warn(`Error fetching GitHub repo ${repo}:`, error);
|
|
157
|
+
return null;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Fetch source file data from GitHub API.
|
|
162
|
+
*/
|
|
163
|
+
async function fetchGitHubSource(source, options) {
|
|
164
|
+
if (!isSafeGitHubRepo(source.repo) || !isSafeGitHubRef(source.ref) || !isSafeGitHubPath(source.path)) return null;
|
|
165
|
+
const key = sourceKey(source);
|
|
166
|
+
if (options.cache) {
|
|
167
|
+
const cached = sourceCache.get(key);
|
|
168
|
+
if (cached && Date.now() - cached.timestamp < options.cacheTTL) return cached.data;
|
|
169
|
+
}
|
|
170
|
+
try {
|
|
171
|
+
const apiUrl = `https://api.github.com/repos/${source.repo}/contents/${encodePath(source.path)}?ref=${encodeURIComponent(source.ref)}`;
|
|
172
|
+
const response = await fetch(apiUrl, { headers: githubHeaders(options) });
|
|
173
|
+
if (!response.ok) {
|
|
174
|
+
console.warn(`Failed to fetch GitHub source ${source.permalink}: ${response.status}`);
|
|
175
|
+
return null;
|
|
176
|
+
}
|
|
177
|
+
const data = await response.json();
|
|
178
|
+
if (data.type !== "file" || data.encoding !== "base64" || !data.content || (data.size ?? 0) > options.maxSourceBytes) return null;
|
|
179
|
+
const content = Buffer.from(data.content.replace(/\s/g, ""), "base64").toString("utf8");
|
|
180
|
+
if (Buffer.byteLength(content) > options.maxSourceBytes) return null;
|
|
181
|
+
const sourceData = {
|
|
182
|
+
repo: source.repo,
|
|
183
|
+
ref: source.ref,
|
|
184
|
+
path: source.path,
|
|
185
|
+
permalink: source.permalink,
|
|
186
|
+
content,
|
|
187
|
+
size: data.size ?? Buffer.byteLength(content),
|
|
188
|
+
html_url: data.html_url ?? source.permalink,
|
|
189
|
+
language: inferLanguage(source.path)
|
|
190
|
+
};
|
|
191
|
+
if (options.cache) sourceCache.set(key, {
|
|
192
|
+
data: sourceData,
|
|
193
|
+
timestamp: Date.now()
|
|
194
|
+
});
|
|
195
|
+
return sourceData;
|
|
196
|
+
} catch (error) {
|
|
197
|
+
console.warn(`Error fetching GitHub source ${source.permalink}:`, error);
|
|
198
|
+
return null;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* Pre-fetch all GitHub repos data.
|
|
203
|
+
*/
|
|
204
|
+
async function prefetchGitHubRepos(repos, options) {
|
|
205
|
+
const mergedOptions = {
|
|
206
|
+
...defaultOptions,
|
|
207
|
+
...options
|
|
208
|
+
};
|
|
209
|
+
const results = /* @__PURE__ */ new Map();
|
|
210
|
+
await Promise.all(Array.from(new Set(repos)).map(async (repo) => {
|
|
211
|
+
const data = await fetchRepoData(repo, mergedOptions);
|
|
212
|
+
results.set(repo, data);
|
|
213
|
+
}));
|
|
214
|
+
return results;
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* Pre-fetch all GitHub source files.
|
|
218
|
+
*/
|
|
219
|
+
async function prefetchGitHubSources(sources, options) {
|
|
220
|
+
const mergedOptions = {
|
|
221
|
+
...defaultOptions,
|
|
222
|
+
...options
|
|
223
|
+
};
|
|
224
|
+
const results = /* @__PURE__ */ new Map();
|
|
225
|
+
const uniqueSources = Array.from(new Map(sources.map((source) => [sourceKey(source), source])).values());
|
|
226
|
+
await Promise.all(uniqueSources.map(async (source) => {
|
|
227
|
+
const data = await fetchGitHubSource(source, mergedOptions);
|
|
228
|
+
results.set(sourceKey(source), data);
|
|
229
|
+
}));
|
|
230
|
+
return results;
|
|
231
|
+
}
|
|
232
|
+
//#endregion
|
|
233
|
+
//#region src/plugins/github/attributes.ts
|
|
234
|
+
const GITHUB_COMPONENT_RE = /<github\b([^>]*)>/gi;
|
|
235
|
+
const ATTRIBUTE_RE = /([:\w-]+)(?:\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s"'>/]+)))?/g;
|
|
236
|
+
/**
|
|
237
|
+
* Collect all GitHub repos from HTML for pre-fetching.
|
|
238
|
+
*/
|
|
239
|
+
async function collectGitHubRepos(html) {
|
|
240
|
+
const repos = [];
|
|
241
|
+
GITHUB_COMPONENT_RE.lastIndex = 0;
|
|
242
|
+
let match;
|
|
243
|
+
while ((match = GITHUB_COMPONENT_RE.exec(html)) !== null) {
|
|
244
|
+
const attrs = parseAttributes(match[1]);
|
|
245
|
+
if (attrs.path || attrs.file || attrs.permalink || attrs.url || attrs.href) continue;
|
|
246
|
+
const repo = attrs.repo;
|
|
247
|
+
if (repo && isSafeGitHubRepo(repo)) repos.push(repo);
|
|
248
|
+
}
|
|
249
|
+
return repos;
|
|
250
|
+
}
|
|
251
|
+
/**
|
|
252
|
+
* Collect all GitHub source references from HTML for pre-fetching.
|
|
253
|
+
*/
|
|
254
|
+
async function collectGitHubSources(html) {
|
|
255
|
+
const sources = [];
|
|
256
|
+
GITHUB_COMPONENT_RE.lastIndex = 0;
|
|
257
|
+
let match;
|
|
258
|
+
while ((match = GITHUB_COMPONENT_RE.exec(html)) !== null) {
|
|
259
|
+
const source = sourceRefFromAttributes(parseAttributes(match[1]));
|
|
260
|
+
if (source) sources.push(source);
|
|
261
|
+
}
|
|
262
|
+
return sources;
|
|
263
|
+
}
|
|
264
|
+
function parseAttributes(raw) {
|
|
265
|
+
const attrs = {};
|
|
266
|
+
ATTRIBUTE_RE.lastIndex = 0;
|
|
267
|
+
let match;
|
|
268
|
+
while ((match = ATTRIBUTE_RE.exec(raw)) !== null) attrs[match[1].toLowerCase()] = match[2] ?? match[3] ?? match[4] ?? "";
|
|
269
|
+
return attrs;
|
|
270
|
+
}
|
|
271
|
+
function attributesFromElement(el) {
|
|
272
|
+
const attrs = {};
|
|
273
|
+
for (const name of [
|
|
274
|
+
"permalink",
|
|
275
|
+
"url",
|
|
276
|
+
"href",
|
|
277
|
+
"repo",
|
|
278
|
+
"path",
|
|
279
|
+
"file",
|
|
280
|
+
"ref",
|
|
281
|
+
"sha",
|
|
282
|
+
"branch",
|
|
283
|
+
"loc",
|
|
284
|
+
"lines",
|
|
285
|
+
"line"
|
|
286
|
+
]) {
|
|
287
|
+
const value = getAttribute(el, name);
|
|
288
|
+
if (value !== void 0) attrs[name] = value;
|
|
289
|
+
}
|
|
290
|
+
return attrs;
|
|
291
|
+
}
|
|
292
|
+
function getAttribute(el, name) {
|
|
293
|
+
const value = el.properties?.[name];
|
|
294
|
+
if (typeof value === "string") return value;
|
|
295
|
+
if (Array.isArray(value)) return value.join(" ");
|
|
296
|
+
}
|
|
297
|
+
function sourceRefFromAttributes(attrs) {
|
|
298
|
+
const permalink = attrs.permalink ?? attrs.url ?? attrs.href;
|
|
299
|
+
if (permalink) return parseGitHubPermalink(permalink);
|
|
300
|
+
const repo = attrs.repo;
|
|
301
|
+
const path = attrs.path ?? attrs.file;
|
|
302
|
+
if (!repo || !path || !isSafeGitHubRepo(repo) || !isSafeGitHubPath(path)) return null;
|
|
303
|
+
const ref = attrs.ref ?? attrs.sha ?? attrs.branch ?? "main";
|
|
304
|
+
if (!isSafeGitHubRef(ref)) return null;
|
|
305
|
+
const source = {
|
|
306
|
+
repo,
|
|
307
|
+
ref,
|
|
308
|
+
path,
|
|
309
|
+
lines: parseGitHubLineRange(attrs.loc ?? attrs.lines ?? attrs.line)
|
|
310
|
+
};
|
|
311
|
+
return {
|
|
312
|
+
...source,
|
|
313
|
+
permalink: createGitHubPermalink(source)
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
//#endregion
|
|
317
|
+
//#region src/plugins/github/fallback-card.ts
|
|
318
|
+
/**
|
|
319
|
+
* Create fallback element when repo data is unavailable.
|
|
320
|
+
*/
|
|
321
|
+
function createFallbackCard(repo) {
|
|
322
|
+
return {
|
|
323
|
+
type: "element",
|
|
324
|
+
tagName: "a",
|
|
325
|
+
properties: {
|
|
326
|
+
className: ["ox-github-card", "error"],
|
|
327
|
+
href: isSafeGitHubRepo(repo) ? `https://github.com/${repo}` : "#",
|
|
328
|
+
target: "_blank",
|
|
329
|
+
rel: "noopener noreferrer"
|
|
330
|
+
},
|
|
331
|
+
children: [{
|
|
332
|
+
type: "element",
|
|
333
|
+
tagName: "div",
|
|
334
|
+
properties: { className: ["ox-github-header"] },
|
|
335
|
+
children: [{
|
|
336
|
+
type: "element",
|
|
337
|
+
tagName: "svg",
|
|
338
|
+
properties: {
|
|
339
|
+
className: ["ox-github-icon"],
|
|
340
|
+
viewBox: "0 0 16 16",
|
|
341
|
+
fill: "currentColor"
|
|
342
|
+
},
|
|
343
|
+
children: [{
|
|
344
|
+
type: "element",
|
|
345
|
+
tagName: "path",
|
|
346
|
+
properties: { d: "M8 0c4.42 0 8 3.58 8 8a8.013 8.013 0 0 1-5.45 7.59c-.4.08-.55-.17-.55-.38 0-.27.01-1.13.01-2.2 0-.75-.25-1.23-.54-1.48 1.78-.2 3.65-.88 3.65-3.95 0-.88-.31-1.59-.82-2.15.08-.2.36-1.02-.08-2.12 0 0-.67-.22-2.2.82-.64-.18-1.32-.27-2-.27-.68 0-1.36.09-2 .27-1.53-1.03-2.2-.82-2.2-.82-.44 1.1-.16 1.92-.08 2.12-.51.56-.82 1.28-.82 2.15 0 3.06 1.86 3.75 3.64 3.95-.23.2-.44.55-.51 1.07-.46.21-1.61.55-2.33-.66-.15-.24-.6-.83-1.23-.82-.67.01-.27.38.01.53.34.19.73.9.82 1.13.16.45.68 1.31 2.69.94 0 .67.01 1.3.01 1.49 0 .21-.15.45-.55.38A7.995 7.995 0 0 1 0 8c0-4.42 3.58-8 8-8Z" },
|
|
347
|
+
children: []
|
|
348
|
+
}]
|
|
349
|
+
}, {
|
|
350
|
+
type: "element",
|
|
351
|
+
tagName: "span",
|
|
352
|
+
properties: { className: ["ox-github-repo"] },
|
|
353
|
+
children: [{
|
|
354
|
+
type: "text",
|
|
355
|
+
value: repo
|
|
356
|
+
}]
|
|
357
|
+
}]
|
|
358
|
+
}]
|
|
359
|
+
};
|
|
360
|
+
}
|
|
361
|
+
//#endregion
|
|
362
|
+
//#region src/plugins/github/repo-card.ts
|
|
363
|
+
function formatNumber(num) {
|
|
364
|
+
if (num >= 1e6) return `${(num / 1e6).toFixed(1)}M`;
|
|
365
|
+
if (num >= 1e3) return `${(num / 1e3).toFixed(1)}k`;
|
|
366
|
+
return String(num);
|
|
367
|
+
}
|
|
368
|
+
function iconPath(d) {
|
|
369
|
+
return {
|
|
370
|
+
type: "element",
|
|
371
|
+
tagName: "svg",
|
|
372
|
+
properties: {
|
|
373
|
+
viewBox: "0 0 16 16",
|
|
374
|
+
fill: "currentColor"
|
|
375
|
+
},
|
|
376
|
+
children: [{
|
|
377
|
+
type: "element",
|
|
378
|
+
tagName: "path",
|
|
379
|
+
properties: { d },
|
|
380
|
+
children: []
|
|
381
|
+
}]
|
|
382
|
+
};
|
|
383
|
+
}
|
|
384
|
+
function createStatsChildren(repoData) {
|
|
385
|
+
const statsChildren = [];
|
|
386
|
+
if (repoData.language) statsChildren.push({
|
|
387
|
+
type: "element",
|
|
388
|
+
tagName: "span",
|
|
389
|
+
properties: { className: ["ox-github-language"] },
|
|
390
|
+
children: [{
|
|
391
|
+
type: "element",
|
|
392
|
+
tagName: "span",
|
|
393
|
+
properties: {
|
|
394
|
+
className: ["ox-github-language-color"],
|
|
395
|
+
"data-lang": repoData.language.toLowerCase()
|
|
396
|
+
},
|
|
397
|
+
children: []
|
|
398
|
+
}, {
|
|
399
|
+
type: "text",
|
|
400
|
+
value: repoData.language
|
|
401
|
+
}]
|
|
402
|
+
});
|
|
403
|
+
statsChildren.push({
|
|
404
|
+
type: "element",
|
|
405
|
+
tagName: "span",
|
|
406
|
+
properties: { className: ["ox-github-stat"] },
|
|
407
|
+
children: [iconPath("M8 .25a.75.75 0 0 1 .673.418l1.882 3.815 4.21.612a.75.75 0 0 1 .416 1.279l-3.046 2.97.719 4.192a.751.751 0 0 1-1.088.791L8 12.347l-3.766 1.98a.75.75 0 0 1-1.088-.79l.72-4.194L.818 6.374a.75.75 0 0 1 .416-1.28l4.21-.611L7.327.668A.75.75 0 0 1 8 .25Z"), {
|
|
408
|
+
type: "text",
|
|
409
|
+
value: formatNumber(repoData.stargazers_count)
|
|
410
|
+
}]
|
|
411
|
+
});
|
|
412
|
+
statsChildren.push({
|
|
413
|
+
type: "element",
|
|
414
|
+
tagName: "span",
|
|
415
|
+
properties: { className: ["ox-github-stat"] },
|
|
416
|
+
children: [iconPath("M5 5.372v.878c0 .414.336.75.75.75h4.5a.75.75 0 0 0 .75-.75v-.878a2.25 2.25 0 1 1 1.5 0v.878a2.25 2.25 0 0 1-2.25 2.25h-1.5v2.128a2.251 2.251 0 1 1-1.5 0V8.5h-1.5A2.25 2.25 0 0 1 3.5 6.25v-.878a2.25 2.25 0 1 1 1.5 0ZM5 3.25a.75.75 0 1 0-1.5 0 .75.75 0 0 0 1.5 0Zm6.75.75a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5Zm-3 8.75a.75.75 0 1 0-1.5 0 .75.75 0 0 0 1.5 0Z"), {
|
|
417
|
+
type: "text",
|
|
418
|
+
value: formatNumber(repoData.forks_count)
|
|
419
|
+
}]
|
|
420
|
+
});
|
|
421
|
+
return statsChildren;
|
|
422
|
+
}
|
|
423
|
+
/**
|
|
424
|
+
* Create GitHub card element from repo data.
|
|
425
|
+
*/
|
|
426
|
+
function createGitHubCard(repoData) {
|
|
427
|
+
return {
|
|
428
|
+
type: "element",
|
|
429
|
+
tagName: "a",
|
|
430
|
+
properties: {
|
|
431
|
+
className: ["ox-github-card"],
|
|
432
|
+
href: repoData.html_url,
|
|
433
|
+
target: "_blank",
|
|
434
|
+
rel: "noopener noreferrer"
|
|
435
|
+
},
|
|
436
|
+
children: [
|
|
437
|
+
{
|
|
438
|
+
type: "element",
|
|
439
|
+
tagName: "div",
|
|
440
|
+
properties: { className: ["ox-github-header"] },
|
|
441
|
+
children: [{
|
|
442
|
+
...iconPath("M2 2.5A2.5 2.5 0 0 1 4.5 0h8.75a.75.75 0 0 1 .75.75v12.5a.75.75 0 0 1-.75.75h-2.5a.75.75 0 0 1 0-1.5h1.75v-2h-8a1 1 0 0 0-.714 1.7.75.75 0 1 1-1.072 1.05A2.495 2.495 0 0 1 2 11.5Zm10.5-1h-8a1 1 0 0 0-1 1v6.708A2.486 2.486 0 0 1 4.5 9h8ZM5 12.25a.25.25 0 0 1 .25-.25h3.5a.25.25 0 0 1 .25.25v3.25a.25.25 0 0 1-.4.2l-1.45-1.087a.249.249 0 0 0-.3 0L5.4 15.7a.25.25 0 0 1-.4-.2Z"),
|
|
443
|
+
properties: {
|
|
444
|
+
className: ["ox-github-icon"],
|
|
445
|
+
viewBox: "0 0 16 16",
|
|
446
|
+
fill: "currentColor"
|
|
447
|
+
}
|
|
448
|
+
}, {
|
|
449
|
+
type: "element",
|
|
450
|
+
tagName: "span",
|
|
451
|
+
properties: { className: ["ox-github-repo"] },
|
|
452
|
+
children: [{
|
|
453
|
+
type: "text",
|
|
454
|
+
value: repoData.full_name
|
|
455
|
+
}]
|
|
456
|
+
}]
|
|
457
|
+
},
|
|
458
|
+
...repoData.description ? [{
|
|
459
|
+
type: "element",
|
|
460
|
+
tagName: "p",
|
|
461
|
+
properties: { className: ["ox-github-description"] },
|
|
462
|
+
children: [{
|
|
463
|
+
type: "text",
|
|
464
|
+
value: repoData.description
|
|
465
|
+
}]
|
|
466
|
+
}] : [],
|
|
467
|
+
{
|
|
468
|
+
type: "element",
|
|
469
|
+
tagName: "div",
|
|
470
|
+
properties: { className: ["ox-github-stats"] },
|
|
471
|
+
children: createStatsChildren(repoData)
|
|
472
|
+
}
|
|
473
|
+
]
|
|
474
|
+
};
|
|
475
|
+
}
|
|
476
|
+
//#endregion
|
|
477
|
+
//#region src/plugins/github/source-card.ts
|
|
478
|
+
function normalizeSourceLines(content) {
|
|
479
|
+
const lines = content.replace(/\r\n?/g, "\n").split("\n");
|
|
480
|
+
if (lines.length > 1 && lines.at(-1) === "") lines.pop();
|
|
481
|
+
return lines.length > 0 ? lines : [""];
|
|
482
|
+
}
|
|
483
|
+
function createGitHubSourceCard(source, lines, options) {
|
|
484
|
+
const allLines = normalizeSourceLines(source.content);
|
|
485
|
+
const start = Math.min(lines?.start ?? 1, allLines.length);
|
|
486
|
+
const end = lines ? Math.min(lines.end, allLines.length) : Math.min(allLines.length, options.maxSourceLines);
|
|
487
|
+
const selectedLines = allLines.slice(start - 1, end);
|
|
488
|
+
const lineRange = {
|
|
489
|
+
start,
|
|
490
|
+
end
|
|
491
|
+
};
|
|
492
|
+
const loc = selectedLines.length;
|
|
493
|
+
const rangeLabel = formatLineRange(lineRange);
|
|
494
|
+
const locLabel = !lines && end < allLines.length ? `${rangeLabel} of ${allLines.length} LOC` : `${rangeLabel} - ${loc} LOC`;
|
|
495
|
+
const languageClass = source.language ? [`language-${source.language}`] : [];
|
|
496
|
+
return {
|
|
497
|
+
type: "element",
|
|
498
|
+
tagName: "figure",
|
|
499
|
+
properties: {
|
|
500
|
+
className: ["ox-github-code"],
|
|
501
|
+
"data-loc": String(loc),
|
|
502
|
+
"data-source": source.permalink
|
|
503
|
+
},
|
|
504
|
+
children: [{
|
|
505
|
+
type: "element",
|
|
506
|
+
tagName: "figcaption",
|
|
507
|
+
properties: { className: ["ox-github-code-header"] },
|
|
508
|
+
children: [{
|
|
509
|
+
type: "element",
|
|
510
|
+
tagName: "a",
|
|
511
|
+
properties: {
|
|
512
|
+
className: ["ox-github-code-title"],
|
|
513
|
+
href: source.permalink,
|
|
514
|
+
target: "_blank",
|
|
515
|
+
rel: "noopener noreferrer"
|
|
516
|
+
},
|
|
517
|
+
children: [{
|
|
518
|
+
type: "text",
|
|
519
|
+
value: `${source.repo}/${source.path}`
|
|
520
|
+
}]
|
|
521
|
+
}, {
|
|
522
|
+
type: "element",
|
|
523
|
+
tagName: "span",
|
|
524
|
+
properties: { className: ["ox-github-code-loc"] },
|
|
525
|
+
children: [{
|
|
526
|
+
type: "text",
|
|
527
|
+
value: locLabel
|
|
528
|
+
}]
|
|
529
|
+
}]
|
|
530
|
+
}, {
|
|
531
|
+
type: "element",
|
|
532
|
+
tagName: "pre",
|
|
533
|
+
properties: {
|
|
534
|
+
className: ["ox-github-code-block", ...languageClass],
|
|
535
|
+
...source.language ? { "data-language": source.language } : {}
|
|
536
|
+
},
|
|
537
|
+
children: [{
|
|
538
|
+
type: "element",
|
|
539
|
+
tagName: "code",
|
|
540
|
+
properties: { className: languageClass },
|
|
541
|
+
children: selectedLines.map((line, index) => {
|
|
542
|
+
const lineNumber = start + index;
|
|
543
|
+
return {
|
|
544
|
+
type: "element",
|
|
545
|
+
tagName: "span",
|
|
546
|
+
properties: {
|
|
547
|
+
className: ["line", "ox-github-code-line"],
|
|
548
|
+
"data-line": String(lineNumber)
|
|
549
|
+
},
|
|
550
|
+
children: [{
|
|
551
|
+
type: "element",
|
|
552
|
+
tagName: "span",
|
|
553
|
+
properties: { className: ["ox-github-code-line-number"] },
|
|
554
|
+
children: [{
|
|
555
|
+
type: "text",
|
|
556
|
+
value: String(lineNumber)
|
|
557
|
+
}]
|
|
558
|
+
}, {
|
|
559
|
+
type: "element",
|
|
560
|
+
tagName: "span",
|
|
561
|
+
properties: { className: ["ox-github-code-line-content"] },
|
|
562
|
+
children: [{
|
|
563
|
+
type: "text",
|
|
564
|
+
value: line || " "
|
|
565
|
+
}]
|
|
566
|
+
}]
|
|
567
|
+
};
|
|
568
|
+
})
|
|
569
|
+
}]
|
|
570
|
+
}]
|
|
571
|
+
};
|
|
572
|
+
}
|
|
573
|
+
//#endregion
|
|
574
|
+
//#region src/plugins/github/transform.ts
|
|
575
|
+
/**
|
|
576
|
+
* Rehype plugin to transform GitHub components.
|
|
577
|
+
*/
|
|
578
|
+
function rehypeGitHub(repoDataMap, sourceDataMap, options) {
|
|
579
|
+
return (tree) => {
|
|
580
|
+
const visit = (node) => {
|
|
581
|
+
if ("children" in node) for (let i = 0; i < node.children.length; i++) {
|
|
582
|
+
const child = node.children[i];
|
|
583
|
+
if (child.type !== "element") continue;
|
|
584
|
+
if (child.tagName.toLowerCase() !== "github") {
|
|
585
|
+
visit(child);
|
|
586
|
+
continue;
|
|
587
|
+
}
|
|
588
|
+
const attrs = attributesFromElement(child);
|
|
589
|
+
const source = sourceRefFromAttributes(attrs);
|
|
590
|
+
if (source) {
|
|
591
|
+
const sourceData = sourceDataMap.get(sourceKey(source));
|
|
592
|
+
node.children[i] = sourceData ? createGitHubSourceCard(sourceData, source.lines, options) : createFallbackCard(source.permalink);
|
|
593
|
+
continue;
|
|
594
|
+
}
|
|
595
|
+
const repo = attrs.repo;
|
|
596
|
+
if (repo) {
|
|
597
|
+
const repoData = repoDataMap.get(repo);
|
|
598
|
+
node.children[i] = repoData ? createGitHubCard(repoData) : createFallbackCard(repo);
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
};
|
|
602
|
+
visit(tree);
|
|
603
|
+
};
|
|
604
|
+
}
|
|
605
|
+
/**
|
|
606
|
+
* Transform GitHub components in HTML.
|
|
607
|
+
*/
|
|
608
|
+
async function transformGitHub(html, repoDataMap, options) {
|
|
609
|
+
const mergedOptions = {
|
|
610
|
+
...defaultOptions,
|
|
611
|
+
...options
|
|
612
|
+
};
|
|
613
|
+
let dataMap = repoDataMap;
|
|
614
|
+
if (!dataMap) dataMap = await prefetchGitHubRepos(await collectGitHubRepos(html), mergedOptions);
|
|
615
|
+
const sourceDataMap = await prefetchGitHubSources(await collectGitHubSources(html), mergedOptions);
|
|
616
|
+
const result = await unified().use(rehypeParse, { fragment: true }).use(rehypeGitHub, dataMap, sourceDataMap, mergedOptions).use(rehypeStringify).process(html);
|
|
617
|
+
return String(result);
|
|
618
|
+
}
|
|
619
|
+
//#endregion
|
|
620
|
+
export { fetchRepoData as a, createGitHubPermalink as c, isSafeGitHubRepo as d, fetchGitHubSource as i, parseGitHubLineRange as l, collectGitHubRepos as n, prefetchGitHubRepos as o, collectGitHubSources as r, prefetchGitHubSources as s, transformGitHub as t, parseGitHubPermalink as u };
|
|
621
|
+
|
|
622
|
+
//# sourceMappingURL=github.mjs.map
|