quoin-editor 0.4.1 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (231) hide show
  1. package/CHANGELOG.md +205 -0
  2. package/MIGRATION.md +233 -0
  3. package/README.md +78 -715
  4. package/dist/ai.cjs +1107 -0
  5. package/dist/ai.cjs.map +1 -0
  6. package/dist/ai.d.cts +68 -0
  7. package/dist/ai.d.ts +68 -0
  8. package/dist/ai.js +1107 -0
  9. package/dist/ai.js.map +1 -0
  10. package/dist/catalogue.cjs +59 -0
  11. package/dist/catalogue.cjs.map +1 -0
  12. package/dist/catalogue.d.cts +39 -0
  13. package/dist/catalogue.d.ts +39 -0
  14. package/dist/catalogue.js +59 -0
  15. package/dist/catalogue.js.map +1 -0
  16. package/dist/chunk-2QCRZ7OL.cjs +319 -0
  17. package/dist/chunk-2QCRZ7OL.cjs.map +1 -0
  18. package/dist/chunk-2VDK27IF.cjs +6666 -0
  19. package/dist/chunk-2VDK27IF.cjs.map +1 -0
  20. package/dist/chunk-47MLKSI2.js +326 -0
  21. package/dist/chunk-47MLKSI2.js.map +1 -0
  22. package/dist/chunk-5DNCCN6X.cjs +326 -0
  23. package/dist/chunk-5DNCCN6X.cjs.map +1 -0
  24. package/dist/chunk-AEJXZBVR.cjs +1378 -0
  25. package/dist/chunk-AEJXZBVR.cjs.map +1 -0
  26. package/dist/chunk-CDELOHDY.cjs +239 -0
  27. package/dist/chunk-CDELOHDY.cjs.map +1 -0
  28. package/dist/chunk-E75EHRPA.cjs +175 -0
  29. package/dist/chunk-E75EHRPA.cjs.map +1 -0
  30. package/dist/chunk-EL2OEJQC.cjs +562 -0
  31. package/dist/chunk-EL2OEJQC.cjs.map +1 -0
  32. package/dist/chunk-GLLXHMH2.cjs +25 -0
  33. package/dist/chunk-GLLXHMH2.cjs.map +1 -0
  34. package/dist/chunk-GTTPVJSD.cjs +3286 -0
  35. package/dist/chunk-GTTPVJSD.cjs.map +1 -0
  36. package/dist/chunk-HELJTLNC.cjs +100 -0
  37. package/dist/chunk-HELJTLNC.cjs.map +1 -0
  38. package/dist/chunk-HJIQT3S5.js +4047 -0
  39. package/dist/chunk-HJIQT3S5.js.map +1 -0
  40. package/dist/chunk-HZSSEYJS.cjs +504 -0
  41. package/dist/chunk-HZSSEYJS.cjs.map +1 -0
  42. package/dist/chunk-JDPTZUBW.js +184 -0
  43. package/dist/chunk-JDPTZUBW.js.map +1 -0
  44. package/dist/chunk-JEIFJHEI.js +14 -0
  45. package/dist/chunk-JEIFJHEI.js.map +1 -0
  46. package/dist/chunk-K574XHMM.js +25 -0
  47. package/dist/chunk-K574XHMM.js.map +1 -0
  48. package/dist/chunk-KAVU6T6J.js +249 -0
  49. package/dist/chunk-KAVU6T6J.js.map +1 -0
  50. package/dist/chunk-KXOBZGQO.js +1378 -0
  51. package/dist/chunk-KXOBZGQO.js.map +1 -0
  52. package/dist/chunk-LCQSG4M6.js +3335 -0
  53. package/dist/chunk-LCQSG4M6.js.map +1 -0
  54. package/dist/chunk-MQ2XQ4MX.js +100 -0
  55. package/dist/chunk-MQ2XQ4MX.js.map +1 -0
  56. package/dist/chunk-ND5XJVK2.cjs +3335 -0
  57. package/dist/chunk-ND5XJVK2.cjs.map +1 -0
  58. package/dist/chunk-NNDYYEW5.js +504 -0
  59. package/dist/chunk-NNDYYEW5.js.map +1 -0
  60. package/dist/chunk-NOKWNK2O.js +319 -0
  61. package/dist/chunk-NOKWNK2O.js.map +1 -0
  62. package/dist/chunk-OMOBMXD5.cjs +57 -0
  63. package/dist/chunk-OMOBMXD5.cjs.map +1 -0
  64. package/dist/chunk-PMWCQSQX.js +239 -0
  65. package/dist/chunk-PMWCQSQX.js.map +1 -0
  66. package/dist/chunk-POBM7MGR.js +30 -0
  67. package/dist/chunk-POBM7MGR.js.map +1 -0
  68. package/dist/chunk-PWUEPDA3.cjs +598 -0
  69. package/dist/chunk-PWUEPDA3.cjs.map +1 -0
  70. package/dist/chunk-PZ4GT5JO.cjs +4047 -0
  71. package/dist/chunk-PZ4GT5JO.cjs.map +1 -0
  72. package/dist/chunk-QAW5NH6S.cjs +14 -0
  73. package/dist/chunk-QAW5NH6S.cjs.map +1 -0
  74. package/dist/chunk-RDG4VI6E.cjs +706 -0
  75. package/dist/chunk-RDG4VI6E.cjs.map +1 -0
  76. package/dist/chunk-RUDWMKYX.js +562 -0
  77. package/dist/chunk-RUDWMKYX.js.map +1 -0
  78. package/dist/chunk-SG37UBDO.js +706 -0
  79. package/dist/chunk-SG37UBDO.js.map +1 -0
  80. package/dist/chunk-SLVDAHHK.cjs +30 -0
  81. package/dist/chunk-SLVDAHHK.cjs.map +1 -0
  82. package/dist/chunk-T7SW6OWV.js +598 -0
  83. package/dist/chunk-T7SW6OWV.js.map +1 -0
  84. package/dist/chunk-TC4DARU2.js +175 -0
  85. package/dist/chunk-TC4DARU2.js.map +1 -0
  86. package/dist/chunk-WQHV3MRE.cjs +184 -0
  87. package/dist/chunk-WQHV3MRE.cjs.map +1 -0
  88. package/dist/chunk-WXMYDXPN.cjs +33 -0
  89. package/dist/chunk-WXMYDXPN.cjs.map +1 -0
  90. package/dist/chunk-XEBWC7ZL.js +57 -0
  91. package/dist/chunk-XEBWC7ZL.js.map +1 -0
  92. package/dist/chunk-ZC24WB7Y.cjs +249 -0
  93. package/dist/chunk-ZC24WB7Y.cjs.map +1 -0
  94. package/dist/chunk-ZGZEJCRV.js +6666 -0
  95. package/dist/chunk-ZGZEJCRV.js.map +1 -0
  96. package/dist/chunk-ZHOSSWUM.js +33 -0
  97. package/dist/chunk-ZHOSSWUM.js.map +1 -0
  98. package/dist/chunk-ZS2WA357.js +3286 -0
  99. package/dist/chunk-ZS2WA357.js.map +1 -0
  100. package/dist/comments.cjs +993 -0
  101. package/dist/comments.cjs.map +1 -0
  102. package/dist/comments.d.cts +88 -0
  103. package/dist/comments.d.ts +88 -0
  104. package/dist/comments.js +993 -0
  105. package/dist/comments.js.map +1 -0
  106. package/dist/diff.cjs +343 -0
  107. package/dist/diff.cjs.map +1 -0
  108. package/dist/diff.d.cts +67 -0
  109. package/dist/diff.d.ts +67 -0
  110. package/dist/diff.js +343 -0
  111. package/dist/diff.js.map +1 -0
  112. package/dist/docx.cjs +854 -0
  113. package/dist/docx.cjs.map +1 -0
  114. package/dist/docx.d.cts +27 -0
  115. package/dist/docx.d.ts +27 -0
  116. package/dist/docx.js +854 -0
  117. package/dist/docx.js.map +1 -0
  118. package/dist/embed-provider-jXwlRmnp.d.cts +19 -0
  119. package/dist/embed-provider-jXwlRmnp.d.ts +19 -0
  120. package/dist/embed.cjs +17 -0
  121. package/dist/embed.cjs.map +1 -0
  122. package/dist/embed.d.cts +56 -0
  123. package/dist/embed.d.ts +56 -0
  124. package/dist/embed.js +17 -0
  125. package/dist/embed.js.map +1 -0
  126. package/dist/emoji-data.cjs +1968 -0
  127. package/dist/emoji-data.cjs.map +1 -0
  128. package/dist/emoji-data.js +1968 -0
  129. package/dist/emoji-data.js.map +1 -0
  130. package/dist/emoji-extension-C12gDPm6.d.cts +46 -0
  131. package/dist/emoji-extension-DpQ43HjE.d.ts +46 -0
  132. package/dist/emoji.cjs +18 -0
  133. package/dist/emoji.cjs.map +1 -0
  134. package/dist/emoji.d.cts +31 -0
  135. package/dist/emoji.d.ts +31 -0
  136. package/dist/emoji.js +18 -0
  137. package/dist/emoji.js.map +1 -0
  138. package/dist/engine.cjs +119 -0
  139. package/dist/engine.cjs.map +1 -0
  140. package/dist/engine.d.cts +144 -0
  141. package/dist/engine.d.ts +144 -0
  142. package/dist/engine.js +119 -0
  143. package/dist/engine.js.map +1 -0
  144. package/dist/find-extension-DKqLLSuP.d.ts +41 -0
  145. package/dist/find-extension-_sbZEt3R.d.cts +41 -0
  146. package/dist/find.cjs +20 -0
  147. package/dist/find.cjs.map +1 -0
  148. package/dist/find.d.cts +44 -0
  149. package/dist/find.d.ts +44 -0
  150. package/dist/find.js +20 -0
  151. package/dist/find.js.map +1 -0
  152. package/dist/headless.cjs +151 -518
  153. package/dist/headless.cjs.map +1 -1
  154. package/dist/headless.d.cts +102 -69
  155. package/dist/headless.d.ts +102 -69
  156. package/dist/headless.js +149 -467
  157. package/dist/headless.js.map +1 -1
  158. package/dist/index-CgD72kHB.d.cts +65 -0
  159. package/dist/index-DNYbTK3w.d.ts +65 -0
  160. package/dist/index.cjs +183 -2753
  161. package/dist/index.cjs.map +1 -1
  162. package/dist/index.d.cts +345 -253
  163. package/dist/index.d.ts +345 -253
  164. package/dist/index.js +168 -2696
  165. package/dist/index.js.map +1 -1
  166. package/dist/math.cjs +18 -0
  167. package/dist/math.cjs.map +1 -0
  168. package/dist/math.d.cts +49 -0
  169. package/dist/math.d.ts +49 -0
  170. package/dist/math.js +18 -0
  171. package/dist/math.js.map +1 -0
  172. package/dist/mention.cjs +18 -0
  173. package/dist/mention.cjs.map +1 -0
  174. package/dist/mention.d.cts +50 -0
  175. package/dist/mention.d.ts +50 -0
  176. package/dist/mention.js +18 -0
  177. package/dist/mention.js.map +1 -0
  178. package/dist/presence.cjs +565 -0
  179. package/dist/presence.cjs.map +1 -0
  180. package/dist/presence.d.cts +30 -0
  181. package/dist/presence.d.ts +30 -0
  182. package/dist/presence.js +565 -0
  183. package/dist/presence.js.map +1 -0
  184. package/dist/registry-CWBOSAHm.d.cts +121 -0
  185. package/dist/registry-CWBOSAHm.d.ts +121 -0
  186. package/dist/stats-extension-Bly88uio.d.ts +40 -0
  187. package/dist/stats-extension-CJPijvUb.d.cts +40 -0
  188. package/dist/stats.cjs +19 -0
  189. package/dist/stats.cjs.map +1 -0
  190. package/dist/stats.d.cts +19 -0
  191. package/dist/stats.d.ts +19 -0
  192. package/dist/stats.js +19 -0
  193. package/dist/stats.js.map +1 -0
  194. package/dist/styles.css +1 -1
  195. package/dist/table-extension-BTLxFy2S.d.cts +57 -0
  196. package/dist/table-extension-Dzf4k6hI.d.ts +57 -0
  197. package/dist/table.cjs +50 -0
  198. package/dist/table.cjs.map +1 -0
  199. package/dist/table.d.cts +57 -0
  200. package/dist/table.d.ts +57 -0
  201. package/dist/table.js +50 -0
  202. package/dist/table.js.map +1 -0
  203. package/dist/to-html-C5t7xyaM.d.ts +21 -0
  204. package/dist/to-html-CNCITCln.d.cts +21 -0
  205. package/dist/toc-extension-B3-s6h2x.d.cts +25 -0
  206. package/dist/toc-extension-DWm5alVj.d.ts +25 -0
  207. package/dist/toc.cjs +18 -0
  208. package/dist/toc.cjs.map +1 -0
  209. package/dist/toc.d.cts +24 -0
  210. package/dist/toc.d.ts +24 -0
  211. package/dist/toc.js +18 -0
  212. package/dist/toc.js.map +1 -0
  213. package/dist/types-Cz7XJLaK.d.cts +366 -0
  214. package/dist/types-DULc8wwg.d.cts +120 -0
  215. package/dist/types-DULc8wwg.d.ts +120 -0
  216. package/dist/types-a1CRBlEh.d.ts +366 -0
  217. package/dist/view-controller-DSHw0ltQ.d.cts +118 -0
  218. package/dist/view-controller-UbpzblGl.d.ts +118 -0
  219. package/dist/view.cjs +23 -0
  220. package/dist/view.cjs.map +1 -0
  221. package/dist/view.d.cts +40 -0
  222. package/dist/view.d.ts +40 -0
  223. package/dist/view.js +23 -0
  224. package/dist/view.js.map +1 -0
  225. package/dist/yjs.cjs +1026 -0
  226. package/dist/yjs.cjs.map +1 -0
  227. package/dist/yjs.d.cts +22 -0
  228. package/dist/yjs.d.ts +22 -0
  229. package/dist/yjs.js +1026 -0
  230. package/dist/yjs.js.map +1 -0
  231. package/package.json +219 -20
@@ -0,0 +1,706 @@
1
+ import {
2
+ domainOf,
3
+ webAddress
4
+ } from "./chunk-K574XHMM.js";
5
+ import {
6
+ Button,
7
+ FLOATING_TOOLBAR,
8
+ Input,
9
+ Popover,
10
+ PopoverAnchor,
11
+ PopoverContent,
12
+ bubblePart,
13
+ focusNode,
14
+ inlineDirection,
15
+ insertBlockToWrite,
16
+ insertBlocks,
17
+ interceptKeys,
18
+ keepFocus,
19
+ linkAddress,
20
+ preventDefault,
21
+ selectHandlers,
22
+ toError,
23
+ useFocusKey,
24
+ useNodeKeys,
25
+ useOutsidePress,
26
+ useQuoin,
27
+ useRangeAnchor,
28
+ useRovingFocus
29
+ } from "./chunk-ZGZEJCRV.js";
30
+ import {
31
+ isComposingKey,
32
+ useEditorState,
33
+ useIsomorphicLayoutEffect,
34
+ useReadOnly
35
+ } from "./chunk-ZS2WA357.js";
36
+ import {
37
+ devWarn
38
+ } from "./chunk-XEBWC7ZL.js";
39
+ import {
40
+ Transaction,
41
+ createPluginKey,
42
+ defineExtension,
43
+ headBlockId,
44
+ insertSlice,
45
+ isCollapsed,
46
+ isParagraph,
47
+ pastedLink,
48
+ primaryRegion,
49
+ regionLength,
50
+ replaceBlockStep,
51
+ runCommands,
52
+ sameSelection,
53
+ setAttrsStep,
54
+ sliceFromClipboard,
55
+ startOf,
56
+ withContent
57
+ } from "./chunk-HJIQT3S5.js";
58
+ import {
59
+ EMBED_FRAME,
60
+ blockById,
61
+ coreEmbedProviders,
62
+ embedFrame,
63
+ sanitizeHref,
64
+ str
65
+ } from "./chunk-LCQSG4M6.js";
66
+
67
+ // src/extensions/embed/LinkCard.tsx
68
+ import { useRef, useState } from "react";
69
+ import { jsx, jsxs } from "react/jsx-runtime";
70
+ function LinkCard({ controller, blockId, url, title, description, image, readOnly, linkRef }) {
71
+ const card = useRef(null);
72
+ const [failed, setFailed] = useState(null);
73
+ const href = sanitizeHref(url);
74
+ const domain = domainOf(url);
75
+ const picture = webAddress(image);
76
+ const name = title || url;
77
+ return /* @__PURE__ */ jsxs("div", { ref: card, className: "quoin-link-card", ...readOnly ? void 0 : selectHandlers(controller, blockId, card), children: [
78
+ /* @__PURE__ */ jsxs("div", { className: "quoin-link-card-text", children: [
79
+ href === null ? /* @__PURE__ */ jsx("span", { className: "quoin-link-card-title", children: name }) : /* @__PURE__ */ jsx(
80
+ "a",
81
+ {
82
+ ref: linkRef,
83
+ href,
84
+ target: "_blank",
85
+ rel: "noopener noreferrer",
86
+ className: "quoin-link-card-title",
87
+ tabIndex: readOnly ? void 0 : -1,
88
+ children: name
89
+ }
90
+ ),
91
+ description && /* @__PURE__ */ jsx("p", { className: "quoin-link-card-description", children: description }),
92
+ domain !== null && /* @__PURE__ */ jsx("span", { className: "quoin-link-card-domain", children: domain })
93
+ ] }),
94
+ picture !== null && picture !== failed && /* @__PURE__ */ jsx(
95
+ "img",
96
+ {
97
+ src: picture,
98
+ alt: "",
99
+ loading: "lazy",
100
+ referrerPolicy: "no-referrer",
101
+ draggable: false,
102
+ className: "quoin-link-card-image",
103
+ onError: () => setFailed(picture)
104
+ }
105
+ )
106
+ ] });
107
+ }
108
+
109
+ // src/extensions/embed/use-link-card-keys.ts
110
+ import { useMemo, useRef as useRef2 } from "react";
111
+ function useLinkCardKeys(controller, blockId, url, editable) {
112
+ const root = useRef2(null);
113
+ const link = useRef2(null);
114
+ const keys = useMemo(() => ({ enter: () => link.current?.focus() }), []);
115
+ useNodeKeys(controller, blockId, editable, sanitizeHref(url) !== null, root, keys);
116
+ return { root, link };
117
+ }
118
+
119
+ // src/extensions/embed/use-unfurl.ts
120
+ import { useEffect } from "react";
121
+
122
+ // src/extensions/embed/unfurled-patch.ts
123
+ var filled = (value) => typeof value === "string" && value.trim() !== "" ? value : void 0;
124
+ function unfurledPatch(found) {
125
+ if (typeof found !== "object" || found === null) return null;
126
+ const { title, description, image } = found;
127
+ const patch = { title: filled(title), description: filled(description), image: webAddress(image) ?? void 0 };
128
+ if (patch.title === void 0) return null;
129
+ return Object.fromEntries(Object.entries(patch).filter(([, value]) => value !== void 0));
130
+ }
131
+
132
+ // src/extensions/embed/use-unfurl.ts
133
+ var canvases = /* @__PURE__ */ new WeakMap();
134
+ function asksOf(controller) {
135
+ let asks = canvases.get(controller);
136
+ if (asks === void 0) {
137
+ asks = { asked: /* @__PURE__ */ new Set(), drawn: /* @__PURE__ */ new Map() };
138
+ canvases.set(controller, asks);
139
+ }
140
+ return asks;
141
+ }
142
+ var keyOf = (blockId, url) => JSON.stringify([blockId, url]);
143
+ function askFirstTime(controller, blockId, url) {
144
+ const { asked } = asksOf(controller);
145
+ const key = keyOf(blockId, url);
146
+ if (asked.has(key)) return false;
147
+ asked.add(key);
148
+ return true;
149
+ }
150
+ function bookmarkAt(controller, blockId, url) {
151
+ const block = blockById(controller.editor.state.doc, blockId);
152
+ return block?.type === "BOOKMARK" && str(block.attrs.url) === url ? block : void 0;
153
+ }
154
+ async function unfurlInto(controller, blockId, url, unfurl, onError) {
155
+ const { asked, drawn } = asksOf(controller);
156
+ let found;
157
+ try {
158
+ found = await unfurl(url);
159
+ } catch (cause) {
160
+ if (bookmarkAt(controller, blockId, url) && drawn.get(blockId)) onError(toError(cause));
161
+ return;
162
+ }
163
+ const patch = unfurledPatch(found);
164
+ if (patch === null) return;
165
+ const block = bookmarkAt(controller, blockId, url);
166
+ if (block === void 0 || !drawn.get(blockId) || controller.readOnly) {
167
+ asked.delete(keyOf(blockId, url));
168
+ return;
169
+ }
170
+ if (str(block.attrs.title) !== "") return;
171
+ const { editor } = controller;
172
+ editor.dispatch(editor.createTransaction().step(setAttrsStep(blockId, patch)).setMeta("addToHistory", false));
173
+ }
174
+ function useUnfurl(controller, blockId, url, unfurl) {
175
+ const { onError } = useQuoin();
176
+ useEffect(() => {
177
+ const { drawn } = asksOf(controller);
178
+ drawn.set(blockId, (drawn.get(blockId) ?? 0) + 1);
179
+ return () => {
180
+ const left = (drawn.get(blockId) ?? 1) - 1;
181
+ if (left > 0) drawn.set(blockId, left);
182
+ else drawn.delete(blockId);
183
+ };
184
+ }, [controller, blockId]);
185
+ useEffect(() => {
186
+ if (unfurl === void 0 || url === "" || !askFirstTime(controller, blockId, url)) return;
187
+ void unfurlInto(controller, blockId, url, unfurl, onError);
188
+ }, [controller, blockId, url, unfurl, onError]);
189
+ }
190
+
191
+ // src/extensions/embed/BookmarkBlock.tsx
192
+ import { jsx as jsx2 } from "react/jsx-runtime";
193
+ function BookmarkBlock({ block, controller, feature }) {
194
+ const readOnly = useReadOnly(controller);
195
+ const { attrs } = block;
196
+ const url = str(attrs.url);
197
+ const title = str(attrs.title);
198
+ const { root, link } = useLinkCardKeys(controller, block.id, url, !readOnly);
199
+ useUnfurl(controller, block.id, url, title === "" && !readOnly ? feature.unfurl : void 0);
200
+ return /* @__PURE__ */ jsx2(
201
+ "div",
202
+ {
203
+ ref: root,
204
+ role: "group",
205
+ "aria-label": feature.labels.bookmark,
206
+ contentEditable: false,
207
+ className: "quoin-bookmark",
208
+ children: /* @__PURE__ */ jsx2(
209
+ LinkCard,
210
+ {
211
+ controller,
212
+ blockId: block.id,
213
+ url,
214
+ title,
215
+ description: str(attrs.description),
216
+ image: str(attrs.image),
217
+ readOnly,
218
+ linkRef: link
219
+ }
220
+ )
221
+ }
222
+ );
223
+ }
224
+
225
+ // src/extensions/embed/EmbedBlock.tsx
226
+ import { useMemo as useMemo3 } from "react";
227
+
228
+ // src/extensions/embed/EmbedFrame.tsx
229
+ import { useCallback, useMemo as useMemo2, useRef as useRef3 } from "react";
230
+
231
+ // src/extensions/embed/embed-player.ts
232
+ var PLAYER_HEIGHTS = { spotify: 352, soundcloud: 166 };
233
+ var playerHeight = (provider) => Object.hasOwn(PLAYER_HEIGHTS, provider) ? PLAYER_HEIGHTS[provider] : void 0;
234
+
235
+ // src/extensions/embed/use-cover-press.ts
236
+ import { useState as useState2 } from "react";
237
+ function useCoverPress(controller) {
238
+ const [pressing, setPressing] = useState2(false);
239
+ useIsomorphicLayoutEffect(() => {
240
+ const doc = controller.root?.ownerDocument;
241
+ if (!pressing || doc === void 0) return;
242
+ const release = () => setPressing(false);
243
+ doc.addEventListener("click", release);
244
+ doc.addEventListener("pointercancel", release);
245
+ doc.addEventListener("dragend", release);
246
+ return () => {
247
+ doc.removeEventListener("click", release);
248
+ doc.removeEventListener("pointercancel", release);
249
+ doc.removeEventListener("dragend", release);
250
+ };
251
+ }, [controller, pressing]);
252
+ const onPress = (event) => {
253
+ if (event.button === 0) setPressing(true);
254
+ };
255
+ return { pressing, onPress };
256
+ }
257
+
258
+ // src/extensions/embed/EmbedFrame.tsx
259
+ import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
260
+ function EmbedFrame({ blockId, controller, match, labels, readOnly }) {
261
+ const root = useRef3(null);
262
+ const frame = useRef3(null);
263
+ const selected = useEditorState(
264
+ controller,
265
+ useCallback(({ selection }) => selection?.kind === "node" && selection.block === blockId, [blockId])
266
+ );
267
+ const cover = useCoverPress(controller);
268
+ const keys = useMemo2(() => ({ enter: () => void setTimeout(() => frame.current?.focus()) }), []);
269
+ useNodeKeys(controller, blockId, !readOnly, true, root, keys);
270
+ const height = playerHeight(match.provider);
271
+ return /* @__PURE__ */ jsxs2(
272
+ "div",
273
+ {
274
+ ref: root,
275
+ className: "quoin-embed-frame",
276
+ ...height === void 0 ? void 0 : { "data-player": "", style: { height } },
277
+ children: [
278
+ /* @__PURE__ */ jsx3(
279
+ "iframe",
280
+ {
281
+ ref: frame,
282
+ src: match.src,
283
+ title: labels.frameTitle(match.provider),
284
+ sandbox: EMBED_FRAME.sandbox,
285
+ loading: EMBED_FRAME.loading,
286
+ referrerPolicy: EMBED_FRAME.referrerpolicy,
287
+ tabIndex: readOnly ? void 0 : -1
288
+ }
289
+ ),
290
+ !readOnly && (!selected || cover.pressing) && /* @__PURE__ */ jsx3(
291
+ "div",
292
+ {
293
+ "aria-hidden": "true",
294
+ className: "quoin-embed-cover",
295
+ onPointerDownCapture: cover.onPress,
296
+ onMouseDown: (event) => event.preventDefault(),
297
+ onClick: () => focusNode(controller, blockId)
298
+ }
299
+ )
300
+ ]
301
+ }
302
+ );
303
+ }
304
+
305
+ // src/extensions/embed/providers.ts
306
+ function frameOf(url, provider) {
307
+ try {
308
+ return embedFrame(url, [provider])?.src ?? null;
309
+ } catch {
310
+ devWarn(`the embed provider "${provider.name}" threw, so it frames nothing.`);
311
+ return null;
312
+ }
313
+ }
314
+ function matchEmbed(url, hostProviders = []) {
315
+ for (const provider of [...hostProviders, ...coreEmbedProviders]) {
316
+ const src = frameOf(url, provider);
317
+ if (src !== null) return { provider: provider.name, src };
318
+ }
319
+ return null;
320
+ }
321
+
322
+ // src/extensions/embed/EmbedBlock.tsx
323
+ import { jsx as jsx4 } from "react/jsx-runtime";
324
+ function EmbedBlock({ block, controller, feature }) {
325
+ const { labels, providers } = feature;
326
+ const readOnly = useReadOnly(controller);
327
+ const url = str(block.attrs.url);
328
+ const match = useMemo3(() => matchEmbed(url, providers), [url, providers]);
329
+ const { root, link } = useLinkCardKeys(controller, block.id, url, !readOnly && match === null);
330
+ return /* @__PURE__ */ jsx4("div", { ref: root, role: "group", "aria-label": labels.embed, contentEditable: false, className: "quoin-embed", children: match === null ? /* @__PURE__ */ jsx4(LinkCard, { controller, blockId: block.id, url, readOnly, linkRef: link }) : /* @__PURE__ */ jsx4(EmbedFrame, { blockId: block.id, controller, match, labels, readOnly }) });
331
+ }
332
+
333
+ // src/extensions/embed/embed-labels.ts
334
+ var PROVIDER_FRAMES = {
335
+ youtube: "YouTube video",
336
+ vimeo: "Vimeo video",
337
+ loom: "Loom video",
338
+ figma: "Figma design",
339
+ codepen: "CodePen pen",
340
+ spotify: "Spotify player",
341
+ soundcloud: "SoundCloud player"
342
+ };
343
+ var englishEmbedLabels = {
344
+ embed: "Embed",
345
+ embedDescription: "A video, design or player from a link",
346
+ bookmark: "Bookmark",
347
+ bookmarkDescription: "A card that links to a page",
348
+ frameTitle: (provider) => Object.hasOwn(PROVIDER_FRAMES, provider) ? PROVIDER_FRAMES[provider] : `${provider} embed`,
349
+ linkOptions: "Link options",
350
+ keepAsLink: "Keep as link",
351
+ embedLink: "Link to embed",
352
+ bookmarkLink: "Link to bookmark",
353
+ notALink: "Enter a web address, such as https://example.com",
354
+ cannotEmbed: "This link cannot be embedded.",
355
+ bookmarkInstead: "Bookmark instead"
356
+ };
357
+ function resolveEmbedLabels(overrides) {
358
+ const given = Object.entries(overrides ?? {}).filter(([, value]) => value !== void 0);
359
+ return given.length === 0 ? englishEmbedLabels : { ...englishEmbedLabels, ...Object.fromEntries(given) };
360
+ }
361
+
362
+ // src/extensions/embed/embed-slash-items.ts
363
+ import { AppWindow, Bookmark } from "lucide-react";
364
+
365
+ // src/extensions/embed/link-request.ts
366
+ import { useCallback as useCallback2, useSyncExternalStore } from "react";
367
+ var stores = /* @__PURE__ */ new WeakMap();
368
+ function storeOf(controller) {
369
+ let store = stores.get(controller);
370
+ if (store === void 0) {
371
+ store = { request: null, listeners: /* @__PURE__ */ new Set() };
372
+ stores.set(controller, store);
373
+ }
374
+ return store;
375
+ }
376
+ function requestLink(controller, request) {
377
+ const store = storeOf(controller);
378
+ if (store.request === request) return;
379
+ store.request = request;
380
+ for (const listener of [...store.listeners]) listener();
381
+ }
382
+ function useLinkRequest(controller) {
383
+ const subscribe = useCallback2(
384
+ (notify) => {
385
+ const { listeners } = storeOf(controller);
386
+ listeners.add(notify);
387
+ return () => void listeners.delete(notify);
388
+ },
389
+ [controller]
390
+ );
391
+ const read = useCallback2(() => storeOf(controller).request, [controller]);
392
+ return useSyncExternalStore(subscribe, read, read);
393
+ }
394
+
395
+ // src/extensions/embed/embed-slash-items.ts
396
+ var embedSlashItems = (labels) => [
397
+ {
398
+ id: "embed",
399
+ title: labels.embed,
400
+ description: labels.embedDescription,
401
+ group: "embeds",
402
+ icon: AppWindow,
403
+ keywords: ["iframe", "video", "youtube", "vimeo", "loom", "figma", "codepen", "spotify", "soundcloud"],
404
+ run: ({ controller, range }) => requestLink(controller, { kind: "embed", at: range })
405
+ },
406
+ {
407
+ id: "bookmark",
408
+ title: labels.bookmark,
409
+ description: labels.bookmarkDescription,
410
+ group: "embeds",
411
+ icon: Bookmark,
412
+ keywords: ["link", "url", "card", "preview", "web"],
413
+ run: ({ controller, range }) => requestLink(controller, { kind: "bookmark", at: range })
414
+ }
415
+ ];
416
+
417
+ // src/extensions/embed/link-choice.ts
418
+ var linkChoiceKey = createPluginKey("linkChoice");
419
+ var linkChoicePlugin = {
420
+ key: linkChoiceKey,
421
+ init: () => null,
422
+ apply(tr, value, before, after) {
423
+ const pasted = tr.getMeta(linkChoiceKey.name);
424
+ if (pasted !== void 0) return pasted;
425
+ if (value === null) return null;
426
+ const moved = tr.selectionSet && !sameSelection(tr.before.selection, tr.selection);
427
+ const lineChanged = blockById(before.doc, value.block) !== blockById(after.doc, value.block);
428
+ return moved || lineChanged ? null : value;
429
+ }
430
+ };
431
+ var linkChoice = (state) => state.pluginState.get(linkChoiceKey.name) ?? null;
432
+ function onEmptyLine({ doc, schema, selection }) {
433
+ if (doc.blocks.length === 0) return true;
434
+ if (selection?.kind !== "text" || !isCollapsed(selection)) return false;
435
+ const block = blockById(doc, selection.head.block);
436
+ return block !== void 0 && isParagraph(block) && regionLength(block, primaryRegion(schema, block) ?? "") === 0;
437
+ }
438
+ var pasteLinkChoice = {
439
+ name: "link-choice",
440
+ match(text, state) {
441
+ const link = pastedLink(text);
442
+ const slice = link === null || link.rest !== "" || !onEmptyLine(state) ? null : sliceFromClipboard({ text });
443
+ if (link === null || slice === null) return null;
444
+ const paste = insertSlice(slice);
445
+ return (before, dispatch) => paste(before, dispatch && ((tr) => dispatch(tr.setMeta(linkChoiceKey.name, choiceAfter(tr, link.href)))));
446
+ }
447
+ };
448
+ function choiceAfter(tr, href) {
449
+ const { selection } = tr;
450
+ return selection?.kind === "text" ? { block: selection.head.block, href } : null;
451
+ }
452
+ function emptyParagraph(blockId) {
453
+ return (state, dispatch) => {
454
+ const line = blockById(state.doc, blockId);
455
+ if (line === void 0 || !isParagraph(line)) return false;
456
+ const tr = new Transaction(state).step(replaceBlockStep(withContent(state.schema, line, [])));
457
+ dispatch?.(tr.setSelection(startOf(tr.doc, state.schema, blockId)));
458
+ return true;
459
+ };
460
+ }
461
+ function replaceLink(blockId, block) {
462
+ return (state, dispatch) => {
463
+ const { ok, tr } = runCommands(state, [emptyParagraph(blockId), insertBlocks([block], "line", blockId)]);
464
+ if (!ok || tr === null) return false;
465
+ dispatch?.(tr);
466
+ return true;
467
+ };
468
+ }
469
+
470
+ // src/extensions/embed/LinkChoicePopover.tsx
471
+ import { useCallback as useCallback3, useEffect as useEffect2, useMemo as useMemo4, useRef as useRef4, useState as useState3 } from "react";
472
+
473
+ // src/extensions/embed/link-blocks.ts
474
+ function embedOf(href, providers) {
475
+ const match = matchEmbed(href, providers);
476
+ return match === null ? null : { type: "EMBED", attrs: { url: href, provider: match.provider } };
477
+ }
478
+ var bookmarkOf = (href) => ({ type: "BOOKMARK", attrs: { url: href } });
479
+ function typedLink(kind, typed, providers) {
480
+ const href = webAddress(linkAddress(typed));
481
+ if (href === null) return { href, block: null, problem: "notALink" };
482
+ const block = kind === "embed" ? embedOf(href, providers) : bookmarkOf(href);
483
+ return { href, block, problem: block === null ? "cannotEmbed" : null };
484
+ }
485
+
486
+ // src/extensions/embed/LinkChoicePopover.tsx
487
+ import { jsx as jsx5, jsxs as jsxs3 } from "react/jsx-runtime";
488
+ var plainKey = (event) => !event.altKey && !event.ctrlKey && !event.metaKey;
489
+ function linkEnd({ doc, schema }, blockId) {
490
+ const block = blockById(doc, blockId);
491
+ const region = block && primaryRegion(schema, block);
492
+ const end = block && region ? regionLength(block, region) : 0;
493
+ return block && region ? { block: blockId, region, from: end, to: end } : null;
494
+ }
495
+ function LinkChoicePopover({ controller, feature }) {
496
+ const readOnly = useReadOnly(controller);
497
+ const choice = useEditorState(controller, linkChoice);
498
+ const [dismissed, setDismissed] = useState3(null);
499
+ const dismiss = useCallback3(() => setDismissed(choice), [choice]);
500
+ if (readOnly || choice === null || choice === dismissed) return null;
501
+ return /* @__PURE__ */ jsx5(LinkChoiceToolbar, { controller, feature, choice, onDismiss: dismiss });
502
+ }
503
+ function useEscapeInText(controller, onEscape) {
504
+ useEffect2(() => {
505
+ const intercept = (event) => {
506
+ if (event.key !== "Escape" || !plainKey(event) || event.shiftKey) return false;
507
+ event.preventDefault();
508
+ onEscape();
509
+ return true;
510
+ };
511
+ return interceptKeys(controller, intercept);
512
+ }, [controller, onEscape]);
513
+ }
514
+ function LinkChoiceToolbar({ controller, feature: { labels, providers }, choice, onDismiss }) {
515
+ const embed = useMemo4(() => embedOf(choice.href, providers), [choice.href, providers]);
516
+ const range = useMemo4(() => linkEnd(controller.editor.state, choice.block), [controller, choice.block]);
517
+ const { anchor } = useRangeAnchor(controller, range);
518
+ const after = useMemo4(() => {
519
+ const host = range && controller.registry.get(range.block, range.region);
520
+ return host && inlineDirection(host) === "rtl" ? "left" : "right";
521
+ }, [controller, range]);
522
+ const roving = useRovingFocus();
523
+ const rovingRef = roving.ref;
524
+ const content = useRef4(null);
525
+ const ref = useCallback3(
526
+ (element) => {
527
+ content.current = element;
528
+ rovingRef(element);
529
+ },
530
+ [rovingRef]
531
+ );
532
+ const ownerDocument = useCallback3(() => controller.root?.ownerDocument ?? document, [controller]);
533
+ useFocusKey(controller, roving.focusCurrent);
534
+ useEscapeInText(controller, onDismiss);
535
+ useOutsidePress(content, ownerDocument, onDismiss);
536
+ const choose = (block) => {
537
+ if (block === null) onDismiss();
538
+ else controller.run(replaceLink(choice.block, block));
539
+ controller.focus();
540
+ };
541
+ const onKeyDown = (event) => {
542
+ if (!roving.owns(event.target) || isComposingKey(controller, event.nativeEvent)) return;
543
+ if (event.key === "Escape") onDismiss();
544
+ if (event.key === "Escape" || event.key === "Tab" && plainKey(event)) {
545
+ event.preventDefault();
546
+ controller.focus();
547
+ } else roving.onKeyDown(event);
548
+ };
549
+ const button = (label, block) => /* @__PURE__ */ jsx5(Button, { type: "button", variant: "ghost", size: "sm", onClick: () => choose(block), children: label });
550
+ return /* @__PURE__ */ jsxs3(Popover, { open: true, modal: false, children: [
551
+ /* @__PURE__ */ jsx5(PopoverAnchor, { virtualRef: anchor }),
552
+ /* @__PURE__ */ jsxs3(
553
+ PopoverContent,
554
+ {
555
+ ref,
556
+ role: "toolbar",
557
+ "aria-label": labels.linkOptions,
558
+ side: after,
559
+ align: "end",
560
+ sideOffset: 8,
561
+ className: FLOATING_TOOLBAR,
562
+ onOpenAutoFocus: preventDefault,
563
+ onCloseAutoFocus: preventDefault,
564
+ onPointerDown: keepFocus,
565
+ onMouseDown: keepFocus,
566
+ onFocus: roving.onFocus,
567
+ onKeyDown,
568
+ ...bubblePart(controller),
569
+ children: [
570
+ embed !== null && button(labels.embed, embed),
571
+ button(labels.bookmark, bookmarkOf(choice.href)),
572
+ button(labels.keepAsLink, null)
573
+ ]
574
+ }
575
+ )
576
+ ] });
577
+ }
578
+
579
+ // src/extensions/embed/LinkInputOverlay.tsx
580
+ import { useCallback as useCallback4, useEffect as useEffect3, useId, useRef as useRef5, useState as useState4 } from "react";
581
+
582
+ // src/extensions/embed/store-after-press.ts
583
+ function storeAfterPress(controller, request, block) {
584
+ const { editor } = controller;
585
+ if (controller.readOnly || controller.root === null) return;
586
+ controller.readSelectionFromDom();
587
+ const line = request.at.block;
588
+ const onLine = headBlockId(editor.state.selection) === line;
589
+ const place = onLine ? insertBlockToWrite(block, false, line) : insertBlocks([block], "stay", line);
590
+ place(editor.state, (tr) => editor.dispatch(tr));
591
+ }
592
+
593
+ // src/extensions/embed/LinkInputOverlay.tsx
594
+ import { jsx as jsx6, jsxs as jsxs4 } from "react/jsx-runtime";
595
+ function LinkInputOverlay({ controller, feature }) {
596
+ const readOnly = useReadOnly(controller);
597
+ const request = useLinkRequest(controller);
598
+ const close = useCallback4(() => requestLink(controller, null), [controller]);
599
+ useEffect3(() => {
600
+ if (readOnly) close();
601
+ }, [readOnly, close]);
602
+ if (readOnly || request === null) return null;
603
+ return /* @__PURE__ */ jsx6(LinkInputPopover, { controller, feature, request, onClose: close });
604
+ }
605
+ function LinkInputPopover({ controller, feature: { labels, providers }, request, onClose }) {
606
+ const [address, setAddress] = useState4("");
607
+ const [problem, setProblem] = useState4(null);
608
+ const messageId = useId();
609
+ const content = useRef5(null);
610
+ const { anchor, side } = useRangeAnchor(controller, request.at);
611
+ const name = request.kind === "embed" ? labels.embedLink : labels.bookmarkLink;
612
+ const typed = () => typedLink(request.kind, address, providers);
613
+ const finish = (block) => {
614
+ onClose();
615
+ if (block !== null && !controller.readOnly) controller.run(insertBlockToWrite(block, false, request.at.block));
616
+ controller.focus();
617
+ };
618
+ const submit = (event) => {
619
+ event.preventDefault();
620
+ const { block, problem: refused } = typed();
621
+ if (block === null) setProblem(refused);
622
+ else finish(block);
623
+ };
624
+ const bookmarkInstead = () => finish(typedLink("bookmark", address, providers).block);
625
+ const ownerDocument = useCallback4(() => controller.root?.ownerDocument ?? document, [controller]);
626
+ useOutsidePress(content, ownerDocument, () => {
627
+ onClose();
628
+ const { block } = typed();
629
+ if (block !== null) setTimeout(() => storeAfterPress(controller, request, block));
630
+ });
631
+ const onKeyDown = (event) => {
632
+ if (event.key !== "Tab" || event.altKey || event.ctrlKey || event.metaKey) return;
633
+ if (isComposingKey(controller, event.nativeEvent)) return;
634
+ const controls = [...event.currentTarget.querySelectorAll("input, button")];
635
+ const next = controls.indexOf(event.target) + (event.shiftKey ? -1 : 1);
636
+ event.preventDefault();
637
+ if (next >= 0 && next < controls.length) controls[next].focus();
638
+ else finish(typed().block);
639
+ };
640
+ return /* @__PURE__ */ jsxs4(Popover, { open: true, modal: false, children: [
641
+ /* @__PURE__ */ jsx6(PopoverAnchor, { virtualRef: anchor }),
642
+ /* @__PURE__ */ jsx6(
643
+ PopoverContent,
644
+ {
645
+ ref: content,
646
+ "aria-label": name,
647
+ side,
648
+ align: "start",
649
+ className: "w-80 p-2 shadow-md",
650
+ onCloseAutoFocus: preventDefault,
651
+ onEscapeKeyDown: (event) => {
652
+ event.preventDefault();
653
+ if (!isComposingKey(controller, event)) finish(null);
654
+ },
655
+ ...bubblePart(controller),
656
+ children: /* @__PURE__ */ jsxs4("form", { noValidate: true, className: "quoin-embed-ask", onSubmit: submit, onKeyDown, children: [
657
+ /* @__PURE__ */ jsx6(
658
+ Input,
659
+ {
660
+ type: "url",
661
+ className: "h-8",
662
+ "aria-label": name,
663
+ "aria-invalid": problem !== null || void 0,
664
+ "aria-describedby": problem === null ? void 0 : messageId,
665
+ value: address,
666
+ onChange: (event) => {
667
+ setAddress(event.target.value);
668
+ setProblem(null);
669
+ }
670
+ }
671
+ ),
672
+ /* @__PURE__ */ jsx6(Button, { type: "submit", size: "sm", children: request.kind === "embed" ? labels.embed : labels.bookmark }),
673
+ problem !== null && /* @__PURE__ */ jsx6("p", { id: messageId, role: "alert", className: "quoin-embed-ask-message", children: labels[problem] }),
674
+ problem === "cannotEmbed" && /* @__PURE__ */ jsx6(Button, { type: "button", variant: "outline", size: "sm", onClick: bookmarkInstead, children: labels.bookmarkInstead })
675
+ ] })
676
+ }
677
+ )
678
+ ] });
679
+ }
680
+
681
+ // src/extensions/embed/embed-extension.tsx
682
+ import { jsx as jsx7 } from "react/jsx-runtime";
683
+ function embedExtension(options = {}) {
684
+ const feature = {
685
+ labels: resolveEmbedLabels(options.labels),
686
+ providers: options.providers ?? [],
687
+ unfurl: options.unfurl
688
+ };
689
+ const EmbedRenderer = (props) => /* @__PURE__ */ jsx7(EmbedBlock, { ...props, feature });
690
+ const BookmarkRenderer = (props) => /* @__PURE__ */ jsx7(BookmarkBlock, { ...props, feature });
691
+ const ChoiceOverlay = ({ controller }) => /* @__PURE__ */ jsx7(LinkChoicePopover, { controller, feature });
692
+ const InputOverlay = ({ controller }) => /* @__PURE__ */ jsx7(LinkInputOverlay, { controller, feature });
693
+ const ui = {
694
+ slashItems: embedSlashItems(feature.labels),
695
+ renderers: { EMBED: EmbedRenderer, BOOKMARK: BookmarkRenderer },
696
+ blockNames: { EMBED: feature.labels.embed, BOOKMARK: feature.labels.bookmark },
697
+ overlays: [ChoiceOverlay, InputOverlay]
698
+ };
699
+ return defineExtension({ name: "embed", plugins: [linkChoicePlugin], pasteRules: [pasteLinkChoice], ui });
700
+ }
701
+
702
+ export {
703
+ englishEmbedLabels,
704
+ embedExtension
705
+ };
706
+ //# sourceMappingURL=chunk-SG37UBDO.js.map