@haklex/rich-renderer-linkcard 0.0.80 → 0.0.82

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.
@@ -0,0 +1,1331 @@
1
+ import { Globe, Star } from "lucide-react";
2
+ import { createContext, use, useCallback, useEffect, useMemo, useRef, useState } from "react";
3
+ import { vars } from "@haklex/rich-style-token";
4
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
5
+ import { useInView } from "react-intersection-observer";
6
+ //#region src/plugins/academic/arxiv.tsx
7
+ var arxivPlugin = {
8
+ name: "arxiv",
9
+ displayName: "arXiv Paper",
10
+ priority: 80,
11
+ typeClass: "academic",
12
+ matchUrl(url) {
13
+ if (url.hostname !== "arxiv.org") return null;
14
+ const match = url.pathname.match(/\/(abs|pdf)\/(\d{4}\.\d+(?:v\d+)?)/i);
15
+ if (!match) return null;
16
+ return {
17
+ id: match[2].toLowerCase(),
18
+ fullUrl: url.toString()
19
+ };
20
+ },
21
+ isValidId(id) {
22
+ return /^\d{4}\.\d+(?:v\d+)?$/.test(id);
23
+ },
24
+ async fetch(id) {
25
+ const text = await (await fetch(`https://export.arxiv.org/api/query?id_list=${id}`)).text();
26
+ const entry = new DOMParser().parseFromString(text, "application/xml").getElementsByTagName("entry")[0];
27
+ const title = entry.getElementsByTagName("title")[0].textContent;
28
+ const authors = entry.getElementsByTagName("author");
29
+ const authorNames = Array.from(authors).map((author) => author.getElementsByTagName("name")[0].textContent);
30
+ return {
31
+ title: /* @__PURE__ */ jsxs("span", {
32
+ style: {
33
+ display: "flex",
34
+ alignItems: "center",
35
+ gap: "8px"
36
+ },
37
+ children: [/* @__PURE__ */ jsx("span", {
38
+ style: { flex: 1 },
39
+ children: title
40
+ }), /* @__PURE__ */ jsx("span", {
41
+ style: { flexShrink: 0 },
42
+ children: /* @__PURE__ */ jsx("span", {
43
+ style: {
44
+ display: "inline-flex",
45
+ alignItems: "center",
46
+ gap: "4px",
47
+ fontSize: vars.typography.fontSizeMd,
48
+ color: "#fb923c"
49
+ },
50
+ children: /* @__PURE__ */ jsx("span", {
51
+ style: {
52
+ fontFamily: "sans-serif",
53
+ fontWeight: 500
54
+ },
55
+ children: id
56
+ })
57
+ })
58
+ })]
59
+ }),
60
+ desc: authorNames.length > 1 ? `${authorNames[0]} et al.` : authorNames[0]
61
+ };
62
+ }
63
+ };
64
+ //#endregion
65
+ //#region src/utils.ts
66
+ function toCamelCase(str) {
67
+ return str.replaceAll(/_([a-z])/g, (_, c) => c.toUpperCase());
68
+ }
69
+ function camelcaseKeys(obj) {
70
+ if (Array.isArray(obj)) return obj.map(camelcaseKeys);
71
+ if (obj !== null && typeof obj === "object") return Object.fromEntries(Object.entries(obj).map(([k, v]) => [toCamelCase(k), camelcaseKeys(v)]));
72
+ return obj;
73
+ }
74
+ async function fetchJsonWithContext(url, context, provider, init) {
75
+ const providerAdapter = provider && context?.adapters ? context.adapters[provider] : void 0;
76
+ if (providerAdapter) return providerAdapter.request(url, init);
77
+ if (context?.fetchJson) return context.fetchJson(url, init);
78
+ const response = await fetch(url, init);
79
+ if (!response.ok) throw new Error(`Request failed: ${response.status}`);
80
+ return response.json();
81
+ }
82
+ async function fetchGitHubApi(url, context) {
83
+ return fetchJsonWithContext(`https://api.github.com${url.replace("https://api.github.com", "")}`, context, "github");
84
+ }
85
+ var LanguageToColorMap = {
86
+ "typescript": "#2b7489",
87
+ "javascript": "#f1e05a",
88
+ "html": "#e34c26",
89
+ "java": "#b07219",
90
+ "go": "#00add8",
91
+ "vue": "#2c3e50",
92
+ "css": "#563d7c",
93
+ "yaml": "#cb171e",
94
+ "json": "#292929",
95
+ "markdown": "#083fa1",
96
+ "csharp": "#178600",
97
+ "c#": "#178600",
98
+ "c": "#555555",
99
+ "cpp": "#f34b7d",
100
+ "c++": "#f34b7d",
101
+ "python": "#3572a5",
102
+ "lua": "#000080",
103
+ "vimscript": "#199f4b",
104
+ "shell": "#89e051",
105
+ "dockerfile": "#384d54",
106
+ "ruby": "#701516",
107
+ "php": "#4f5d95",
108
+ "lisp": "#3fb68b",
109
+ "kotlin": "#F18E33",
110
+ "rust": "#dea584",
111
+ "dart": "#00B4AB",
112
+ "swift": "#ffac45",
113
+ "objective-c": "#438eff",
114
+ "objective-c++": "#6866fb",
115
+ "r": "#198ce7",
116
+ "matlab": "#e16737",
117
+ "scala": "#c22d40",
118
+ "sql": "#e38c00",
119
+ "perl": "#0298c3"
120
+ };
121
+ var bangumiTypeMap = {
122
+ subject: "subjects",
123
+ character: "characters",
124
+ person: "persons"
125
+ };
126
+ var allowedBangumiTypes = Object.keys(bangumiTypeMap);
127
+ function hslToHex(h, s, l) {
128
+ s /= 100;
129
+ l /= 100;
130
+ const a = s * Math.min(l, 1 - l);
131
+ const f = (n) => {
132
+ const k = (n + h / 30) % 12;
133
+ const color = l - a * Math.max(Math.min(k - 3, 9 - k, 1), -1);
134
+ return Math.round(255 * color).toString(16).padStart(2, "0");
135
+ };
136
+ return `#${f(0)}${f(8)}${f(4)}`;
137
+ }
138
+ function generateColor(str, saturation = [30, 35], lightness = [60, 70]) {
139
+ let hash = 0;
140
+ for (let i = 0; i < str.length; i++) hash = str.charCodeAt(i) + ((hash << 5) - hash);
141
+ const sRange = saturation[1] - saturation[0] + 1;
142
+ const lRange = lightness[1] - lightness[0] + 1;
143
+ return hslToHex(Math.abs(hash) % 360, saturation[0] + Math.abs(hash >> 8) % sRange, lightness[0] + Math.abs(hash >> 16) % lRange);
144
+ }
145
+ function stripMarkdown(text) {
146
+ return text.replaceAll(/!\[[^\]]*\]\([^)]*\)/g, "").replaceAll(/\[([^\]]*)\]\([^)]*\)/g, "$1").replaceAll(/[#*>_`~]/g, "").replaceAll(/\n+/g, " ").trim();
147
+ }
148
+ //#endregion
149
+ //#region src/plugins/code/leetcode.tsx
150
+ function getDifficultyColor(difficulty) {
151
+ switch (difficulty) {
152
+ case "Easy": return "#00BFA5";
153
+ case "Medium": return "#FFA726";
154
+ case "Hard": return "#F44336";
155
+ default: return "#757575";
156
+ }
157
+ }
158
+ var leetcodePlugin = {
159
+ name: "leetcode",
160
+ displayName: "LeetCode",
161
+ priority: 65,
162
+ typeClass: "wide",
163
+ provider: "leetcode",
164
+ matchUrl(url) {
165
+ if (url.hostname !== "leetcode.cn" && url.hostname !== "leetcode.com") return null;
166
+ const parts = url.pathname.split("/").filter(Boolean);
167
+ if (parts[0] !== "problems" || !parts[1]) return null;
168
+ return {
169
+ id: parts[1],
170
+ fullUrl: url.toString()
171
+ };
172
+ },
173
+ isValidId(id) {
174
+ return typeof id === "string" && id.length > 0;
175
+ },
176
+ async fetch(id, _meta, context) {
177
+ const body = {
178
+ query: `query questionData($titleSlug: String!) {\n question(titleSlug: $titleSlug) {translatedTitle\n difficulty\n likes\n topicTags { translatedName\n }\n stats\n }\n}\n`,
179
+ variables: { titleSlug: id }
180
+ };
181
+ const questionTitleData = camelcaseKeys((await fetchJsonWithContext("https://leetcode.cn/graphql/", context, "leetcode", {
182
+ method: "POST",
183
+ headers: { "Content-Type": "application/json" },
184
+ body: JSON.stringify(body)
185
+ })).data.question);
186
+ const stats = JSON.parse(questionTitleData.stats);
187
+ return {
188
+ title: /* @__PURE__ */ jsxs("span", {
189
+ style: {
190
+ display: "flex",
191
+ alignItems: "center",
192
+ gap: "8px"
193
+ },
194
+ children: [/* @__PURE__ */ jsx("span", {
195
+ style: { flex: 1 },
196
+ children: questionTitleData.translatedTitle
197
+ }), /* @__PURE__ */ jsx("span", {
198
+ style: { flexShrink: 0 },
199
+ children: questionTitleData.likes > 0 && /* @__PURE__ */ jsxs("span", {
200
+ style: {
201
+ display: "inline-flex",
202
+ alignItems: "center",
203
+ gap: "4px",
204
+ fontSize: vars.typography.fontSizeMd,
205
+ color: "#fb923c"
206
+ },
207
+ children: ["👍", /* @__PURE__ */ jsx("span", {
208
+ style: {
209
+ fontFamily: "sans-serif",
210
+ fontWeight: 500
211
+ },
212
+ children: questionTitleData.likes
213
+ })]
214
+ })
215
+ })]
216
+ }),
217
+ desc: /* @__PURE__ */ jsxs(Fragment, { children: [
218
+ /* @__PURE__ */ jsx("span", {
219
+ style: {
220
+ marginRight: "16px",
221
+ fontWeight: "bold",
222
+ color: getDifficultyColor(questionTitleData.difficulty)
223
+ },
224
+ children: questionTitleData.difficulty
225
+ }),
226
+ /* @__PURE__ */ jsx("span", {
227
+ style: { overflow: "hidden" },
228
+ children: questionTitleData.topicTags.map((tag) => tag.translatedName).join(" / ")
229
+ }),
230
+ /* @__PURE__ */ jsxs("span", {
231
+ style: {
232
+ float: "right",
233
+ overflow: "hidden"
234
+ },
235
+ children: ["AR: ", stats.acRate]
236
+ })
237
+ ] }),
238
+ image: "https://upload.wikimedia.org/wikipedia/commons/1/19/LeetCode_logo_black.png",
239
+ color: getDifficultyColor(questionTitleData.difficulty)
240
+ };
241
+ }
242
+ };
243
+ //#endregion
244
+ //#region src/plugins/github/commit.tsx
245
+ var githubCommitPlugin = {
246
+ name: "gh-commit",
247
+ displayName: "GitHub Commit",
248
+ priority: 95,
249
+ typeClass: "github",
250
+ provider: "github",
251
+ matchUrl(url) {
252
+ if (url.hostname !== "github.com") return null;
253
+ const parts = url.pathname.split("/").filter(Boolean);
254
+ if (parts.length < 4 || parts[2] !== "commit") return null;
255
+ const [owner, repo, , commitId] = parts;
256
+ return {
257
+ id: `${owner}/${repo}/commit/${commitId}`,
258
+ fullUrl: url.toString()
259
+ };
260
+ },
261
+ isValidId(id) {
262
+ const parts = id.split("/");
263
+ return parts.length === 4 && parts.every((p) => p.length > 0) && parts[2] === "commit";
264
+ },
265
+ async fetch(id, _meta, context) {
266
+ const [owner, repo, , commitId] = id.split("/");
267
+ const data = camelcaseKeys(await fetchGitHubApi(`https://api.github.com/repos/${owner}/${repo}/commits/${commitId}`, context));
268
+ return {
269
+ title: /* @__PURE__ */ jsx("span", {
270
+ style: { fontWeight: "normal" },
271
+ children: data.commit.message.replace(/Signed-off-by:.+/s, "").trim()
272
+ }),
273
+ desc: /* @__PURE__ */ jsxs("span", {
274
+ style: {
275
+ display: "flex",
276
+ flexWrap: "wrap",
277
+ alignItems: "center",
278
+ gap: "4px 12px",
279
+ fontFamily: vars.typography.fontMono
280
+ },
281
+ children: [
282
+ /* @__PURE__ */ jsxs("span", {
283
+ style: {
284
+ display: "inline-flex",
285
+ alignItems: "center",
286
+ gap: "8px"
287
+ },
288
+ children: [/* @__PURE__ */ jsxs("span", {
289
+ style: { color: "#238636" },
290
+ children: ["+", data.stats.additions]
291
+ }), /* @__PURE__ */ jsxs("span", {
292
+ style: { color: "#f85149" },
293
+ children: ["-", data.stats.deletions]
294
+ })]
295
+ }),
296
+ /* @__PURE__ */ jsx("span", {
297
+ style: { fontSize: vars.typography.fontSizeMd },
298
+ children: data.sha.slice(0, 7)
299
+ }),
300
+ /* @__PURE__ */ jsxs("span", {
301
+ style: {
302
+ fontSize: vars.typography.fontSizeMd,
303
+ opacity: .8
304
+ },
305
+ children: [
306
+ owner,
307
+ "/",
308
+ repo
309
+ ]
310
+ })
311
+ ]
312
+ }),
313
+ image: data.author?.avatarUrl
314
+ };
315
+ }
316
+ };
317
+ //#endregion
318
+ //#region src/plugins/github/discussion.tsx
319
+ var githubDiscussionPlugin = {
320
+ name: "gh-discussion",
321
+ displayName: "GitHub Discussion",
322
+ priority: 95,
323
+ typeClass: "github",
324
+ provider: "github",
325
+ matchUrl(url) {
326
+ if (url.hostname !== "github.com") return null;
327
+ if (!url.pathname.includes("/discussions/")) return null;
328
+ const parts = url.pathname.split("/").filter(Boolean);
329
+ if (parts.length < 4 || parts[2] !== "discussions") return null;
330
+ const discussionNumber = parts[3];
331
+ if (!/^\d+$/.test(discussionNumber)) return null;
332
+ const [owner, repo] = parts;
333
+ return {
334
+ id: `${owner}/${repo}/${discussionNumber}`,
335
+ fullUrl: url.toString()
336
+ };
337
+ },
338
+ isValidId(id) {
339
+ const parts = id.split("/");
340
+ return parts.length === 3 && parts.every((p) => p.length > 0) && /^\d+$/.test(parts[2]);
341
+ },
342
+ async fetch(id, _meta, context) {
343
+ const [owner, repo, discussionNumber] = id.split("/");
344
+ const data = camelcaseKeys(await fetchGitHubApi(`https://api.github.com/repos/${owner}/${repo}/discussions/${discussionNumber}`, context));
345
+ const categoryName = data.category?.name || "Discussion";
346
+ return {
347
+ title: `Discussion: ${data.title}`,
348
+ desc: /* @__PURE__ */ jsxs("span", {
349
+ style: {
350
+ display: "flex",
351
+ flexWrap: "wrap",
352
+ alignItems: "center",
353
+ gap: "4px 12px",
354
+ fontFamily: vars.typography.fontMono
355
+ },
356
+ children: [/* @__PURE__ */ jsx("span", {
357
+ style: {
358
+ borderRadius: "4px",
359
+ backgroundColor: "rgba(128,128,128,0.15)",
360
+ padding: "2px 6px",
361
+ fontSize: vars.typography.fontSizeXs
362
+ },
363
+ children: categoryName
364
+ }), /* @__PURE__ */ jsxs("span", {
365
+ style: {
366
+ fontSize: vars.typography.fontSizeMd,
367
+ opacity: .8
368
+ },
369
+ children: [
370
+ "#",
371
+ discussionNumber,
372
+ " · ",
373
+ owner,
374
+ "/",
375
+ repo
376
+ ]
377
+ })]
378
+ }),
379
+ image: data.user?.avatarUrl
380
+ };
381
+ }
382
+ };
383
+ //#endregion
384
+ //#region src/plugins/github/issue.tsx
385
+ var stateColors = {
386
+ open: "#238636",
387
+ closed: "#f85149"
388
+ };
389
+ var stateTextMap$1 = {
390
+ open: "Open",
391
+ closed: "Closed"
392
+ };
393
+ var githubIssuePlugin = {
394
+ name: "gh-issue",
395
+ displayName: "GitHub Issue",
396
+ priority: 95,
397
+ typeClass: "github",
398
+ provider: "github",
399
+ matchUrl(url) {
400
+ if (url.hostname !== "github.com") return null;
401
+ if (!url.pathname.includes("/issues/")) return null;
402
+ const parts = url.pathname.split("/").filter(Boolean);
403
+ if (parts.length < 4 || parts[2] !== "issues") return null;
404
+ const issueNumber = parts[3];
405
+ if (!/^\d+$/.test(issueNumber)) return null;
406
+ const [owner, repo] = parts;
407
+ return {
408
+ id: `${owner}/${repo}/${issueNumber}`,
409
+ fullUrl: url.toString()
410
+ };
411
+ },
412
+ isValidId(id) {
413
+ const parts = id.split("/");
414
+ return parts.length === 3 && parts.every((p) => p.length > 0) && /^\d+$/.test(parts[2]);
415
+ },
416
+ async fetch(id, _meta, context) {
417
+ const [owner, repo, issueNumber] = id.split("/");
418
+ const data = camelcaseKeys(await fetchGitHubApi(`https://api.github.com/repos/${owner}/${repo}/issues/${issueNumber}`, context));
419
+ const color = stateColors[data.state] || "#6b7280";
420
+ const stateText = stateTextMap$1[data.state] || data.state;
421
+ return {
422
+ title: `Issue: ${data.title}`,
423
+ color,
424
+ desc: /* @__PURE__ */ jsxs("span", {
425
+ style: {
426
+ display: "flex",
427
+ flexWrap: "wrap",
428
+ alignItems: "center",
429
+ gap: "4px 12px",
430
+ fontFamily: vars.typography.fontMono
431
+ },
432
+ children: [/* @__PURE__ */ jsx("span", {
433
+ style: { color },
434
+ children: stateText
435
+ }), /* @__PURE__ */ jsxs("span", {
436
+ style: {
437
+ fontSize: vars.typography.fontSizeMd,
438
+ opacity: .8
439
+ },
440
+ children: [
441
+ "#",
442
+ issueNumber,
443
+ " · ",
444
+ owner,
445
+ "/",
446
+ repo
447
+ ]
448
+ })]
449
+ }),
450
+ image: data.user?.avatarUrl
451
+ };
452
+ }
453
+ };
454
+ //#endregion
455
+ //#region src/plugins/github/pr.tsx
456
+ var getPrState = (data) => {
457
+ if (data.merged) return "merged";
458
+ return data.state;
459
+ };
460
+ var stateColorMap = {
461
+ open: "#238636",
462
+ merged: "#8957e5",
463
+ closed: "#f85149"
464
+ };
465
+ var stateTextMap = {
466
+ open: "Open",
467
+ merged: "Merged",
468
+ closed: "Closed"
469
+ };
470
+ var githubPrPlugin = {
471
+ name: "gh-pr",
472
+ displayName: "GitHub Pull Request",
473
+ priority: 95,
474
+ typeClass: "github",
475
+ provider: "github",
476
+ matchUrl(url) {
477
+ if (url.hostname !== "github.com") return null;
478
+ if (!url.pathname.includes("/pull/")) return null;
479
+ const parts = url.pathname.split("/").filter(Boolean);
480
+ if (parts.length < 4 || parts[2] !== "pull") return null;
481
+ const [owner, repo, , prNumber] = parts;
482
+ return {
483
+ id: `${owner}/${repo}/${prNumber}`,
484
+ fullUrl: url.toString()
485
+ };
486
+ },
487
+ isValidId(id) {
488
+ const parts = id.split("/");
489
+ return parts.length === 3 && parts.every((p) => p.length > 0);
490
+ },
491
+ async fetch(id, _meta, context) {
492
+ const [owner, repo, prNumber] = id.split("/");
493
+ const data = camelcaseKeys(await fetchGitHubApi(`https://api.github.com/repos/${owner}/${repo}/pulls/${prNumber}`, context));
494
+ const state = getPrState(data);
495
+ const color = stateColorMap[state];
496
+ const stateText = stateTextMap[state];
497
+ return {
498
+ title: `PR: ${data.title}`,
499
+ color,
500
+ desc: /* @__PURE__ */ jsxs("span", {
501
+ style: {
502
+ display: "flex",
503
+ flexWrap: "wrap",
504
+ alignItems: "center",
505
+ gap: "4px 12px",
506
+ fontFamily: vars.typography.fontMono
507
+ },
508
+ children: [
509
+ /* @__PURE__ */ jsx("span", {
510
+ style: { color },
511
+ children: stateText
512
+ }),
513
+ /* @__PURE__ */ jsxs("span", {
514
+ style: {
515
+ display: "inline-flex",
516
+ alignItems: "center",
517
+ gap: "8px",
518
+ whiteSpace: "nowrap"
519
+ },
520
+ children: [/* @__PURE__ */ jsxs("span", {
521
+ style: { color: "#238636" },
522
+ children: ["+", data.additions]
523
+ }), /* @__PURE__ */ jsxs("span", {
524
+ style: { color: "#f85149" },
525
+ children: ["-", data.deletions]
526
+ })]
527
+ }),
528
+ /* @__PURE__ */ jsxs("span", {
529
+ style: {
530
+ fontSize: vars.typography.fontSizeMd,
531
+ opacity: .8
532
+ },
533
+ children: [
534
+ owner,
535
+ "/",
536
+ repo
537
+ ]
538
+ })
539
+ ]
540
+ }),
541
+ image: data.user.avatarUrl
542
+ };
543
+ }
544
+ };
545
+ //#endregion
546
+ //#region src/plugins/github/repo.tsx
547
+ function formatStargazers(n) {
548
+ return n.toLocaleString("en-US");
549
+ }
550
+ var githubRepoPlugin = {
551
+ name: "gh-repo",
552
+ displayName: "GitHub Repository",
553
+ priority: 100,
554
+ typeClass: "github",
555
+ provider: "github",
556
+ matchUrl(url) {
557
+ if (url.hostname !== "github.com") return null;
558
+ const parts = url.pathname.split("/").filter(Boolean);
559
+ if (parts.length !== 2) return null;
560
+ const [owner, repo] = parts;
561
+ if (!owner || !repo) return null;
562
+ return {
563
+ id: `${owner}/${repo}`,
564
+ fullUrl: url.toString()
565
+ };
566
+ },
567
+ isValidId(id) {
568
+ const parts = id.split("/");
569
+ return parts.length === 2 && parts[0].length > 0 && parts[1].length > 0;
570
+ },
571
+ async fetch(id, _meta, context) {
572
+ const [owner, repo] = id.split("/");
573
+ const data = camelcaseKeys(await fetchGitHubApi(`https://api.github.com/repos/${owner}/${repo}`, context));
574
+ return {
575
+ title: /* @__PURE__ */ jsxs("span", {
576
+ style: {
577
+ display: "flex",
578
+ alignItems: "center",
579
+ gap: "8px"
580
+ },
581
+ children: [/* @__PURE__ */ jsx("span", {
582
+ style: { flex: 1 },
583
+ children: data.name
584
+ }), /* @__PURE__ */ jsx("span", {
585
+ style: { flexShrink: 0 },
586
+ children: data.stargazersCount > 0 && /* @__PURE__ */ jsxs("span", {
587
+ style: {
588
+ display: "inline-flex",
589
+ alignItems: "center",
590
+ gap: "4px",
591
+ fontSize: vars.typography.fontSizeMd,
592
+ color: "#fb923c"
593
+ },
594
+ children: [/* @__PURE__ */ jsx(Star, {
595
+ "aria-hidden": true,
596
+ size: 14,
597
+ strokeWidth: 2
598
+ }), /* @__PURE__ */ jsx("span", {
599
+ style: {
600
+ fontFamily: "sans-serif",
601
+ fontWeight: 500
602
+ },
603
+ children: formatStargazers(data.stargazersCount)
604
+ })]
605
+ })
606
+ })]
607
+ }),
608
+ desc: data.description,
609
+ image: data.owner.avatarUrl,
610
+ color: LanguageToColorMap[data.language?.toLowerCase()]
611
+ };
612
+ }
613
+ };
614
+ //#endregion
615
+ //#region src/plugins/media/bangumi.tsx
616
+ var bangumiPlugin = {
617
+ name: "bangumi",
618
+ displayName: "Bangumi",
619
+ priority: 70,
620
+ typeClass: "media",
621
+ provider: "bangumi",
622
+ matchUrl(url) {
623
+ if (url.hostname !== "bgm.tv" && url.hostname !== "bangumi.tv") return null;
624
+ const parts = url.pathname.split("/").filter(Boolean);
625
+ if (parts.length < 2) return null;
626
+ const [type, realId] = parts;
627
+ if (!allowedBangumiTypes.includes(type)) return null;
628
+ return {
629
+ id: `${type}/${realId}`,
630
+ fullUrl: url.toString(),
631
+ meta: { type }
632
+ };
633
+ },
634
+ isValidId(id) {
635
+ const [type, realId] = id.split("/");
636
+ return allowedBangumiTypes.includes(type) && realId?.length > 0;
637
+ },
638
+ async fetch(id, _meta, context) {
639
+ const [type, realId] = id.split("/");
640
+ const json = await fetchJsonWithContext(`https://api.bgm.tv/v0/${bangumiTypeMap[type]}/${realId}`, context, "bangumi");
641
+ let title = "";
642
+ let originalTitle = "";
643
+ if (type === "subject") if (json.name_cn && json.name_cn !== json.name && json.name_cn !== "") {
644
+ title = json.name_cn;
645
+ originalTitle = json.name;
646
+ } else {
647
+ title = json.name;
648
+ originalTitle = json.name;
649
+ }
650
+ else if (type === "character" || type === "person") {
651
+ const { infobox } = json;
652
+ infobox.forEach((item) => {
653
+ if (item.key === "简体中文名") title = typeof item.value === "string" ? item.value : item.value[0].v;
654
+ else if (item.key === "别名") {
655
+ item.value.forEach((alias) => {
656
+ originalTitle += `${alias.v} / `;
657
+ });
658
+ originalTitle = originalTitle.slice(0, -3);
659
+ }
660
+ });
661
+ }
662
+ const starStyle = {
663
+ display: "inline-flex",
664
+ flexShrink: 0,
665
+ alignItems: "center",
666
+ gap: "4px",
667
+ alignSelf: "center",
668
+ fontSize: vars.typography.fontSizeXs,
669
+ color: "#fb923c"
670
+ };
671
+ return {
672
+ title: /* @__PURE__ */ jsxs("span", {
673
+ style: {
674
+ display: "flex",
675
+ flexWrap: "wrap",
676
+ alignItems: "flex-end",
677
+ gap: "8px"
678
+ },
679
+ children: [
680
+ /* @__PURE__ */ jsx("span", { children: title }),
681
+ title !== originalTitle && /* @__PURE__ */ jsxs("span", {
682
+ style: {
683
+ fontSize: vars.typography.fontSizeMd,
684
+ opacity: .7
685
+ },
686
+ children: [
687
+ "(",
688
+ originalTitle,
689
+ ")"
690
+ ]
691
+ }),
692
+ type === "subject" && /* @__PURE__ */ jsxs("span", {
693
+ style: {
694
+ display: "inline-flex",
695
+ flexShrink: 0,
696
+ alignItems: "center",
697
+ gap: "12px",
698
+ alignSelf: "center"
699
+ },
700
+ children: [/* @__PURE__ */ jsxs("span", {
701
+ style: starStyle,
702
+ children: ["★", /* @__PURE__ */ jsx("span", {
703
+ style: {
704
+ fontFamily: "sans-serif",
705
+ fontWeight: 500
706
+ },
707
+ children: json.rating.score > 0 && json.rating.score.toFixed(1)
708
+ })]
709
+ }), /* @__PURE__ */ jsxs("span", {
710
+ style: starStyle,
711
+ children: ["☆", /* @__PURE__ */ jsx("span", {
712
+ style: {
713
+ fontFamily: "sans-serif",
714
+ fontWeight: 500
715
+ },
716
+ children: json.collection && json.collection.on_hold + json.collection.dropped + json.collection.wish + json.collection.collect + json.collection.doing
717
+ })]
718
+ })]
719
+ }),
720
+ (type === "character" || type === "person") && /* @__PURE__ */ jsxs("span", {
721
+ style: starStyle,
722
+ children: ["☆", /* @__PURE__ */ jsx("span", {
723
+ style: {
724
+ fontFamily: "sans-serif",
725
+ fontWeight: 500
726
+ },
727
+ children: json.stat.collects > 0 && json.stat.collects
728
+ })]
729
+ })
730
+ ]
731
+ }),
732
+ desc: /* @__PURE__ */ jsx("span", {
733
+ style: {
734
+ overflow: "visible",
735
+ whiteSpace: "pre-wrap"
736
+ },
737
+ children: json.summary
738
+ }),
739
+ image: json.images.grid,
740
+ color: generateColor(title),
741
+ classNames: {
742
+ image: "link-card__image--poster",
743
+ cardRoot: "link-card--poster"
744
+ }
745
+ };
746
+ }
747
+ };
748
+ //#endregion
749
+ //#region src/plugins/media/netease-music.tsx
750
+ var neteaseMusicPlugin = {
751
+ name: "netease-music-song",
752
+ displayName: "Netease Music Song",
753
+ priority: 60,
754
+ typeClass: "wide",
755
+ provider: "netease-music",
756
+ matchUrl(url) {
757
+ if (url.hostname !== "music.163.com") return null;
758
+ if (!url.pathname.includes("/song") && !url.hash.includes("/song")) return null;
759
+ const urlString = url.toString().replaceAll("/#/", "/");
760
+ const id = new URL(urlString).searchParams.get("id");
761
+ if (!id) return null;
762
+ return {
763
+ id,
764
+ fullUrl: url.toString()
765
+ };
766
+ },
767
+ isValidId(id) {
768
+ return id.length > 0;
769
+ },
770
+ async fetch(id, _meta, context) {
771
+ const songInfo = (await fetchJsonWithContext(`https://music.163.com/song/${id}`, context, "netease-music")).songs[0];
772
+ const albumInfo = songInfo.al;
773
+ const singerInfo = songInfo.ar;
774
+ return {
775
+ title: /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("span", { children: songInfo.name }), songInfo.tns && /* @__PURE__ */ jsx("span", {
776
+ style: {
777
+ marginLeft: "8px",
778
+ fontSize: vars.typography.fontSizeMd,
779
+ color: "#a1a1aa"
780
+ },
781
+ children: songInfo.tns[0]
782
+ })] }),
783
+ desc: /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsxs("span", {
784
+ style: { display: "block" },
785
+ children: [/* @__PURE__ */ jsx("span", {
786
+ style: { fontWeight: "bold" },
787
+ children: "歌手:"
788
+ }), /* @__PURE__ */ jsx("span", { children: singerInfo.map((p) => p.name).join(" / ") })]
789
+ }), /* @__PURE__ */ jsxs("span", {
790
+ style: { display: "block" },
791
+ children: [/* @__PURE__ */ jsx("span", {
792
+ style: { fontWeight: "bold" },
793
+ children: "专辑:"
794
+ }), /* @__PURE__ */ jsx("span", { children: albumInfo.name })]
795
+ })] }),
796
+ image: albumInfo.picUrl,
797
+ color: "#e72d2c"
798
+ };
799
+ }
800
+ };
801
+ //#endregion
802
+ //#region src/plugins/media/qq-music.tsx
803
+ var qqMusicPlugin = {
804
+ name: "qq-music-song",
805
+ displayName: "QQ Music Song",
806
+ priority: 60,
807
+ typeClass: "wide",
808
+ provider: "qq-music",
809
+ matchUrl(url) {
810
+ if (url.hostname !== "y.qq.com") return null;
811
+ if (!url.pathname.includes("/songDetail/")) return null;
812
+ const parts = url.pathname.split("/");
813
+ const songDetailIndex = parts.indexOf("songDetail");
814
+ if (songDetailIndex === -1 || !parts[songDetailIndex + 1]) return null;
815
+ return {
816
+ id: parts[songDetailIndex + 1],
817
+ fullUrl: url.toString()
818
+ };
819
+ },
820
+ isValidId(id) {
821
+ return typeof id === "string" && id.length > 0;
822
+ },
823
+ async fetch(id, _meta, context) {
824
+ const songInfo = (await fetchJsonWithContext(`https://y.qq.com/song/${id}`, context, "qq-music")).data[0];
825
+ const albumId = songInfo.album.mid;
826
+ return {
827
+ title: /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("span", { children: songInfo.title }), songInfo.subtitle && /* @__PURE__ */ jsx("span", {
828
+ style: {
829
+ marginLeft: "8px",
830
+ fontSize: vars.typography.fontSizeMd,
831
+ color: "#a1a1aa"
832
+ },
833
+ children: songInfo.subtitle
834
+ })] }),
835
+ desc: /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsxs("span", {
836
+ style: { display: "block" },
837
+ children: [/* @__PURE__ */ jsx("span", {
838
+ style: { fontWeight: "bold" },
839
+ children: "歌手:"
840
+ }), /* @__PURE__ */ jsx("span", { children: songInfo.singer.map((p) => p.name).join(" / ") })]
841
+ }), /* @__PURE__ */ jsxs("span", {
842
+ style: { display: "block" },
843
+ children: [/* @__PURE__ */ jsx("span", {
844
+ style: { fontWeight: "bold" },
845
+ children: "专辑:"
846
+ }), /* @__PURE__ */ jsx("span", { children: songInfo.album.name })]
847
+ })] }),
848
+ image: `https://y.gtimg.cn/music/photo_new/T002R300x300M000${albumId}.jpg?max_age=2592000`,
849
+ color: "#31c27c"
850
+ };
851
+ }
852
+ };
853
+ //#endregion
854
+ //#region src/plugins/media/tmdb.tsx
855
+ var TMDB_LANGUAGE_BY_LOCALE = {
856
+ zh: "zh-CN",
857
+ en: "en-US",
858
+ ja: "ja-JP"
859
+ };
860
+ var getCurrentLocale = () => {
861
+ if (typeof document !== "undefined") {
862
+ const lang = document.documentElement?.lang?.trim();
863
+ if (lang) return lang;
864
+ }
865
+ if (typeof window !== "undefined") {
866
+ const firstSegment = window.location.pathname.split("/").find((s) => s.length > 0);
867
+ if (firstSegment) return firstSegment;
868
+ }
869
+ return "en";
870
+ };
871
+ var tmdbPlugin = {
872
+ name: "tmdb",
873
+ displayName: "The Movie Database",
874
+ priority: 70,
875
+ typeClass: "media",
876
+ provider: "tmdb",
877
+ matchUrl(url) {
878
+ if (!url.hostname.includes("themoviedb.org")) return null;
879
+ const parts = url.pathname.split("/").filter(Boolean);
880
+ if (parts.length < 2) return null;
881
+ const [type, realId] = parts;
882
+ if (!["tv", "movie"].includes(type) || !realId) return null;
883
+ return {
884
+ id: `${type}/${realId}`,
885
+ fullUrl: url.toString(),
886
+ meta: { type }
887
+ };
888
+ },
889
+ isValidId(id) {
890
+ const [type, realId] = id.split("/");
891
+ return ["tv", "movie"].includes(type) && realId?.length > 0;
892
+ },
893
+ async fetch(id, _meta, context) {
894
+ const [type, realId] = id.split("/");
895
+ const json = await fetchJsonWithContext(`https://api.themoviedb.org/3/${type}/${realId}?language=${TMDB_LANGUAGE_BY_LOCALE[getCurrentLocale()] || (typeof navigator !== "undefined" ? navigator.language : "en-US") || "en-US"}`, context, "tmdb");
896
+ const title = type === "tv" ? json.name : json.title;
897
+ const originalTitle = type === "tv" ? json.original_name : json.original_title;
898
+ return {
899
+ title: /* @__PURE__ */ jsxs("span", {
900
+ style: {
901
+ display: "flex",
902
+ flexWrap: "wrap",
903
+ alignItems: "flex-end",
904
+ gap: "8px"
905
+ },
906
+ children: [
907
+ /* @__PURE__ */ jsx("span", { children: title }),
908
+ title !== originalTitle && /* @__PURE__ */ jsxs("span", {
909
+ style: {
910
+ fontSize: vars.typography.fontSizeMd,
911
+ opacity: .7
912
+ },
913
+ children: [
914
+ "(",
915
+ originalTitle,
916
+ ")"
917
+ ]
918
+ }),
919
+ /* @__PURE__ */ jsxs("span", {
920
+ style: {
921
+ display: "inline-flex",
922
+ flexShrink: 0,
923
+ alignItems: "center",
924
+ gap: "4px",
925
+ alignSelf: "center",
926
+ fontSize: vars.typography.fontSizeXs,
927
+ color: "#fb923c"
928
+ },
929
+ children: ["★", /* @__PURE__ */ jsx("span", {
930
+ style: {
931
+ fontFamily: "sans-serif",
932
+ fontWeight: 500
933
+ },
934
+ children: json.vote_average > 0 && json.vote_average.toFixed(1)
935
+ })]
936
+ })
937
+ ]
938
+ }),
939
+ desc: /* @__PURE__ */ jsx("span", {
940
+ style: {
941
+ overflow: "visible",
942
+ whiteSpace: "pre-wrap"
943
+ },
944
+ children: json.overview
945
+ }),
946
+ image: `https://image.tmdb.org/t/p/w500${json.poster_path}`,
947
+ color: generateColor(title || originalTitle || id),
948
+ classNames: {
949
+ image: "link-card__image--poster",
950
+ cardRoot: "link-card--poster"
951
+ }
952
+ };
953
+ }
954
+ };
955
+ //#endregion
956
+ //#region src/plugins/self/mx-space.tsx
957
+ function createMxSpacePlugin(config) {
958
+ const webHost = new URL(config.webUrl).hostname;
959
+ return {
960
+ name: "self",
961
+ displayName: "MxSpace Article",
962
+ priority: 10,
963
+ provider: "mx-space",
964
+ matchUrl(url) {
965
+ if (webHost !== url.hostname) return null;
966
+ if (!url.pathname.startsWith("/posts/") && !url.pathname.startsWith("/notes/")) return null;
967
+ return {
968
+ id: url.pathname.slice(1),
969
+ fullUrl: url.toString()
970
+ };
971
+ },
972
+ isValidId(id) {
973
+ const [type, ...rest] = id.split("/");
974
+ if (type !== "posts" && type !== "notes") return false;
975
+ if (type === "posts") return rest.length === 2;
976
+ return rest.length === 1;
977
+ },
978
+ async fetch(id, _meta, context) {
979
+ const [type, ...rest] = id.split("/");
980
+ let data = {
981
+ title: "",
982
+ text: ""
983
+ };
984
+ if (type === "posts") {
985
+ const [cate, slug] = rest;
986
+ data = await fetchJsonWithContext(`posts/${cate}/${slug}`, context, "mx-space");
987
+ } else if (type === "notes") {
988
+ const [nid] = rest;
989
+ const response = await fetchJsonWithContext(`notes/${nid}`, context, "mx-space");
990
+ data = response.data || response;
991
+ }
992
+ const coverImage = data.cover || data.meta?.cover;
993
+ const spotlightColor = generateColor(data.title);
994
+ return {
995
+ title: data.title,
996
+ desc: data.summary || `${stripMarkdown(data.text).slice(0, 50)}...`,
997
+ image: coverImage || data.images?.[0]?.src,
998
+ color: spotlightColor
999
+ };
1000
+ }
1001
+ };
1002
+ }
1003
+ //#endregion
1004
+ //#region src/plugins/index.ts
1005
+ var plugins = [
1006
+ githubRepoPlugin,
1007
+ githubCommitPlugin,
1008
+ githubPrPlugin,
1009
+ githubIssuePlugin,
1010
+ githubDiscussionPlugin,
1011
+ arxivPlugin,
1012
+ tmdbPlugin,
1013
+ bangumiPlugin,
1014
+ qqMusicPlugin,
1015
+ neteaseMusicPlugin,
1016
+ leetcodePlugin
1017
+ ].sort((a, b) => (b.priority ?? 0) - (a.priority ?? 0));
1018
+ function getPluginByName(name) {
1019
+ return plugins.find((p) => p.name === name);
1020
+ }
1021
+ var pluginMap = new Map(plugins.map((p) => [p.name, p]));
1022
+ //#endregion
1023
+ //#region src/hooks/useUrlMatcher.ts
1024
+ function matchUrl(url, pluginRegistry = plugins) {
1025
+ try {
1026
+ const parsed = new URL(url);
1027
+ for (const plugin of pluginRegistry) {
1028
+ const match = plugin.matchUrl(parsed);
1029
+ if (match) return {
1030
+ plugin,
1031
+ match
1032
+ };
1033
+ }
1034
+ } catch {}
1035
+ return null;
1036
+ }
1037
+ function useUrlMatcher(url, pluginRegistry = plugins) {
1038
+ return useMemo(() => {
1039
+ if (!url) return null;
1040
+ return matchUrl(url, pluginRegistry);
1041
+ }, [url, pluginRegistry]);
1042
+ }
1043
+ //#endregion
1044
+ //#region src/styles.css.ts
1045
+ var semanticClassNames = {
1046
+ card: "link-card",
1047
+ cardShortDesc: "link-card--short-desc",
1048
+ cardSkeleton: "link-card--skeleton",
1049
+ cardError: "link-card--error",
1050
+ cardPoster: "link-card--poster",
1051
+ cardWide: "link-card--wide",
1052
+ cardMedia: "link-card--media",
1053
+ cardGithub: "link-card--github",
1054
+ cardAcademic: "link-card--academic",
1055
+ bg: "link-card__bg",
1056
+ image: "link-card__image",
1057
+ imagePoster: "link-card__image--poster",
1058
+ icon: "link-card__icon",
1059
+ content: "link-card__content",
1060
+ title: "link-card__title",
1061
+ titleText: "link-card__title-text",
1062
+ desc: "link-card__desc",
1063
+ desc2: "link-card__desc-2",
1064
+ editWrapper: "rich-link-card-edit-wrapper",
1065
+ editPanel: "rich-link-card-edit-panel",
1066
+ editUrlRow: "rich-link-card-edit-url-row",
1067
+ editLinkIcon: "rich-link-card-edit-link-icon",
1068
+ editInput: "rich-link-card-edit-input"
1069
+ };
1070
+ var card = "_13weebv0";
1071
+ var image = "_13weebv3";
1072
+ var icon = "_13weebv5";
1073
+ var content = "_13weebv6";
1074
+ var title = "_13weebv7";
1075
+ var titleText = "_13weebv8";
1076
+ var desc = "_13weebv9";
1077
+ var desc2 = "_13weebva";
1078
+ var cardSkeleton = "_13weebvg";
1079
+ var editWrapper = "_13weebvj";
1080
+ var editPanel = "_13weebvk";
1081
+ var editUrlRow = "_13weebvl";
1082
+ var editLinkIcon = "_13weebvm";
1083
+ var editInput = "_13weebvn";
1084
+ var typeCardModifier = {
1085
+ media: "_13weebvd",
1086
+ github: "_13weebve",
1087
+ academic: "_13weebvf",
1088
+ wide: "_13weebvc"
1089
+ };
1090
+ var semanticTypeClassNames = {
1091
+ media: "link-card--media",
1092
+ github: "link-card--github",
1093
+ academic: "link-card--academic",
1094
+ wide: "link-card--wide"
1095
+ };
1096
+ var semanticClassToStyle = {
1097
+ "link-card--short-desc": "_13weebv1",
1098
+ "link-card--skeleton": "_13weebvg",
1099
+ "link-card--error": "_13weebvi",
1100
+ "link-card--poster": "_13weebvb",
1101
+ "link-card--wide": "_13weebvc",
1102
+ "link-card--media": "_13weebvd",
1103
+ "link-card--github": "_13weebve",
1104
+ "link-card--academic": "_13weebvf",
1105
+ "link-card__image--poster": "_13weebv4"
1106
+ };
1107
+ //#endregion
1108
+ //#region src/FetchContext.tsx
1109
+ var ctx = createContext(void 0);
1110
+ var LinkCardFetchProvider = ctx.Provider;
1111
+ function useLinkCardFetchContext() {
1112
+ return use(ctx);
1113
+ }
1114
+ //#endregion
1115
+ //#region src/hooks/useCardFetcher.ts
1116
+ function useCardFetcher(options) {
1117
+ const { source, plugin, id, fallbackUrl, enabled = true, context } = options;
1118
+ const [loading, setLoading] = useState(true);
1119
+ const [isError, setIsError] = useState(false);
1120
+ const [fullUrl] = useState(fallbackUrl || "javascript:;");
1121
+ const [cardInfo, setCardInfo] = useState();
1122
+ const isValid = useMemo(() => {
1123
+ if (!enabled || !plugin) return false;
1124
+ return plugin.isValidId(id);
1125
+ }, [
1126
+ plugin,
1127
+ enabled,
1128
+ id
1129
+ ]);
1130
+ const fetchInfo = useCallback(async () => {
1131
+ if (!plugin || !isValid) return;
1132
+ setLoading(true);
1133
+ setIsError(false);
1134
+ try {
1135
+ setCardInfo(await plugin.fetch(id, void 0, context));
1136
+ } catch (err) {
1137
+ console.error(`[LinkCard] Error fetching ${source || plugin.name} data:`, err);
1138
+ setIsError(true);
1139
+ } finally {
1140
+ setLoading(false);
1141
+ }
1142
+ }, [
1143
+ context,
1144
+ plugin,
1145
+ isValid,
1146
+ id,
1147
+ source
1148
+ ]);
1149
+ const { ref } = useInView({
1150
+ triggerOnce: true,
1151
+ onChange(inView) {
1152
+ if (!inView || !enabled) return;
1153
+ fetchInfo();
1154
+ }
1155
+ });
1156
+ return {
1157
+ loading,
1158
+ isError,
1159
+ cardInfo,
1160
+ fullUrl,
1161
+ isValid,
1162
+ ref
1163
+ };
1164
+ }
1165
+ //#endregion
1166
+ //#region src/LinkCardSkeleton.tsx
1167
+ function LinkCardSkeleton({ source, className }) {
1168
+ const typeClass = (source ? pluginMap.get(source) : void 0)?.typeClass;
1169
+ const typeStyleClass = typeClass ? typeCardModifier[typeClass] : "";
1170
+ const typeSemanticClass = typeClass ? semanticTypeClassNames[typeClass] : "";
1171
+ return /* @__PURE__ */ jsxs("span", {
1172
+ "data-hide-print": true,
1173
+ "data-source": source || void 0,
1174
+ className: [
1175
+ card,
1176
+ semanticClassNames.card,
1177
+ cardSkeleton,
1178
+ semanticClassNames.cardSkeleton,
1179
+ typeStyleClass,
1180
+ typeSemanticClass,
1181
+ className
1182
+ ].filter(Boolean).join(" "),
1183
+ children: [/* @__PURE__ */ jsx("span", { className: `${image} ${semanticClassNames.image}` }), /* @__PURE__ */ jsxs("span", {
1184
+ className: `${content} ${semanticClassNames.content}`,
1185
+ children: [
1186
+ /* @__PURE__ */ jsx("span", {
1187
+ className: `${title} ${semanticClassNames.title}`,
1188
+ children: /* @__PURE__ */ jsx("span", { className: `${titleText} ${semanticClassNames.titleText}` })
1189
+ }),
1190
+ /* @__PURE__ */ jsx("span", { className: `${desc} ${semanticClassNames.desc}` }),
1191
+ /* @__PURE__ */ jsx("span", { className: `${desc} ${semanticClassNames.desc} ${desc2} ${semanticClassNames.desc2}` })
1192
+ ]
1193
+ })]
1194
+ });
1195
+ }
1196
+ //#endregion
1197
+ //#region src/LinkCardRenderer.tsx
1198
+ function FallbackIcon({ favicon }) {
1199
+ const [faviconFailed, setFaviconFailed] = useState(false);
1200
+ return /* @__PURE__ */ jsx("span", {
1201
+ className: `${icon} ${semanticClassNames.icon}`,
1202
+ children: favicon && !faviconFailed ? /* @__PURE__ */ jsx("img", {
1203
+ alt: "",
1204
+ src: favicon,
1205
+ onError: () => setFaviconFailed(true)
1206
+ }) : /* @__PURE__ */ jsx(Globe, { "aria-hidden": "true" })
1207
+ });
1208
+ }
1209
+ function mapSemanticClasses(classNames) {
1210
+ if (!classNames) return "";
1211
+ return classNames.split(/\s+/).filter(Boolean).map((cls) => semanticClassToStyle[cls] ? `${semanticClassToStyle[cls]} ${cls}` : cls).join(" ");
1212
+ }
1213
+ var LinkCardRenderer = (props) => {
1214
+ const { url, title: title$1, description, favicon, image: image$1, source: explicitSource, id: explicitId, className, plugins: extraPlugins, fetchContext: fetchContextProp } = props;
1215
+ const contextValue = useLinkCardFetchContext();
1216
+ const fetchContext = fetchContextProp ?? contextValue;
1217
+ const mergedPlugins = useMemo(() => {
1218
+ if (!extraPlugins || extraPlugins.length === 0) return plugins;
1219
+ const map = new Map(plugins.map((p) => [p.name, p]));
1220
+ for (const plugin of extraPlugins) map.set(plugin.name, plugin);
1221
+ return [...map.values()].sort((a, b) => (b.priority ?? 0) - (a.priority ?? 0));
1222
+ }, [extraPlugins]);
1223
+ const pluginMap = useMemo(() => new Map(mergedPlugins.map((plugin) => [plugin.name, plugin])), [mergedPlugins]);
1224
+ const urlMatch = useUrlMatcher(!explicitSource || !explicitId ? url : void 0, mergedPlugins);
1225
+ const source = explicitSource || urlMatch?.plugin.name;
1226
+ const id = explicitId || urlMatch?.match.id;
1227
+ const matchedFullUrl = urlMatch?.match.fullUrl;
1228
+ const useDynamicFetch = !!source && !!id;
1229
+ const selectedPlugin = source ? pluginMap.get(source) : void 0;
1230
+ const { loading, isError, cardInfo, fullUrl, isValid, ref } = useCardFetcher({
1231
+ source,
1232
+ plugin: selectedPlugin,
1233
+ id: id || "",
1234
+ fallbackUrl: matchedFullUrl || url,
1235
+ enabled: useDynamicFetch,
1236
+ context: fetchContext
1237
+ });
1238
+ const typeClass = selectedPlugin?.typeClass;
1239
+ const typeStyleClass = typeClass ? typeCardModifier[typeClass] : "";
1240
+ const typeSemanticClass = typeClass ? semanticTypeClassNames[typeClass] : "";
1241
+ const isErrorState = useDynamicFetch && isError;
1242
+ const finalTitle = cardInfo?.title || title$1 || (isErrorState ? "" : url);
1243
+ const finalDesc = cardInfo?.desc || description;
1244
+ const finalImage = cardInfo?.image || image$1;
1245
+ const finalColor = cardInfo?.color;
1246
+ const classNames = cardInfo?.classNames || {};
1247
+ const mappedCardRootClass = mapSemanticClasses(classNames.cardRoot);
1248
+ const mappedImageClass = mapSemanticClasses(classNames.image);
1249
+ const [shortDesc, setShortDesc] = useState(false);
1250
+ const descRef = useRef(null);
1251
+ useEffect(() => {
1252
+ const el = descRef.current;
1253
+ if (!el || !finalDesc) {
1254
+ setShortDesc(false);
1255
+ return;
1256
+ }
1257
+ const style = getComputedStyle(el);
1258
+ const maxTwoLines = (Number.parseFloat(style.lineHeight) || 1.5 * 14) * 2;
1259
+ setShortDesc(el.scrollHeight <= maxTwoLines + 1);
1260
+ }, [finalDesc, finalTitle]);
1261
+ if (useDynamicFetch && !isValid) return null;
1262
+ if (useDynamicFetch && loading) return /* @__PURE__ */ jsx("a", {
1263
+ "data-hide-print": true,
1264
+ href: fullUrl,
1265
+ ref,
1266
+ rel: "noopener noreferrer",
1267
+ target: "_blank",
1268
+ children: /* @__PURE__ */ jsx(LinkCardSkeleton, { source })
1269
+ });
1270
+ const hasImage = !!finalImage;
1271
+ const showImagePlaceholder = isErrorState && !hasImage;
1272
+ const shouldCenterContent = !finalDesc || shortDesc;
1273
+ return /* @__PURE__ */ jsxs("a", {
1274
+ "data-hide-print": true,
1275
+ "data-source": source || void 0,
1276
+ href: useDynamicFetch ? fullUrl : url,
1277
+ ref: useDynamicFetch ? ref : void 0,
1278
+ rel: "noopener noreferrer",
1279
+ target: "_blank",
1280
+ className: [
1281
+ card,
1282
+ semanticClassNames.card,
1283
+ typeStyleClass,
1284
+ typeSemanticClass,
1285
+ shouldCenterContent && "_13weebv1",
1286
+ shouldCenterContent && semanticClassNames.cardShortDesc,
1287
+ useDynamicFetch && (loading || isError) && "_13weebvg",
1288
+ useDynamicFetch && (loading || isError) && semanticClassNames.cardSkeleton,
1289
+ useDynamicFetch && isError && "_13weebvi",
1290
+ useDynamicFetch && isError && semanticClassNames.cardError,
1291
+ "not-prose",
1292
+ className,
1293
+ mappedCardRootClass
1294
+ ].filter(Boolean).join(" "),
1295
+ style: { borderColor: finalColor ? `${finalColor}30` : void 0 },
1296
+ children: [
1297
+ finalColor && /* @__PURE__ */ jsx("div", {
1298
+ className: `_13weebv2 ${semanticClassNames.bg}`,
1299
+ style: {
1300
+ backgroundColor: finalColor,
1301
+ opacity: .04
1302
+ }
1303
+ }),
1304
+ hasImage || showImagePlaceholder ? /* @__PURE__ */ jsx("span", {
1305
+ "data-image": finalImage || "",
1306
+ className: [
1307
+ image,
1308
+ semanticClassNames.image,
1309
+ mappedImageClass
1310
+ ].filter(Boolean).join(" "),
1311
+ style: { backgroundImage: finalImage ? `url(${finalImage})` : void 0 }
1312
+ }) : /* @__PURE__ */ jsx(FallbackIcon, { favicon }),
1313
+ /* @__PURE__ */ jsxs("span", {
1314
+ className: `${content} ${semanticClassNames.content}`,
1315
+ children: [/* @__PURE__ */ jsx("span", {
1316
+ className: `${title} ${semanticClassNames.title}`,
1317
+ children: /* @__PURE__ */ jsx("span", {
1318
+ className: `${titleText} ${semanticClassNames.titleText}`,
1319
+ children: finalTitle
1320
+ })
1321
+ }), finalDesc && /* @__PURE__ */ jsx("span", {
1322
+ className: `_13weebv9 ${semanticClassNames.desc}`,
1323
+ ref: descRef,
1324
+ children: finalDesc
1325
+ })]
1326
+ })
1327
+ ]
1328
+ });
1329
+ };
1330
+ //#endregion
1331
+ export { fetchJsonWithContext as A, githubIssuePlugin as C, LanguageToColorMap as D, leetcodePlugin as E, arxivPlugin as M, camelcaseKeys as O, githubPrPlugin as S, githubCommitPlugin as T, tmdbPlugin as _, editInput as a, bangumiPlugin as b, editUrlRow as c, matchUrl as d, useUrlMatcher as f, createMxSpacePlugin as g, plugins as h, useLinkCardFetchContext as i, generateColor as j, fetchGitHubApi as k, editWrapper as l, pluginMap as m, LinkCardSkeleton as n, editLinkIcon as o, getPluginByName as p, LinkCardFetchProvider as r, editPanel as s, LinkCardRenderer as t, semanticClassNames as u, qqMusicPlugin as v, githubDiscussionPlugin as w, githubRepoPlugin as x, neteaseMusicPlugin as y };