@haklex/rich-ext-embed 0.0.41 → 0.0.42

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,664 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
3
+ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
4
+ import { DecoratorNode, $getNodeByKey } from "lexical";
5
+ import { createContext, use, lazy, useState, useMemo, useEffect, Suspense, Component, createElement, useRef, useCallback } from "react";
6
+ import { jsx, jsxs } from "react/jsx-runtime";
7
+ import { useColorScheme } from "@haklex/rich-editor";
8
+ import { Github, ExternalLink, Trash2 } from "lucide-react";
9
+ import { useLexicalComposerContext } from "@lexical/react/LexicalComposerContext";
10
+ var semanticClassNames$1 = { wrapper: "rich-embed-link-wrapper", embed: "rich-embed", badge: "rich-embed__badge", dot: "rich-embed__dot", divider: "rich-embed__divider", input: "rich-embed__input", actions: "rich-embed__actions", actionButton: "rich-embed__action-btn", actionButtonDanger: "rich-embed__action-btn--danger" };
11
+ var semanticEmbedModifierClass = { generic: "rich-embed--generic", tweet: "rich-embed--tweet", youtube: "rich-embed--youtube", codesandbox: "rich-embed--codesandbox", bilibili: "rich-embed--bilibili", "github-file": "rich-embed--github-file", "github-gist": "rich-embed--github-gist", thinking: "rich-embed--thinking" };
12
+ var wrapper = "_1qucygc1";
13
+ var embed = "_1qucygc2";
14
+ var embedType = { generic: "_1qucygc3", tweet: "_1qucygc4", youtube: "_1qucygc5", codesandbox: "_1qucygc6", bilibili: "_1qucygc7", "github-file": "_1qucygc8", "github-gist": "_1qucygc9", thinking: "_1qucygca" };
15
+ var badge = "_1qucygcb";
16
+ var dot = "_1qucygcc";
17
+ var divider = "_1qucygcd";
18
+ var input = "_1qucygce";
19
+ var actions = "_1qucygcf";
20
+ var actionButton = "_1qucygcg";
21
+ var actionButtonDanger = "_1qucygch";
22
+ const EmbedRendererCtx = createContext({});
23
+ const EmbedRendererProvider = EmbedRendererCtx.Provider;
24
+ function useEmbedRenderers() {
25
+ return use(EmbedRendererCtx);
26
+ }
27
+ var semanticClassNames = { ratioContainer: "embed-static-ratio-container", ratioInner: "embed-static-ratio-inner", iframe: "embed-static-iframe", gist: "embed-static-gist", gistIframe: "embed-static-gist__iframe", sourceLink: "embed-static-source-link", githubFile: "embed-static-github-file", githubFileCode: "embed-static-github-file__code", githubFileCodeLong: "embed-static-github-file__code--long", githubFileLine: "embed-static-github-file__line", githubFileLineNum: "embed-static-github-file__line-num", shiki: "embed-static-shiki", fallback: "embed-static-fallback", fallbackBadge: "embed-static-fallback__badge", fallbackDot: "embed-static-fallback__dot", fallbackLink: "embed-static-fallback__link", tweet: "embed-static-tweet", loading: "embed-static-loading" };
28
+ var semanticFallbackModifierClass = { generic: "embed-static-fallback--generic", tweet: "embed-static-fallback--tweet", youtube: "embed-static-fallback--youtube", codesandbox: "embed-static-fallback--codesandbox", bilibili: "embed-static-fallback--bilibili", "github-file": "embed-static-fallback--github-file", "github-gist": "embed-static-fallback--github-gist", thinking: "embed-static-fallback--thinking" };
29
+ var ratioContainer = "_1xclnej1";
30
+ var ratioInner = "_1xclnej2";
31
+ var iframe = "_1xclnej3";
32
+ var gist = "_1xclnej4";
33
+ var gistIframe = "_1xclnej5";
34
+ var sourceLink = "_1xclnej6";
35
+ var githubFile = "_1xclnej7";
36
+ var githubFileCode = "_1xclnej8";
37
+ var githubFileCodeLong = "_1xclnej9";
38
+ var githubFileLine = "_1xclneja";
39
+ var githubFileLineNum = "_1xclnejb";
40
+ var shiki = "_1xclnejc";
41
+ var fallback = "_1xclnejd";
42
+ var fallbackType = { generic: "_1xclneje", tweet: "_1xclnejf", youtube: "_1xclnejg", codesandbox: "_1xclnejh", bilibili: "_1xclneji", "github-file": "_1xclnejj", "github-gist": "_1xclnejk", thinking: "_1xclnejl" };
43
+ var fallbackBadge = "_1xclnejm";
44
+ var fallbackDot = "_1xclnejn";
45
+ var fallbackLink = "_1xclnejo";
46
+ var tweet = "_1xclnejp";
47
+ var loading = "_1xclnejq";
48
+ const extToLang = {
49
+ ".js": "javascript",
50
+ ".ts": "typescript",
51
+ ".mjs": "javascript",
52
+ ".cjs": "javascript",
53
+ ".mts": "typescript",
54
+ ".cts": "typescript",
55
+ ".jsx": "jsx",
56
+ ".tsx": "tsx",
57
+ ".md": "markdown",
58
+ ".css": "css",
59
+ ".scss": "scss",
60
+ ".html": "html",
61
+ ".json": "json",
62
+ ".yml": "yaml",
63
+ ".yaml": "yaml",
64
+ ".toml": "toml",
65
+ ".xml": "xml",
66
+ ".sh": "bash",
67
+ ".bash": "bash",
68
+ ".zsh": "bash",
69
+ ".go": "go",
70
+ ".py": "python",
71
+ ".rb": "ruby",
72
+ ".java": "java",
73
+ ".c": "c",
74
+ ".cpp": "cpp",
75
+ ".cs": "csharp",
76
+ ".rs": "rust",
77
+ ".swift": "swift",
78
+ ".kt": "kotlin",
79
+ ".lua": "lua",
80
+ ".sql": "sql",
81
+ ".graphql": "graphql",
82
+ ".scala": "scala",
83
+ ".dart": "dart",
84
+ ".vue": "vue",
85
+ ".h": "c",
86
+ ".hpp": "cpp",
87
+ ".m": "objectivec",
88
+ ".ex": "elixir",
89
+ ".r": "r"
90
+ };
91
+ function getLangFromPath(path) {
92
+ const dot2 = path.lastIndexOf(".");
93
+ if (dot2 === -1) return "text";
94
+ return extToLang[path.slice(dot2).toLowerCase()] || "text";
95
+ }
96
+ const shikiPromise = import("shiki/bundle/web").catch(() => null);
97
+ const typeLabels$1 = {
98
+ tweet: "X / Twitter",
99
+ youtube: "YouTube",
100
+ codesandbox: "CodeSandbox",
101
+ bilibili: "Bilibili",
102
+ "github-file": "GitHub File",
103
+ "github-gist": "GitHub Gist",
104
+ thinking: "Thinking"
105
+ };
106
+ const LazyTweet = lazy(
107
+ () => import("react-tweet").then((mod) => ({ default: mod.IsolatedTweet }))
108
+ );
109
+ class EmbedErrorBoundary extends Component {
110
+ constructor() {
111
+ super(...arguments);
112
+ __publicField(this, "state", { hasError: false });
113
+ }
114
+ static getDerivedStateFromError() {
115
+ return { hasError: true };
116
+ }
117
+ render() {
118
+ return this.state.hasError ? this.props.fallback : this.props.children;
119
+ }
120
+ }
121
+ function GitHubSvg() {
122
+ return /* @__PURE__ */ jsx(Github, { size: 16 });
123
+ }
124
+ function FixedRatioContainer({
125
+ children,
126
+ ratio = 58
127
+ }) {
128
+ return /* @__PURE__ */ jsx(
129
+ "div",
130
+ {
131
+ className: `${ratioContainer} ${semanticClassNames.ratioContainer}`,
132
+ children: /* @__PURE__ */ jsx(
133
+ "div",
134
+ {
135
+ className: `${ratioInner} ${semanticClassNames.ratioInner}`,
136
+ style: { paddingBottom: `${ratio}%` },
137
+ children
138
+ }
139
+ )
140
+ }
141
+ );
142
+ }
143
+ function FallbackLink({ url, type }) {
144
+ const label = type ? typeLabels$1[type] : "Embed";
145
+ const cssModifier = type || "generic";
146
+ const fallbackTypeClass = fallbackType[cssModifier];
147
+ const semanticModifierClass = semanticFallbackModifierClass[cssModifier];
148
+ return /* @__PURE__ */ jsxs(
149
+ "div",
150
+ {
151
+ className: `${fallback} ${fallbackTypeClass} ${semanticClassNames.fallback} ${semanticModifierClass}`.trim(),
152
+ children: [
153
+ /* @__PURE__ */ jsxs(
154
+ "span",
155
+ {
156
+ className: `${fallbackBadge} ${semanticClassNames.fallbackBadge}`,
157
+ children: [
158
+ /* @__PURE__ */ jsx(
159
+ "span",
160
+ {
161
+ className: `${fallbackDot} ${semanticClassNames.fallbackDot}`
162
+ }
163
+ ),
164
+ label
165
+ ]
166
+ }
167
+ ),
168
+ /* @__PURE__ */ jsx(
169
+ "a",
170
+ {
171
+ className: `${fallbackLink} ${semanticClassNames.fallbackLink}`,
172
+ href: url,
173
+ target: "_blank",
174
+ rel: "noreferrer",
175
+ children: url
176
+ }
177
+ )
178
+ ]
179
+ }
180
+ );
181
+ }
182
+ function TweetRenderer({ url }) {
183
+ const colorScheme = useColorScheme();
184
+ let parsedUrl = null;
185
+ try {
186
+ parsedUrl = new URL(url);
187
+ } catch {
188
+ return /* @__PURE__ */ jsx(FallbackLink, { url, type: "tweet" });
189
+ }
190
+ const id = parsedUrl.pathname.split("/").pop();
191
+ if (!id) return /* @__PURE__ */ jsx(FallbackLink, { url, type: "tweet" });
192
+ const fallback2 = /* @__PURE__ */ jsx(FallbackLink, { url, type: "tweet" });
193
+ return /* @__PURE__ */ jsx(EmbedErrorBoundary, { fallback: fallback2, children: /* @__PURE__ */ jsx("div", { className: `${tweet} ${semanticClassNames.tweet}`, children: /* @__PURE__ */ jsx(
194
+ Suspense,
195
+ {
196
+ fallback: /* @__PURE__ */ jsx(
197
+ "div",
198
+ {
199
+ className: `${loading} ${semanticClassNames.loading}`,
200
+ children: "Loading tweet..."
201
+ }
202
+ ),
203
+ children: /* @__PURE__ */ jsx(
204
+ LazyTweet,
205
+ {
206
+ id,
207
+ theme: colorScheme === "dark" ? "dark" : "light",
208
+ style: { width: "100%" }
209
+ }
210
+ )
211
+ }
212
+ ) }) });
213
+ }
214
+ function GithubFileEmbed({ url, href }) {
215
+ const colorScheme = useColorScheme();
216
+ const shikiTheme = colorScheme === "dark" ? "github-dark" : "github-light";
217
+ const split = url.pathname.split("/");
218
+ const [, owner, repo, , ...rest] = split;
219
+ const ref = rest[0];
220
+ const path = ref ? rest.slice(1).join("/") : rest.join("/");
221
+ const matchResult = url.hash.match(/L\d+/g);
222
+ let startLine = 0;
223
+ let endLine;
224
+ if (matchResult) {
225
+ if (matchResult.length === 1) {
226
+ startLine = Number.parseInt(matchResult[0].slice(1)) - 1;
227
+ endLine = startLine + 1;
228
+ } else {
229
+ startLine = Number.parseInt(matchResult[0].slice(1)) - 1;
230
+ endLine = Number.parseInt(matchResult[1].slice(1));
231
+ }
232
+ }
233
+ const [content, setContent] = useState(null);
234
+ const [highlightedHtml, setHighlightedHtml] = useState(null);
235
+ const [loading$1, setLoading] = useState(true);
236
+ const [error, setError] = useState(false);
237
+ const lang = useMemo(() => getLangFromPath(path), [path]);
238
+ useEffect(() => {
239
+ let cancelled = false;
240
+ setContent(null);
241
+ setHighlightedHtml(null);
242
+ setLoading(true);
243
+ setError(false);
244
+ const fetchPromise = fetch(
245
+ `https://cdn.jsdelivr.net/gh/${owner}/${repo}${ref ? `@${ref}` : ""}/${path}`
246
+ ).then((res) => {
247
+ if (!res.ok) throw new Error(`HTTP ${res.status}`);
248
+ return res.text();
249
+ });
250
+ Promise.all([fetchPromise, shikiPromise]).then(([text, shikiMod]) => {
251
+ if (cancelled) return;
252
+ setContent(text);
253
+ const lines2 = text.split("\n");
254
+ const end2 = endLine ?? lines2.length;
255
+ const displayCode = lines2.slice(startLine, end2).join("\n");
256
+ if (!shikiMod) {
257
+ setLoading(false);
258
+ return;
259
+ }
260
+ shikiMod.codeToHtml(displayCode, {
261
+ lang,
262
+ theme: shikiTheme
263
+ }).then((html) => {
264
+ if (!cancelled) {
265
+ setHighlightedHtml(html);
266
+ setLoading(false);
267
+ }
268
+ }).catch(() => {
269
+ if (!cancelled) setLoading(false);
270
+ });
271
+ }).catch(() => {
272
+ if (!cancelled) {
273
+ setError(true);
274
+ setLoading(false);
275
+ }
276
+ });
277
+ return () => {
278
+ cancelled = true;
279
+ };
280
+ }, [owner, repo, ref, path, lang, startLine, endLine, shikiTheme]);
281
+ if (loading$1) {
282
+ return /* @__PURE__ */ jsx("div", { className: `${loading} ${semanticClassNames.loading}`, children: "Loading GitHub File Preview..." });
283
+ }
284
+ if (error || content === null) {
285
+ return /* @__PURE__ */ jsx(FallbackLink, { url: href, type: "github-file" });
286
+ }
287
+ const lines = content.split("\n");
288
+ const end = endLine ?? lines.length;
289
+ const isLong = end - startLine > 20;
290
+ return /* @__PURE__ */ jsxs(
291
+ "div",
292
+ {
293
+ className: `${githubFile} ${semanticClassNames.githubFile}`,
294
+ children: [
295
+ /* @__PURE__ */ jsx(
296
+ "div",
297
+ {
298
+ className: `${githubFileCode} ${semanticClassNames.githubFileCode} ${isLong ? `${githubFileCodeLong} ${semanticClassNames.githubFileCodeLong}` : ""}`.trim(),
299
+ children: highlightedHtml ? /* @__PURE__ */ jsx(
300
+ "div",
301
+ {
302
+ className: `${shiki} ${semanticClassNames.shiki}`,
303
+ style: { "--start-line": startLine },
304
+ dangerouslySetInnerHTML: { __html: highlightedHtml }
305
+ }
306
+ ) : /* @__PURE__ */ jsx("pre", { children: /* @__PURE__ */ jsx("code", { children: lines.slice(startLine, end).map((line, i) => /* @__PURE__ */ jsxs(
307
+ "span",
308
+ {
309
+ className: `${githubFileLine} ${semanticClassNames.githubFileLine}`,
310
+ children: [
311
+ /* @__PURE__ */ jsx(
312
+ "span",
313
+ {
314
+ className: `${githubFileLineNum} ${semanticClassNames.githubFileLineNum}`,
315
+ children: startLine + i + 1
316
+ }
317
+ ),
318
+ line,
319
+ "\n"
320
+ ]
321
+ },
322
+ i
323
+ )) }) })
324
+ }
325
+ ),
326
+ /* @__PURE__ */ jsxs(
327
+ "a",
328
+ {
329
+ className: `${sourceLink} ${semanticClassNames.sourceLink}`,
330
+ href,
331
+ target: "_blank",
332
+ rel: "noreferrer",
333
+ children: [
334
+ /* @__PURE__ */ jsx(GitHubSvg, {}),
335
+ /* @__PURE__ */ jsx("span", { children: href })
336
+ ]
337
+ }
338
+ )
339
+ ]
340
+ }
341
+ );
342
+ }
343
+ function EmbedStaticRenderer({ type, url }) {
344
+ const customRenderers = useEmbedRenderers();
345
+ if (!url) return null;
346
+ if (type && customRenderers[type]) {
347
+ const Custom = customRenderers[type];
348
+ return /* @__PURE__ */ jsx(Custom, { url, type });
349
+ }
350
+ let parsedUrl = null;
351
+ try {
352
+ parsedUrl = new URL(url);
353
+ } catch {
354
+ return /* @__PURE__ */ jsx(FallbackLink, { url, type });
355
+ }
356
+ switch (type) {
357
+ case "tweet": {
358
+ return /* @__PURE__ */ jsx(TweetRenderer, { url });
359
+ }
360
+ case "youtube": {
361
+ const id = parsedUrl.searchParams.get("v");
362
+ if (!id) return /* @__PURE__ */ jsx(FallbackLink, { url, type });
363
+ return /* @__PURE__ */ jsx(FixedRatioContainer, { children: /* @__PURE__ */ jsx(
364
+ "iframe",
365
+ {
366
+ src: `https://www.youtube.com/embed/${id}`,
367
+ className: `${iframe} ${semanticClassNames.iframe}`,
368
+ allow: "accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture",
369
+ allowFullScreen: true,
370
+ title: "YouTube video player"
371
+ }
372
+ ) });
373
+ }
374
+ case "bilibili": {
375
+ const id = parsedUrl.pathname.split("/")[2];
376
+ if (!id) return /* @__PURE__ */ jsx(FallbackLink, { url, type });
377
+ return /* @__PURE__ */ jsx(FixedRatioContainer, { children: /* @__PURE__ */ jsx(
378
+ "iframe",
379
+ {
380
+ src: `//player.bilibili.com/player.html?bvid=${id}&autoplay=0`,
381
+ scrolling: "no",
382
+ className: `${iframe} ${semanticClassNames.iframe}`,
383
+ allowFullScreen: true
384
+ }
385
+ ) });
386
+ }
387
+ case "codesandbox": {
388
+ const path = parsedUrl.pathname.slice(2);
389
+ return /* @__PURE__ */ jsx(FixedRatioContainer, { children: /* @__PURE__ */ jsx(
390
+ "iframe",
391
+ {
392
+ className: `${iframe} ${semanticClassNames.iframe}`,
393
+ src: `https://codesandbox.io/embed/${path}?fontsize=14&hidenavigation=1&theme=dark${parsedUrl.search}`
394
+ }
395
+ ) });
396
+ }
397
+ case "github-gist": {
398
+ const [, owner, id] = parsedUrl.pathname.split("/");
399
+ if (!owner || !id) return /* @__PURE__ */ jsx(FallbackLink, { url, type });
400
+ return /* @__PURE__ */ jsxs("div", { className: `${gist} ${semanticClassNames.gist}`, children: [
401
+ /* @__PURE__ */ jsx(
402
+ "iframe",
403
+ {
404
+ src: `https://gist.github.com/${owner}/${id}.pibb`,
405
+ className: `${gistIframe} ${semanticClassNames.gistIframe}`
406
+ }
407
+ ),
408
+ /* @__PURE__ */ jsxs(
409
+ "a",
410
+ {
411
+ className: `${sourceLink} ${semanticClassNames.sourceLink}`,
412
+ href: url,
413
+ target: "_blank",
414
+ rel: "noreferrer",
415
+ children: [
416
+ /* @__PURE__ */ jsx(GitHubSvg, {}),
417
+ /* @__PURE__ */ jsx("span", { children: url })
418
+ ]
419
+ }
420
+ )
421
+ ] });
422
+ }
423
+ case "github-file": {
424
+ return /* @__PURE__ */ jsx(GithubFileEmbed, { url: parsedUrl, href: url });
425
+ }
426
+ default:
427
+ return /* @__PURE__ */ jsx(FallbackLink, { url, type });
428
+ }
429
+ }
430
+ class EmbedNode extends DecoratorNode {
431
+ constructor(url, source, key) {
432
+ super(key);
433
+ __publicField(this, "__url");
434
+ __publicField(this, "__source");
435
+ this.__url = url;
436
+ this.__source = source;
437
+ }
438
+ static getType() {
439
+ return "embed";
440
+ }
441
+ static clone(node) {
442
+ return new EmbedNode(node.__url, node.__source, node.__key);
443
+ }
444
+ createDOM(_config) {
445
+ const div = document.createElement("div");
446
+ div.className = `${wrapper} ${semanticClassNames$1.wrapper}`;
447
+ return div;
448
+ }
449
+ updateDOM() {
450
+ return false;
451
+ }
452
+ isInline() {
453
+ return false;
454
+ }
455
+ static importJSON(serializedNode) {
456
+ return $createEmbedNode(serializedNode.url, serializedNode.source);
457
+ }
458
+ exportJSON() {
459
+ return {
460
+ ...super.exportJSON(),
461
+ type: "embed",
462
+ url: this.__url,
463
+ source: this.__source,
464
+ version: 1
465
+ };
466
+ }
467
+ getUrl() {
468
+ return this.getLatest().__url;
469
+ }
470
+ setUrl(url) {
471
+ const writable = this.getWritable();
472
+ writable.__url = url;
473
+ }
474
+ getSource() {
475
+ return this.getLatest().__source;
476
+ }
477
+ setSource(source) {
478
+ const writable = this.getWritable();
479
+ writable.__source = source;
480
+ }
481
+ decorate(_editor, _config) {
482
+ return createElement(EmbedStaticRenderer, {
483
+ type: this.__source,
484
+ url: this.__url
485
+ });
486
+ }
487
+ }
488
+ function $createEmbedNode(url, source) {
489
+ return new EmbedNode(url, source);
490
+ }
491
+ function $isEmbedNode(node) {
492
+ return node instanceof EmbedNode;
493
+ }
494
+ const isTweetUrl = (url) => (url.hostname === "twitter.com" || url.hostname === "x.com") && url.pathname.startsWith("/");
495
+ const isYoutubeUrl = (url) => url.hostname === "www.youtube.com" && url.pathname.startsWith("/watch");
496
+ const isCodesandboxUrl = (url) => url.hostname === "codesandbox.io" && url.pathname.split("/").length === 3;
497
+ const isBilibiliVideoUrl = (url) => url.hostname.includes("bilibili.com") && url.pathname.startsWith("/video/BV");
498
+ const isGithubFilePreviewUrl = (url) => {
499
+ const [, , , type] = url.pathname.split("/");
500
+ return url.hostname === "github.com" && type === "blob";
501
+ };
502
+ const isGistUrl = (url) => url.hostname === "gist.github.com";
503
+ function createSelfThinkingMatcher(hostnames) {
504
+ return (url) => hostnames.includes(url.hostname) && url.pathname.startsWith("/thinking/");
505
+ }
506
+ function matchEmbedUrl(url, selfThinkingMatcher) {
507
+ if (isTweetUrl(url)) return "tweet";
508
+ if (isYoutubeUrl(url)) return "youtube";
509
+ if (isCodesandboxUrl(url)) return "codesandbox";
510
+ if (isBilibiliVideoUrl(url)) return "bilibili";
511
+ if (isGithubFilePreviewUrl(url)) return "github-file";
512
+ if (isGistUrl(url)) return "github-gist";
513
+ if (selfThinkingMatcher?.(url)) return "thinking";
514
+ return null;
515
+ }
516
+ const typeLabels = {
517
+ tweet: "X / Twitter",
518
+ youtube: "YouTube",
519
+ codesandbox: "CodeSandbox",
520
+ bilibili: "Bilibili",
521
+ "github-file": "GitHub File",
522
+ "github-gist": "GitHub Gist",
523
+ thinking: "Thinking"
524
+ };
525
+ function EmbedLinkRenderer({
526
+ type,
527
+ url,
528
+ nodeKey
529
+ }) {
530
+ const [editor] = useLexicalComposerContext();
531
+ const [editUrl, setEditUrl] = useState(url);
532
+ const inputRef = useRef(null);
533
+ useEffect(() => {
534
+ setEditUrl(url);
535
+ }, [url]);
536
+ useEffect(() => {
537
+ if (!url) {
538
+ inputRef.current?.focus();
539
+ }
540
+ }, [url]);
541
+ const commitUrl = useCallback(() => {
542
+ const trimmed = editUrl.trim();
543
+ if (!trimmed) return;
544
+ editor.update(() => {
545
+ const node = $getNodeByKey(nodeKey);
546
+ if (!node || !("setUrl" in node)) return;
547
+ const n = node;
548
+ const currentUrl = n.getUrl?.();
549
+ if (currentUrl !== trimmed) {
550
+ n.setUrl(trimmed);
551
+ }
552
+ if (n.setSource) {
553
+ try {
554
+ n.setSource(matchEmbedUrl(new URL(trimmed)));
555
+ } catch {
556
+ }
557
+ }
558
+ });
559
+ }, [editor, nodeKey, editUrl]);
560
+ const handleKeyDown = useCallback(
561
+ (e) => {
562
+ if (e.key === "Enter") {
563
+ e.preventDefault();
564
+ commitUrl();
565
+ inputRef.current?.blur();
566
+ } else if (e.key === "Escape") {
567
+ e.preventDefault();
568
+ setEditUrl(url);
569
+ inputRef.current?.blur();
570
+ }
571
+ },
572
+ [commitUrl, url]
573
+ );
574
+ const handleDelete = useCallback(() => {
575
+ editor.update(() => {
576
+ const node = $getNodeByKey(nodeKey);
577
+ if (node) node.remove();
578
+ });
579
+ }, [editor, nodeKey]);
580
+ const handleOpen = useCallback(() => {
581
+ if (url) window.open(url, "_blank", "noopener,noreferrer");
582
+ }, [url]);
583
+ const label = type ? typeLabels[type] : "Embed";
584
+ const cssModifier = type || "generic";
585
+ const embedModifierClass = embedType[cssModifier];
586
+ const semanticModifierClass = semanticEmbedModifierClass[cssModifier];
587
+ return /* @__PURE__ */ jsxs(
588
+ "div",
589
+ {
590
+ className: `${embed} ${embedModifierClass} ${semanticClassNames$1.embed} ${semanticModifierClass}`.trim(),
591
+ children: [
592
+ /* @__PURE__ */ jsxs("span", { className: `${badge} ${semanticClassNames$1.badge}`, children: [
593
+ /* @__PURE__ */ jsx("span", { className: `${dot} ${semanticClassNames$1.dot}` }),
594
+ label
595
+ ] }),
596
+ /* @__PURE__ */ jsx(
597
+ "span",
598
+ {
599
+ className: `${divider} ${semanticClassNames$1.divider}`
600
+ }
601
+ ),
602
+ /* @__PURE__ */ jsx(
603
+ "input",
604
+ {
605
+ ref: inputRef,
606
+ className: `${input} ${semanticClassNames$1.input}`,
607
+ type: "url",
608
+ value: editUrl,
609
+ onChange: (e) => setEditUrl(e.target.value),
610
+ onBlur: commitUrl,
611
+ onKeyDown: handleKeyDown,
612
+ placeholder: "https://..."
613
+ }
614
+ ),
615
+ /* @__PURE__ */ jsxs(
616
+ "span",
617
+ {
618
+ className: `${actions} ${semanticClassNames$1.actions}`,
619
+ children: [
620
+ /* @__PURE__ */ jsx(
621
+ "button",
622
+ {
623
+ type: "button",
624
+ className: `${actionButton} ${semanticClassNames$1.actionButton}`,
625
+ onClick: handleOpen,
626
+ title: "Open in new tab",
627
+ disabled: !url,
628
+ children: /* @__PURE__ */ jsx(ExternalLink, {})
629
+ }
630
+ ),
631
+ /* @__PURE__ */ jsx(
632
+ "button",
633
+ {
634
+ type: "button",
635
+ className: `${actionButton} ${semanticClassNames$1.actionButton} ${actionButtonDanger} ${semanticClassNames$1.actionButtonDanger}`,
636
+ onClick: handleDelete,
637
+ title: "Delete",
638
+ children: /* @__PURE__ */ jsx(Trash2, {})
639
+ }
640
+ )
641
+ ]
642
+ }
643
+ )
644
+ ]
645
+ }
646
+ );
647
+ }
648
+ export {
649
+ $createEmbedNode as $,
650
+ EmbedNode as E,
651
+ EmbedLinkRenderer as a,
652
+ $isEmbedNode as b,
653
+ createSelfThinkingMatcher as c,
654
+ EmbedRendererProvider as d,
655
+ EmbedStaticRenderer as e,
656
+ isCodesandboxUrl as f,
657
+ isGistUrl as g,
658
+ isGithubFilePreviewUrl as h,
659
+ isBilibiliVideoUrl as i,
660
+ isTweetUrl as j,
661
+ isYoutubeUrl as k,
662
+ matchEmbedUrl as m,
663
+ useEmbedRenderers as u
664
+ };
package/dist/index.mjs CHANGED
@@ -1,650 +1,12 @@
1
1
  var __defProp = Object.defineProperty;
2
2
  var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
3
3
  var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
4
- import { DecoratorNode, $getNodeByKey, $insertNodes, createCommand, PASTE_COMMAND, COMMAND_PRIORITY_LOW } from "lexical";
5
- import { createContext, use, lazy, useState, useMemo, useEffect, Suspense, Component, createElement, useRef, useCallback } from "react";
6
- import { jsx, jsxs } from "react/jsx-runtime";
7
- import { useColorScheme } from "@haklex/rich-editor";
8
- import { Github, ExternalLink, Trash2, Code } from "lucide-react";
4
+ import { E as EmbedNode, a as EmbedLinkRenderer, c as createSelfThinkingMatcher, m as matchEmbedUrl } from "./EmbedLinkRenderer-D0t-8cUk.js";
5
+ import { $, b, d, e, i, f, g, h, j, k, u } from "./EmbedLinkRenderer-D0t-8cUk.js";
6
+ import { $insertNodes, createCommand, PASTE_COMMAND, COMMAND_PRIORITY_LOW } from "lexical";
7
+ import { Code } from "lucide-react";
8
+ import { createElement, useMemo, useEffect } from "react";
9
9
  import { useLexicalComposerContext } from "@lexical/react/LexicalComposerContext";
10
- var semanticClassNames$1 = { wrapper: "rich-embed-link-wrapper", embed: "rich-embed", badge: "rich-embed__badge", dot: "rich-embed__dot", divider: "rich-embed__divider", input: "rich-embed__input", actions: "rich-embed__actions", actionButton: "rich-embed__action-btn", actionButtonDanger: "rich-embed__action-btn--danger" };
11
- var semanticEmbedModifierClass = { generic: "rich-embed--generic", tweet: "rich-embed--tweet", youtube: "rich-embed--youtube", codesandbox: "rich-embed--codesandbox", bilibili: "rich-embed--bilibili", "github-file": "rich-embed--github-file", "github-gist": "rich-embed--github-gist", thinking: "rich-embed--thinking" };
12
- var wrapper = "_1qucygc1";
13
- var embed = "_1qucygc2";
14
- var embedType = { generic: "_1qucygc3", tweet: "_1qucygc4", youtube: "_1qucygc5", codesandbox: "_1qucygc6", bilibili: "_1qucygc7", "github-file": "_1qucygc8", "github-gist": "_1qucygc9", thinking: "_1qucygca" };
15
- var badge = "_1qucygcb";
16
- var dot = "_1qucygcc";
17
- var divider = "_1qucygcd";
18
- var input = "_1qucygce";
19
- var actions = "_1qucygcf";
20
- var actionButton = "_1qucygcg";
21
- var actionButtonDanger = "_1qucygch";
22
- const EmbedRendererCtx = createContext({});
23
- const EmbedRendererProvider = EmbedRendererCtx.Provider;
24
- function useEmbedRenderers() {
25
- return use(EmbedRendererCtx);
26
- }
27
- var semanticClassNames = { ratioContainer: "embed-static-ratio-container", ratioInner: "embed-static-ratio-inner", iframe: "embed-static-iframe", gist: "embed-static-gist", gistIframe: "embed-static-gist__iframe", sourceLink: "embed-static-source-link", githubFile: "embed-static-github-file", githubFileCode: "embed-static-github-file__code", githubFileCodeLong: "embed-static-github-file__code--long", githubFileLine: "embed-static-github-file__line", githubFileLineNum: "embed-static-github-file__line-num", shiki: "embed-static-shiki", fallback: "embed-static-fallback", fallbackBadge: "embed-static-fallback__badge", fallbackDot: "embed-static-fallback__dot", fallbackLink: "embed-static-fallback__link", tweet: "embed-static-tweet", loading: "embed-static-loading" };
28
- var semanticFallbackModifierClass = { generic: "embed-static-fallback--generic", tweet: "embed-static-fallback--tweet", youtube: "embed-static-fallback--youtube", codesandbox: "embed-static-fallback--codesandbox", bilibili: "embed-static-fallback--bilibili", "github-file": "embed-static-fallback--github-file", "github-gist": "embed-static-fallback--github-gist", thinking: "embed-static-fallback--thinking" };
29
- var ratioContainer = "_1xclnej1";
30
- var ratioInner = "_1xclnej2";
31
- var iframe = "_1xclnej3";
32
- var gist = "_1xclnej4";
33
- var gistIframe = "_1xclnej5";
34
- var sourceLink = "_1xclnej6";
35
- var githubFile = "_1xclnej7";
36
- var githubFileCode = "_1xclnej8";
37
- var githubFileCodeLong = "_1xclnej9";
38
- var githubFileLine = "_1xclneja";
39
- var githubFileLineNum = "_1xclnejb";
40
- var shiki = "_1xclnejc";
41
- var fallback = "_1xclnejd";
42
- var fallbackType = { generic: "_1xclneje", tweet: "_1xclnejf", youtube: "_1xclnejg", codesandbox: "_1xclnejh", bilibili: "_1xclneji", "github-file": "_1xclnejj", "github-gist": "_1xclnejk", thinking: "_1xclnejl" };
43
- var fallbackBadge = "_1xclnejm";
44
- var fallbackDot = "_1xclnejn";
45
- var fallbackLink = "_1xclnejo";
46
- var tweet = "_1xclnejp";
47
- var loading = "_1xclnejq";
48
- const extToLang = {
49
- ".js": "javascript",
50
- ".ts": "typescript",
51
- ".mjs": "javascript",
52
- ".cjs": "javascript",
53
- ".mts": "typescript",
54
- ".cts": "typescript",
55
- ".jsx": "jsx",
56
- ".tsx": "tsx",
57
- ".md": "markdown",
58
- ".css": "css",
59
- ".scss": "scss",
60
- ".html": "html",
61
- ".json": "json",
62
- ".yml": "yaml",
63
- ".yaml": "yaml",
64
- ".toml": "toml",
65
- ".xml": "xml",
66
- ".sh": "bash",
67
- ".bash": "bash",
68
- ".zsh": "bash",
69
- ".go": "go",
70
- ".py": "python",
71
- ".rb": "ruby",
72
- ".java": "java",
73
- ".c": "c",
74
- ".cpp": "cpp",
75
- ".cs": "csharp",
76
- ".rs": "rust",
77
- ".swift": "swift",
78
- ".kt": "kotlin",
79
- ".lua": "lua",
80
- ".sql": "sql",
81
- ".graphql": "graphql",
82
- ".scala": "scala",
83
- ".dart": "dart",
84
- ".vue": "vue",
85
- ".h": "c",
86
- ".hpp": "cpp",
87
- ".m": "objectivec",
88
- ".ex": "elixir",
89
- ".r": "r"
90
- };
91
- function getLangFromPath(path) {
92
- const dot2 = path.lastIndexOf(".");
93
- if (dot2 === -1) return "text";
94
- return extToLang[path.slice(dot2).toLowerCase()] || "text";
95
- }
96
- const shikiPromise = import("shiki/bundle/web").catch(() => null);
97
- const typeLabels$1 = {
98
- tweet: "X / Twitter",
99
- youtube: "YouTube",
100
- codesandbox: "CodeSandbox",
101
- bilibili: "Bilibili",
102
- "github-file": "GitHub File",
103
- "github-gist": "GitHub Gist",
104
- thinking: "Thinking"
105
- };
106
- const LazyTweet = lazy(
107
- () => import("react-tweet").then((mod) => ({ default: mod.IsolatedTweet }))
108
- );
109
- class EmbedErrorBoundary extends Component {
110
- constructor() {
111
- super(...arguments);
112
- __publicField(this, "state", { hasError: false });
113
- }
114
- static getDerivedStateFromError() {
115
- return { hasError: true };
116
- }
117
- render() {
118
- return this.state.hasError ? this.props.fallback : this.props.children;
119
- }
120
- }
121
- function GitHubSvg() {
122
- return /* @__PURE__ */ jsx(Github, { size: 16 });
123
- }
124
- function FixedRatioContainer({
125
- children,
126
- ratio = 58
127
- }) {
128
- return /* @__PURE__ */ jsx(
129
- "div",
130
- {
131
- className: `${ratioContainer} ${semanticClassNames.ratioContainer}`,
132
- children: /* @__PURE__ */ jsx(
133
- "div",
134
- {
135
- className: `${ratioInner} ${semanticClassNames.ratioInner}`,
136
- style: { paddingBottom: `${ratio}%` },
137
- children
138
- }
139
- )
140
- }
141
- );
142
- }
143
- function FallbackLink({ url, type }) {
144
- const label = type ? typeLabels$1[type] : "Embed";
145
- const cssModifier = type || "generic";
146
- const fallbackTypeClass = fallbackType[cssModifier];
147
- const semanticModifierClass = semanticFallbackModifierClass[cssModifier];
148
- return /* @__PURE__ */ jsxs(
149
- "div",
150
- {
151
- className: `${fallback} ${fallbackTypeClass} ${semanticClassNames.fallback} ${semanticModifierClass}`.trim(),
152
- children: [
153
- /* @__PURE__ */ jsxs(
154
- "span",
155
- {
156
- className: `${fallbackBadge} ${semanticClassNames.fallbackBadge}`,
157
- children: [
158
- /* @__PURE__ */ jsx(
159
- "span",
160
- {
161
- className: `${fallbackDot} ${semanticClassNames.fallbackDot}`
162
- }
163
- ),
164
- label
165
- ]
166
- }
167
- ),
168
- /* @__PURE__ */ jsx(
169
- "a",
170
- {
171
- className: `${fallbackLink} ${semanticClassNames.fallbackLink}`,
172
- href: url,
173
- target: "_blank",
174
- rel: "noreferrer",
175
- children: url
176
- }
177
- )
178
- ]
179
- }
180
- );
181
- }
182
- function TweetRenderer({ url }) {
183
- const colorScheme = useColorScheme();
184
- let parsedUrl = null;
185
- try {
186
- parsedUrl = new URL(url);
187
- } catch {
188
- return /* @__PURE__ */ jsx(FallbackLink, { url, type: "tweet" });
189
- }
190
- const id = parsedUrl.pathname.split("/").pop();
191
- if (!id) return /* @__PURE__ */ jsx(FallbackLink, { url, type: "tweet" });
192
- const fallback2 = /* @__PURE__ */ jsx(FallbackLink, { url, type: "tweet" });
193
- return /* @__PURE__ */ jsx(EmbedErrorBoundary, { fallback: fallback2, children: /* @__PURE__ */ jsx("div", { className: `${tweet} ${semanticClassNames.tweet}`, children: /* @__PURE__ */ jsx(
194
- Suspense,
195
- {
196
- fallback: /* @__PURE__ */ jsx(
197
- "div",
198
- {
199
- className: `${loading} ${semanticClassNames.loading}`,
200
- children: "Loading tweet..."
201
- }
202
- ),
203
- children: /* @__PURE__ */ jsx(
204
- LazyTweet,
205
- {
206
- id,
207
- theme: colorScheme === "dark" ? "dark" : "light",
208
- style: { width: "100%" }
209
- }
210
- )
211
- }
212
- ) }) });
213
- }
214
- function GithubFileEmbed({ url, href }) {
215
- const colorScheme = useColorScheme();
216
- const shikiTheme = colorScheme === "dark" ? "github-dark" : "github-light";
217
- const split = url.pathname.split("/");
218
- const [, owner, repo, , ...rest] = split;
219
- const ref = rest[0];
220
- const path = ref ? rest.slice(1).join("/") : rest.join("/");
221
- const matchResult = url.hash.match(/L\d+/g);
222
- let startLine = 0;
223
- let endLine;
224
- if (matchResult) {
225
- if (matchResult.length === 1) {
226
- startLine = Number.parseInt(matchResult[0].slice(1)) - 1;
227
- endLine = startLine + 1;
228
- } else {
229
- startLine = Number.parseInt(matchResult[0].slice(1)) - 1;
230
- endLine = Number.parseInt(matchResult[1].slice(1));
231
- }
232
- }
233
- const [content, setContent] = useState(null);
234
- const [highlightedHtml, setHighlightedHtml] = useState(null);
235
- const [loading$1, setLoading] = useState(true);
236
- const [error, setError] = useState(false);
237
- const lang = useMemo(() => getLangFromPath(path), [path]);
238
- useEffect(() => {
239
- let cancelled = false;
240
- setContent(null);
241
- setHighlightedHtml(null);
242
- setLoading(true);
243
- setError(false);
244
- const fetchPromise = fetch(
245
- `https://cdn.jsdelivr.net/gh/${owner}/${repo}${ref ? `@${ref}` : ""}/${path}`
246
- ).then((res) => {
247
- if (!res.ok) throw new Error(`HTTP ${res.status}`);
248
- return res.text();
249
- });
250
- Promise.all([fetchPromise, shikiPromise]).then(([text, shikiMod]) => {
251
- if (cancelled) return;
252
- setContent(text);
253
- const lines2 = text.split("\n");
254
- const end2 = endLine ?? lines2.length;
255
- const displayCode = lines2.slice(startLine, end2).join("\n");
256
- if (!shikiMod) {
257
- setLoading(false);
258
- return;
259
- }
260
- shikiMod.codeToHtml(displayCode, {
261
- lang,
262
- theme: shikiTheme
263
- }).then((html) => {
264
- if (!cancelled) {
265
- setHighlightedHtml(html);
266
- setLoading(false);
267
- }
268
- }).catch(() => {
269
- if (!cancelled) setLoading(false);
270
- });
271
- }).catch(() => {
272
- if (!cancelled) {
273
- setError(true);
274
- setLoading(false);
275
- }
276
- });
277
- return () => {
278
- cancelled = true;
279
- };
280
- }, [owner, repo, ref, path, lang, startLine, endLine, shikiTheme]);
281
- if (loading$1) {
282
- return /* @__PURE__ */ jsx("div", { className: `${loading} ${semanticClassNames.loading}`, children: "Loading GitHub File Preview..." });
283
- }
284
- if (error || content === null) {
285
- return /* @__PURE__ */ jsx(FallbackLink, { url: href, type: "github-file" });
286
- }
287
- const lines = content.split("\n");
288
- const end = endLine ?? lines.length;
289
- const isLong = end - startLine > 20;
290
- return /* @__PURE__ */ jsxs(
291
- "div",
292
- {
293
- className: `${githubFile} ${semanticClassNames.githubFile}`,
294
- children: [
295
- /* @__PURE__ */ jsx(
296
- "div",
297
- {
298
- className: `${githubFileCode} ${semanticClassNames.githubFileCode} ${isLong ? `${githubFileCodeLong} ${semanticClassNames.githubFileCodeLong}` : ""}`.trim(),
299
- children: highlightedHtml ? /* @__PURE__ */ jsx(
300
- "div",
301
- {
302
- className: `${shiki} ${semanticClassNames.shiki}`,
303
- style: { "--start-line": startLine },
304
- dangerouslySetInnerHTML: { __html: highlightedHtml }
305
- }
306
- ) : /* @__PURE__ */ jsx("pre", { children: /* @__PURE__ */ jsx("code", { children: lines.slice(startLine, end).map((line, i) => /* @__PURE__ */ jsxs(
307
- "span",
308
- {
309
- className: `${githubFileLine} ${semanticClassNames.githubFileLine}`,
310
- children: [
311
- /* @__PURE__ */ jsx(
312
- "span",
313
- {
314
- className: `${githubFileLineNum} ${semanticClassNames.githubFileLineNum}`,
315
- children: startLine + i + 1
316
- }
317
- ),
318
- line,
319
- "\n"
320
- ]
321
- },
322
- i
323
- )) }) })
324
- }
325
- ),
326
- /* @__PURE__ */ jsxs(
327
- "a",
328
- {
329
- className: `${sourceLink} ${semanticClassNames.sourceLink}`,
330
- href,
331
- target: "_blank",
332
- rel: "noreferrer",
333
- children: [
334
- /* @__PURE__ */ jsx(GitHubSvg, {}),
335
- /* @__PURE__ */ jsx("span", { children: href })
336
- ]
337
- }
338
- )
339
- ]
340
- }
341
- );
342
- }
343
- function EmbedStaticRenderer({ type, url }) {
344
- const customRenderers = useEmbedRenderers();
345
- if (!url) return null;
346
- if (type && customRenderers[type]) {
347
- const Custom = customRenderers[type];
348
- return /* @__PURE__ */ jsx(Custom, { url, type });
349
- }
350
- let parsedUrl = null;
351
- try {
352
- parsedUrl = new URL(url);
353
- } catch {
354
- return /* @__PURE__ */ jsx(FallbackLink, { url, type });
355
- }
356
- switch (type) {
357
- case "tweet": {
358
- return /* @__PURE__ */ jsx(TweetRenderer, { url });
359
- }
360
- case "youtube": {
361
- const id = parsedUrl.searchParams.get("v");
362
- if (!id) return /* @__PURE__ */ jsx(FallbackLink, { url, type });
363
- return /* @__PURE__ */ jsx(FixedRatioContainer, { children: /* @__PURE__ */ jsx(
364
- "iframe",
365
- {
366
- src: `https://www.youtube.com/embed/${id}`,
367
- className: `${iframe} ${semanticClassNames.iframe}`,
368
- allow: "accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture",
369
- allowFullScreen: true,
370
- title: "YouTube video player"
371
- }
372
- ) });
373
- }
374
- case "bilibili": {
375
- const id = parsedUrl.pathname.split("/")[2];
376
- if (!id) return /* @__PURE__ */ jsx(FallbackLink, { url, type });
377
- return /* @__PURE__ */ jsx(FixedRatioContainer, { children: /* @__PURE__ */ jsx(
378
- "iframe",
379
- {
380
- src: `//player.bilibili.com/player.html?bvid=${id}&autoplay=0`,
381
- scrolling: "no",
382
- className: `${iframe} ${semanticClassNames.iframe}`,
383
- allowFullScreen: true
384
- }
385
- ) });
386
- }
387
- case "codesandbox": {
388
- const path = parsedUrl.pathname.slice(2);
389
- return /* @__PURE__ */ jsx(FixedRatioContainer, { children: /* @__PURE__ */ jsx(
390
- "iframe",
391
- {
392
- className: `${iframe} ${semanticClassNames.iframe}`,
393
- src: `https://codesandbox.io/embed/${path}?fontsize=14&hidenavigation=1&theme=dark${parsedUrl.search}`
394
- }
395
- ) });
396
- }
397
- case "github-gist": {
398
- const [, owner, id] = parsedUrl.pathname.split("/");
399
- if (!owner || !id) return /* @__PURE__ */ jsx(FallbackLink, { url, type });
400
- return /* @__PURE__ */ jsxs("div", { className: `${gist} ${semanticClassNames.gist}`, children: [
401
- /* @__PURE__ */ jsx(
402
- "iframe",
403
- {
404
- src: `https://gist.github.com/${owner}/${id}.pibb`,
405
- className: `${gistIframe} ${semanticClassNames.gistIframe}`
406
- }
407
- ),
408
- /* @__PURE__ */ jsxs(
409
- "a",
410
- {
411
- className: `${sourceLink} ${semanticClassNames.sourceLink}`,
412
- href: url,
413
- target: "_blank",
414
- rel: "noreferrer",
415
- children: [
416
- /* @__PURE__ */ jsx(GitHubSvg, {}),
417
- /* @__PURE__ */ jsx("span", { children: url })
418
- ]
419
- }
420
- )
421
- ] });
422
- }
423
- case "github-file": {
424
- return /* @__PURE__ */ jsx(GithubFileEmbed, { url: parsedUrl, href: url });
425
- }
426
- default:
427
- return /* @__PURE__ */ jsx(FallbackLink, { url, type });
428
- }
429
- }
430
- class EmbedNode extends DecoratorNode {
431
- constructor(url, source, key) {
432
- super(key);
433
- __publicField(this, "__url");
434
- __publicField(this, "__source");
435
- this.__url = url;
436
- this.__source = source;
437
- }
438
- static getType() {
439
- return "embed";
440
- }
441
- static clone(node) {
442
- return new EmbedNode(node.__url, node.__source, node.__key);
443
- }
444
- createDOM(_config) {
445
- const div = document.createElement("div");
446
- div.className = `${wrapper} ${semanticClassNames$1.wrapper}`;
447
- return div;
448
- }
449
- updateDOM() {
450
- return false;
451
- }
452
- isInline() {
453
- return false;
454
- }
455
- static importJSON(serializedNode) {
456
- return $createEmbedNode(serializedNode.url, serializedNode.source);
457
- }
458
- exportJSON() {
459
- return {
460
- ...super.exportJSON(),
461
- type: "embed",
462
- url: this.__url,
463
- source: this.__source,
464
- version: 1
465
- };
466
- }
467
- getUrl() {
468
- return this.getLatest().__url;
469
- }
470
- setUrl(url) {
471
- const writable = this.getWritable();
472
- writable.__url = url;
473
- }
474
- getSource() {
475
- return this.getLatest().__source;
476
- }
477
- setSource(source) {
478
- const writable = this.getWritable();
479
- writable.__source = source;
480
- }
481
- decorate(_editor, _config) {
482
- return createElement(EmbedStaticRenderer, {
483
- type: this.__source,
484
- url: this.__url
485
- });
486
- }
487
- }
488
- function $createEmbedNode(url, source) {
489
- return new EmbedNode(url, source);
490
- }
491
- function $isEmbedNode(node) {
492
- return node instanceof EmbedNode;
493
- }
494
- const isTweetUrl = (url) => (url.hostname === "twitter.com" || url.hostname === "x.com") && url.pathname.startsWith("/");
495
- const isYoutubeUrl = (url) => url.hostname === "www.youtube.com" && url.pathname.startsWith("/watch");
496
- const isCodesandboxUrl = (url) => url.hostname === "codesandbox.io" && url.pathname.split("/").length === 3;
497
- const isBilibiliVideoUrl = (url) => url.hostname.includes("bilibili.com") && url.pathname.startsWith("/video/BV");
498
- const isGithubFilePreviewUrl = (url) => {
499
- const [, , , type] = url.pathname.split("/");
500
- return url.hostname === "github.com" && type === "blob";
501
- };
502
- const isGistUrl = (url) => url.hostname === "gist.github.com";
503
- function createSelfThinkingMatcher(hostnames) {
504
- return (url) => hostnames.includes(url.hostname) && url.pathname.startsWith("/thinking/");
505
- }
506
- function matchEmbedUrl(url, selfThinkingMatcher) {
507
- if (isTweetUrl(url)) return "tweet";
508
- if (isYoutubeUrl(url)) return "youtube";
509
- if (isCodesandboxUrl(url)) return "codesandbox";
510
- if (isBilibiliVideoUrl(url)) return "bilibili";
511
- if (isGithubFilePreviewUrl(url)) return "github-file";
512
- if (isGistUrl(url)) return "github-gist";
513
- if (selfThinkingMatcher?.(url)) return "thinking";
514
- return null;
515
- }
516
- const typeLabels = {
517
- tweet: "X / Twitter",
518
- youtube: "YouTube",
519
- codesandbox: "CodeSandbox",
520
- bilibili: "Bilibili",
521
- "github-file": "GitHub File",
522
- "github-gist": "GitHub Gist",
523
- thinking: "Thinking"
524
- };
525
- function EmbedLinkRenderer({
526
- type,
527
- url,
528
- nodeKey
529
- }) {
530
- const [editor] = useLexicalComposerContext();
531
- const [editUrl, setEditUrl] = useState(url);
532
- const inputRef = useRef(null);
533
- useEffect(() => {
534
- setEditUrl(url);
535
- }, [url]);
536
- useEffect(() => {
537
- if (!url) {
538
- inputRef.current?.focus();
539
- }
540
- }, [url]);
541
- const commitUrl = useCallback(() => {
542
- const trimmed = editUrl.trim();
543
- if (!trimmed) return;
544
- editor.update(() => {
545
- const node = $getNodeByKey(nodeKey);
546
- if (!node || !("setUrl" in node)) return;
547
- const n = node;
548
- const currentUrl = n.getUrl?.();
549
- if (currentUrl !== trimmed) {
550
- n.setUrl(trimmed);
551
- }
552
- if (n.setSource) {
553
- try {
554
- n.setSource(matchEmbedUrl(new URL(trimmed)));
555
- } catch {
556
- }
557
- }
558
- });
559
- }, [editor, nodeKey, editUrl]);
560
- const handleKeyDown = useCallback(
561
- (e) => {
562
- if (e.key === "Enter") {
563
- e.preventDefault();
564
- commitUrl();
565
- inputRef.current?.blur();
566
- } else if (e.key === "Escape") {
567
- e.preventDefault();
568
- setEditUrl(url);
569
- inputRef.current?.blur();
570
- }
571
- },
572
- [commitUrl, url]
573
- );
574
- const handleDelete = useCallback(() => {
575
- editor.update(() => {
576
- const node = $getNodeByKey(nodeKey);
577
- if (node) node.remove();
578
- });
579
- }, [editor, nodeKey]);
580
- const handleOpen = useCallback(() => {
581
- if (url) window.open(url, "_blank", "noopener,noreferrer");
582
- }, [url]);
583
- const label = type ? typeLabels[type] : "Embed";
584
- const cssModifier = type || "generic";
585
- const embedModifierClass = embedType[cssModifier];
586
- const semanticModifierClass = semanticEmbedModifierClass[cssModifier];
587
- return /* @__PURE__ */ jsxs(
588
- "div",
589
- {
590
- className: `${embed} ${embedModifierClass} ${semanticClassNames$1.embed} ${semanticModifierClass}`.trim(),
591
- children: [
592
- /* @__PURE__ */ jsxs("span", { className: `${badge} ${semanticClassNames$1.badge}`, children: [
593
- /* @__PURE__ */ jsx("span", { className: `${dot} ${semanticClassNames$1.dot}` }),
594
- label
595
- ] }),
596
- /* @__PURE__ */ jsx(
597
- "span",
598
- {
599
- className: `${divider} ${semanticClassNames$1.divider}`
600
- }
601
- ),
602
- /* @__PURE__ */ jsx(
603
- "input",
604
- {
605
- ref: inputRef,
606
- className: `${input} ${semanticClassNames$1.input}`,
607
- type: "url",
608
- value: editUrl,
609
- onChange: (e) => setEditUrl(e.target.value),
610
- onBlur: commitUrl,
611
- onKeyDown: handleKeyDown,
612
- placeholder: "https://..."
613
- }
614
- ),
615
- /* @__PURE__ */ jsxs(
616
- "span",
617
- {
618
- className: `${actions} ${semanticClassNames$1.actions}`,
619
- children: [
620
- /* @__PURE__ */ jsx(
621
- "button",
622
- {
623
- type: "button",
624
- className: `${actionButton} ${semanticClassNames$1.actionButton}`,
625
- onClick: handleOpen,
626
- title: "Open in new tab",
627
- disabled: !url,
628
- children: /* @__PURE__ */ jsx(ExternalLink, {})
629
- }
630
- ),
631
- /* @__PURE__ */ jsx(
632
- "button",
633
- {
634
- type: "button",
635
- className: `${actionButton} ${semanticClassNames$1.actionButton} ${actionButtonDanger} ${semanticClassNames$1.actionButtonDanger}`,
636
- onClick: handleDelete,
637
- title: "Delete",
638
- children: /* @__PURE__ */ jsx(Trash2, {})
639
- }
640
- )
641
- ]
642
- }
643
- )
644
- ]
645
- }
646
- );
647
- }
648
10
  const _EmbedEditNode = class _EmbedEditNode extends EmbedNode {
649
11
  static clone(node) {
650
12
  return new _EmbedEditNode(node.__url, node.__source, node.__key);
@@ -743,25 +105,25 @@ function EmbedPlugin({ selfHostnames }) {
743
105
  }
744
106
  export {
745
107
  $createEmbedEditNode,
746
- $createEmbedNode,
108
+ $ as $createEmbedNode,
747
109
  $isEmbedEditNode,
748
- $isEmbedNode,
110
+ b as $isEmbedNode,
749
111
  EmbedEditNode,
750
112
  EmbedLinkRenderer,
751
113
  EmbedNode,
752
114
  EmbedPlugin,
753
- EmbedRendererProvider,
754
- EmbedStaticRenderer,
115
+ d as EmbedRendererProvider,
116
+ e as EmbedStaticRenderer,
755
117
  INSERT_EMBED_COMMAND,
756
118
  createSelfThinkingMatcher,
757
119
  embedEditNodes,
758
120
  embedNodes,
759
- isBilibiliVideoUrl,
760
- isCodesandboxUrl,
761
- isGistUrl,
762
- isGithubFilePreviewUrl,
763
- isTweetUrl,
764
- isYoutubeUrl,
121
+ i as isBilibiliVideoUrl,
122
+ f as isCodesandboxUrl,
123
+ g as isGistUrl,
124
+ h as isGithubFilePreviewUrl,
125
+ j as isTweetUrl,
126
+ k as isYoutubeUrl,
765
127
  matchEmbedUrl,
766
- useEmbedRenderers
128
+ u as useEmbedRenderers
767
129
  };
@@ -0,0 +1,21 @@
1
+ import { E as EmbedNode } from "./EmbedLinkRenderer-D0t-8cUk.js";
2
+ import { $, b, a, d, e, c, i, f, g, h, j, k, m, u } from "./EmbedLinkRenderer-D0t-8cUk.js";
3
+ const embedNodes = [EmbedNode];
4
+ export {
5
+ $ as $createEmbedNode,
6
+ b as $isEmbedNode,
7
+ a as EmbedLinkRenderer,
8
+ EmbedNode,
9
+ d as EmbedRendererProvider,
10
+ e as EmbedStaticRenderer,
11
+ c as createSelfThinkingMatcher,
12
+ embedNodes,
13
+ i as isBilibiliVideoUrl,
14
+ f as isCodesandboxUrl,
15
+ g as isGistUrl,
16
+ h as isGithubFilePreviewUrl,
17
+ j as isTweetUrl,
18
+ k as isYoutubeUrl,
19
+ m as matchEmbedUrl,
20
+ u as useEmbedRenderers
21
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@haklex/rich-ext-embed",
3
- "version": "0.0.41",
3
+ "version": "0.0.42",
4
4
  "description": "Embed extension for Twitter, YouTube, etc.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -22,8 +22,8 @@
22
22
  "dependencies": {
23
23
  "lucide-react": "^0.574.0",
24
24
  "react-tweet": "npm:@innei/react-tweet@3.4.2",
25
- "@haklex/rich-editor": "0.0.41",
26
- "@haklex/rich-style-token": "0.0.41"
25
+ "@haklex/rich-style-token": "0.0.42",
26
+ "@haklex/rich-editor": "0.0.42"
27
27
  },
28
28
  "devDependencies": {
29
29
  "@lexical/react": "^0.41.0",