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