@haklex/rich-renderer-image 0.0.80 → 0.0.82

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -1,703 +1,644 @@
1
- import { jsx, jsxs, Fragment } from "react/jsx-runtime";
1
+ import { C as root, S as replaceUploadArea, _ as imageState, a as editInput, c as editToolbar, d as editToolbarVisible, f as editTrigger, g as image, h as frameEditMode, i as editFieldIcon, l as editToolbarButton, m as frame, o as editPanel, r as editField, s as editPlaceholder, t as ImageRenderer, u as editToolbarButtonDanger, v as imageVisible, w as semanticClassNames } from "./ImageRenderer-BUTTEOl6.js";
2
2
  import { useRendererMode } from "@haklex/rich-editor";
3
3
  import { useLexicalComposerContext } from "@lexical/react/LexicalComposerContext";
4
- import { atom, createStore, Provider, useStore, useAtomValue, useSetAtom } from "jotai";
5
- import { useRef, useState, useEffect, useCallback } from "react";
6
- import { decodeThumbHash, computeImageMeta } from "@haklex/rich-editor/renderers";
7
- import { ActionBar, ActionButton, SegmentedControl, Popover, PopoverTrigger, PopoverPanel } from "@haklex/rich-editor-ui";
8
- import { ImageIcon, Type, Captions, Loader2, Upload, Replace, ExternalLink, Copy, Download, Trash2 } from "lucide-react";
9
- import { e as editField, s as semanticClassNames, a as editFieldIcon, b as editInput, r as replaceUploadArea, p as panelHint, c as replacePreview, d as editToolbar, f as editToolbarVisible, g as editToolbarButton, h as editPanel, i as editToolbarButtonDanger, j as editPlaceholder, I as ImageRenderer, k as editTrigger, l as root, m as image, n as imageVisible, o as loader, q as errorBadge, t as frame, u as frameEditMode, v as imageState, w as caption } from "./ImageRenderer-DyeaT08P.js";
10
- import { $isImageNode, $createImageNode } from "@haklex/rich-editor/nodes";
4
+ import { Provider, atom, createStore, useAtomValue, useSetAtom, useStore } from "jotai";
5
+ import { useCallback, useEffect, useRef, useState } from "react";
6
+ import { computeImageMeta, decodeThumbHash } from "@haklex/rich-editor/renderers";
7
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
8
+ import { ActionBar, ActionButton, Popover, PopoverPanel, PopoverTrigger, SegmentedControl } from "@haklex/rich-editor-ui";
9
+ import { Captions, Copy, Download, ExternalLink, ImageIcon, Loader2, Replace, Trash2, Type, Upload } from "lucide-react";
10
+ import { $createImageNode, $isImageNode } from "@haklex/rich-editor/nodes";
11
11
  import { useImageUpload } from "@haklex/rich-editor/plugins";
12
12
  import { $getNearestNodeFromDOMNode } from "lexical";
13
- function getCaptionText(altText, caption2) {
14
- if (caption2) return caption2;
15
- if (!altText) return void 0;
16
- if (altText.startsWith("!") || altText.startsWith("¡")) {
17
- return altText.slice(1);
18
- }
19
- return void 0;
13
+ //#region src/atoms.ts
14
+ function getCaptionText(altText, caption) {
15
+ if (caption) return caption;
16
+ if (!altText) return void 0;
17
+ if (altText.startsWith("!") || altText.startsWith("¡")) return altText.slice(1);
20
18
  }
21
- const srcAtom = atom("");
22
- const altTextAtom = atom("");
23
- const widthAtom = atom();
24
- const heightAtom = atom();
25
- const captionAtom = atom();
26
- const thumbhashAtom = atom();
27
- const accentAtom = atom();
28
- const loadStateAtom = atom("loading");
29
- const hoveringAtom = atom(false);
30
- const metaOpenAtom = atom(false);
31
- const replaceOpenAtom = atom(false);
32
- const toolbarVisibleAtom = atom(
33
- (get) => get(hoveringAtom) || get(metaOpenAtom) || get(replaceOpenAtom)
34
- );
35
- const focusCaptionOnOpenAtom = atom(false);
36
- const editSrcAtom = atom("");
37
- const editAltTextAtom = atom("");
38
- const editCaptionAtom = atom("");
39
- const replaceModeAtom = atom("upload");
40
- const replaceUrlAtom = atom("");
41
- const replacePreviewAtom = atom(null);
42
- const replaceMetaAtom = atom(null);
43
- const replaceLoadingAtom = atom(false);
44
- const replaceErrorAtom = atom(null);
45
- const wrapperRefAtom = atom({
46
- current: null
19
+ var srcAtom = atom("");
20
+ var altTextAtom = atom("");
21
+ var widthAtom = atom();
22
+ var heightAtom = atom();
23
+ var captionAtom = atom();
24
+ var thumbhashAtom = atom();
25
+ var accentAtom = atom();
26
+ var loadStateAtom = atom("loading");
27
+ var hoveringAtom = atom(false);
28
+ var metaOpenAtom = atom(false);
29
+ var replaceOpenAtom = atom(false);
30
+ var toolbarVisibleAtom = atom((get) => get(hoveringAtom) || get(metaOpenAtom) || get(replaceOpenAtom));
31
+ var focusCaptionOnOpenAtom = atom(false);
32
+ var editSrcAtom = atom("");
33
+ var editAltTextAtom = atom("");
34
+ var editCaptionAtom = atom("");
35
+ var replaceModeAtom = atom("upload");
36
+ var replaceUrlAtom = atom("");
37
+ var replacePreviewAtom = atom(null);
38
+ var replaceMetaAtom = atom(null);
39
+ var replaceLoadingAtom = atom(false);
40
+ var replaceErrorAtom = atom(null);
41
+ var wrapperRefAtom = atom({ current: null });
42
+ var fileInputRefAtom = atom({ current: null });
43
+ var placeholderUrlAtom = atom((get) => {
44
+ const thumbhash = get(thumbhashAtom);
45
+ return thumbhash ? decodeThumbHash(thumbhash) : void 0;
47
46
  });
48
- const fileInputRefAtom = atom({
49
- current: null
47
+ var captionTextAtom = atom((get) => getCaptionText(get(altTextAtom), get(captionAtom)));
48
+ var frameStyleAtom = atom((get) => {
49
+ const placeholderUrl = get(placeholderUrlAtom);
50
+ const accent = get(accentAtom);
51
+ const loadState = get(loadStateAtom);
52
+ const width = get(widthAtom);
53
+ const height = get(heightAtom);
54
+ return {
55
+ backgroundColor: loadState !== "loaded" && !placeholderUrl ? accent || "#f5f5f5" : "transparent",
56
+ backgroundImage: placeholderUrl && loadState !== "loaded" ? `url(${placeholderUrl})` : void 0,
57
+ backgroundSize: "cover",
58
+ width: width ? Math.min(width, 1200) : void 0,
59
+ maxWidth: "100%",
60
+ ...width && height ? { aspectRatio: `${width} / ${height}` } : {}
61
+ };
50
62
  });
51
- const placeholderUrlAtom = atom((get) => {
52
- const thumbhash = get(thumbhashAtom);
53
- return thumbhash ? decodeThumbHash(thumbhash) : void 0;
54
- });
55
- const captionTextAtom = atom((get) => getCaptionText(get(altTextAtom), get(captionAtom)));
56
- const frameStyleAtom = atom((get) => {
57
- const placeholderUrl = get(placeholderUrlAtom);
58
- const accent = get(accentAtom);
59
- const loadState = get(loadStateAtom);
60
- const width = get(widthAtom);
61
- const height = get(heightAtom);
62
- return {
63
- backgroundColor: loadState !== "loaded" && !placeholderUrl ? accent || "#f5f5f5" : "transparent",
64
- backgroundImage: placeholderUrl && loadState !== "loaded" ? `url(${placeholderUrl})` : void 0,
65
- backgroundSize: "cover",
66
- width: width ? Math.min(width, 1200) : void 0,
67
- maxWidth: "100%",
68
- ...width && height ? { aspectRatio: `${width} / ${height}` } : {}
69
- };
70
- });
71
- function ImageEditProvider({
72
- props,
73
- children
74
- }) {
75
- const wrapperRef = useRef(null);
76
- const fileInputRef = useRef(null);
77
- const [store] = useState(() => {
78
- const s = createStore();
79
- s.set(srcAtom, props.src);
80
- s.set(altTextAtom, props.altText);
81
- s.set(widthAtom, props.width);
82
- s.set(heightAtom, props.height);
83
- s.set(captionAtom, props.caption);
84
- s.set(thumbhashAtom, props.thumbhash);
85
- s.set(accentAtom, props.accent);
86
- s.set(editSrcAtom, props.src);
87
- s.set(editAltTextAtom, props.altText);
88
- s.set(editCaptionAtom, props.caption || "");
89
- s.set(wrapperRefAtom, wrapperRef);
90
- s.set(fileInputRefAtom, fileInputRef);
91
- s.set(loadStateAtom, props.src ? "loading" : "error");
92
- s.set(replaceOpenAtom, !props.src);
93
- return s;
94
- });
95
- useEffect(() => {
96
- store.set(srcAtom, props.src);
97
- store.set(altTextAtom, props.altText);
98
- store.set(widthAtom, props.width);
99
- store.set(heightAtom, props.height);
100
- store.set(captionAtom, props.caption);
101
- store.set(thumbhashAtom, props.thumbhash);
102
- store.set(accentAtom, props.accent);
103
- }, [
104
- store,
105
- props.src,
106
- props.altText,
107
- props.width,
108
- props.height,
109
- props.caption,
110
- props.thumbhash,
111
- props.accent
112
- ]);
113
- useEffect(() => {
114
- store.set(editSrcAtom, props.src);
115
- store.set(editAltTextAtom, props.altText);
116
- store.set(editCaptionAtom, props.caption || "");
117
- }, [store, props.src, props.altText, props.caption]);
118
- useEffect(() => {
119
- store.set(loadStateAtom, props.src ? "loading" : "error");
120
- if (!props.src) store.set(replaceOpenAtom, true);
121
- }, [store, props.src]);
122
- return /* @__PURE__ */ jsx(Provider, { store, children });
63
+ //#endregion
64
+ //#region src/ImageEditContext.tsx
65
+ function ImageEditProvider({ props, children }) {
66
+ const wrapperRef = useRef(null);
67
+ const fileInputRef = useRef(null);
68
+ const [store] = useState(() => {
69
+ const s = createStore();
70
+ s.set(srcAtom, props.src);
71
+ s.set(altTextAtom, props.altText);
72
+ s.set(widthAtom, props.width);
73
+ s.set(heightAtom, props.height);
74
+ s.set(captionAtom, props.caption);
75
+ s.set(thumbhashAtom, props.thumbhash);
76
+ s.set(accentAtom, props.accent);
77
+ s.set(editSrcAtom, props.src);
78
+ s.set(editAltTextAtom, props.altText);
79
+ s.set(editCaptionAtom, props.caption || "");
80
+ s.set(wrapperRefAtom, wrapperRef);
81
+ s.set(fileInputRefAtom, fileInputRef);
82
+ s.set(loadStateAtom, props.src ? "loading" : "error");
83
+ s.set(replaceOpenAtom, !props.src);
84
+ return s;
85
+ });
86
+ useEffect(() => {
87
+ store.set(srcAtom, props.src);
88
+ store.set(altTextAtom, props.altText);
89
+ store.set(widthAtom, props.width);
90
+ store.set(heightAtom, props.height);
91
+ store.set(captionAtom, props.caption);
92
+ store.set(thumbhashAtom, props.thumbhash);
93
+ store.set(accentAtom, props.accent);
94
+ }, [
95
+ store,
96
+ props.src,
97
+ props.altText,
98
+ props.width,
99
+ props.height,
100
+ props.caption,
101
+ props.thumbhash,
102
+ props.accent
103
+ ]);
104
+ useEffect(() => {
105
+ store.set(editSrcAtom, props.src);
106
+ store.set(editAltTextAtom, props.altText);
107
+ store.set(editCaptionAtom, props.caption || "");
108
+ }, [
109
+ store,
110
+ props.src,
111
+ props.altText,
112
+ props.caption
113
+ ]);
114
+ useEffect(() => {
115
+ store.set(loadStateAtom, props.src ? "loading" : "error");
116
+ if (!props.src) store.set(replaceOpenAtom, true);
117
+ }, [store, props.src]);
118
+ return /* @__PURE__ */ jsx(Provider, {
119
+ store,
120
+ children
121
+ });
123
122
  }
123
+ //#endregion
124
+ //#region src/useImageActions.ts
124
125
  function isSafeImageSrc(src) {
125
- return !/^(?:javascript\s*:|vbscript\s*:|data\s*:(?!image\/))/i.test(src);
126
+ return !/^(?:javascript\s*:|vbscript\s*:|data\s*:(?!image\/))/i.test(src);
126
127
  }
127
128
  function readAsDataUrl(file) {
128
- return new Promise((resolve, reject) => {
129
- const reader = new FileReader();
130
- reader.onload = () => resolve(String(reader.result));
131
- reader.onerror = () => reject(reader.error ?? new Error("File read failed"));
132
- reader.readAsDataURL(file);
133
- });
129
+ return new Promise((resolve, reject) => {
130
+ const reader = new FileReader();
131
+ reader.onload = () => resolve(String(reader.result));
132
+ reader.onerror = () => reject(reader.error ?? /* @__PURE__ */ new Error("File read failed"));
133
+ reader.readAsDataURL(file);
134
+ });
134
135
  }
135
136
  function loadImageByUrl(src) {
136
- return new Promise((resolve, reject) => {
137
- const image2 = new Image();
138
- image2.onload = () => {
139
- resolve({
140
- width: image2.naturalWidth || image2.width,
141
- height: image2.naturalHeight || image2.height
142
- });
143
- };
144
- image2.onerror = () => reject(new Error("Image load failed"));
145
- image2.src = src;
146
- });
137
+ return new Promise((resolve, reject) => {
138
+ const image = new Image();
139
+ image.onload = () => {
140
+ resolve({
141
+ width: image.naturalWidth || image.width,
142
+ height: image.naturalHeight || image.height
143
+ });
144
+ };
145
+ image.onerror = () => reject(/* @__PURE__ */ new Error("Image load failed"));
146
+ image.src = src;
147
+ });
147
148
  }
148
149
  function useImageActions() {
149
- const store = useStore();
150
- const [editor] = useLexicalComposerContext();
151
- const uploadImage = useImageUpload();
152
- const withImageNode = useCallback(
153
- (updater) => {
154
- const wrapperRef = store.get(wrapperRefAtom);
155
- if (!wrapperRef.current) return;
156
- editor.update(() => {
157
- const node = $getNearestNodeFromDOMNode(wrapperRef.current);
158
- if ($isImageNode(node)) updater(node);
159
- });
160
- },
161
- [editor, store]
162
- );
163
- const closeReplacePanel = useCallback(() => {
164
- store.set(replaceOpenAtom, false);
165
- store.set(replaceModeAtom, "upload");
166
- store.set(replaceUrlAtom, "");
167
- store.set(replacePreviewAtom, null);
168
- store.set(replaceMetaAtom, null);
169
- store.set(replaceLoadingAtom, false);
170
- store.set(replaceErrorAtom, null);
171
- }, [store]);
172
- const commitMeta = useCallback(() => {
173
- const editSrc = store.get(editSrcAtom).trim();
174
- if (!editSrc || !isSafeImageSrc(editSrc)) return;
175
- withImageNode((node) => {
176
- node.setSrc(editSrc);
177
- node.setAltText(store.get(editAltTextAtom).trim());
178
- node.setCaption(store.get(editCaptionAtom).trim() || void 0);
179
- });
180
- store.set(metaOpenAtom, false);
181
- }, [store, withImageNode]);
182
- const handleDelete = useCallback(() => {
183
- const wrapperRef = store.get(wrapperRefAtom);
184
- if (!wrapperRef.current) return;
185
- editor.update(() => {
186
- const node = $getNearestNodeFromDOMNode(wrapperRef.current);
187
- if (node) node.remove();
188
- });
189
- store.set(metaOpenAtom, false);
190
- closeReplacePanel();
191
- }, [closeReplacePanel, editor, store]);
192
- const handleOpen = useCallback(() => {
193
- const src = store.get(srcAtom);
194
- if (src) window.open(src, "_blank", "noopener,noreferrer");
195
- }, [store]);
196
- const handleDownload = useCallback(() => {
197
- const src = store.get(srcAtom);
198
- if (!src) return;
199
- const link = document.createElement("a");
200
- link.href = src;
201
- link.download = store.get(altTextAtom) || "image";
202
- link.click();
203
- }, [store]);
204
- const handleDuplicate = useCallback(() => {
205
- withImageNode((node) => {
206
- const copy = $createImageNode({
207
- src: node.getSrc(),
208
- altText: node.getAltText(),
209
- width: node.getWidth(),
210
- height: node.getHeight(),
211
- caption: node.getCaption(),
212
- thumbhash: node.getThumbhash(),
213
- accent: node.getAccent()
214
- });
215
- node.insertAfter(copy);
216
- });
217
- }, [withImageNode]);
218
- const handleReplaceFile = useCallback(
219
- async (file) => {
220
- if (!file || !file.type.startsWith("image/")) return;
221
- store.set(replaceLoadingAtom, true);
222
- store.set(replaceErrorAtom, null);
223
- try {
224
- const fallbackUploader = async (nextFile) => ({
225
- src: await readAsDataUrl(nextFile),
226
- altText: nextFile.name
227
- });
228
- const uploader = uploadImage ?? fallbackUploader;
229
- const [result, meta] = await Promise.all([uploader(file), computeImageMeta(file)]);
230
- withImageNode((node) => {
231
- node.setSrc(result.src);
232
- node.setAltText(result.altText ?? file.name);
233
- node.setDimensions(result.width ?? meta.width, result.height ?? meta.height);
234
- node.setThumbhash(result.thumbhash ?? meta.thumbhash);
235
- });
236
- closeReplacePanel();
237
- } catch {
238
- store.set(replaceErrorAtom, "Failed to replace image");
239
- } finally {
240
- store.set(replaceLoadingAtom, false);
241
- }
242
- },
243
- [closeReplacePanel, store, uploadImage, withImageNode]
244
- );
245
- const handlePreviewUrl = useCallback(async () => {
246
- const url = store.get(replaceUrlAtom).trim();
247
- if (!url || !isSafeImageSrc(url)) {
248
- store.set(replaceErrorAtom, "Invalid image URL");
249
- return;
250
- }
251
- store.set(replaceLoadingAtom, true);
252
- store.set(replaceErrorAtom, null);
253
- try {
254
- const meta = await loadImageByUrl(url);
255
- store.set(replacePreviewAtom, url);
256
- store.set(replaceMetaAtom, meta);
257
- } catch {
258
- store.set(replacePreviewAtom, null);
259
- store.set(replaceMetaAtom, null);
260
- store.set(replaceErrorAtom, "Image URL could not be loaded");
261
- } finally {
262
- store.set(replaceLoadingAtom, false);
263
- }
264
- }, [store]);
265
- const handleReplaceByUrl = useCallback(() => {
266
- const preview = store.get(replacePreviewAtom);
267
- if (!preview || !isSafeImageSrc(preview)) return;
268
- const meta = store.get(replaceMetaAtom);
269
- withImageNode((node) => {
270
- node.setSrc(preview);
271
- node.setDimensions(meta?.width, meta?.height);
272
- node.setThumbhash(void 0);
273
- });
274
- closeReplacePanel();
275
- }, [closeReplacePanel, store, withImageNode]);
276
- const handleReplaceOpenChange = useCallback(
277
- (nextOpen) => {
278
- store.set(replaceOpenAtom, nextOpen);
279
- if (!nextOpen) closeReplacePanel();
280
- },
281
- [closeReplacePanel, store]
282
- );
283
- return {
284
- commitMeta,
285
- closeReplacePanel,
286
- handleReplaceFile,
287
- handlePreviewUrl,
288
- handleReplaceByUrl,
289
- handleReplaceOpenChange,
290
- handleOpen,
291
- handleDuplicate,
292
- handleDownload,
293
- handleDelete
294
- };
150
+ const store = useStore();
151
+ const [editor] = useLexicalComposerContext();
152
+ const uploadImage = useImageUpload();
153
+ const withImageNode = useCallback((updater) => {
154
+ const wrapperRef = store.get(wrapperRefAtom);
155
+ if (!wrapperRef.current) return;
156
+ editor.update(() => {
157
+ const node = $getNearestNodeFromDOMNode(wrapperRef.current);
158
+ if ($isImageNode(node)) updater(node);
159
+ });
160
+ }, [editor, store]);
161
+ const closeReplacePanel = useCallback(() => {
162
+ store.set(replaceOpenAtom, false);
163
+ store.set(replaceModeAtom, "upload");
164
+ store.set(replaceUrlAtom, "");
165
+ store.set(replacePreviewAtom, null);
166
+ store.set(replaceMetaAtom, null);
167
+ store.set(replaceLoadingAtom, false);
168
+ store.set(replaceErrorAtom, null);
169
+ }, [store]);
170
+ const commitMeta = useCallback(() => {
171
+ const editSrc = store.get(editSrcAtom).trim();
172
+ if (!editSrc || !isSafeImageSrc(editSrc)) return;
173
+ withImageNode((node) => {
174
+ node.setSrc(editSrc);
175
+ node.setAltText(store.get(editAltTextAtom).trim());
176
+ node.setCaption(store.get(editCaptionAtom).trim() || void 0);
177
+ });
178
+ store.set(metaOpenAtom, false);
179
+ }, [store, withImageNode]);
180
+ const handleDelete = useCallback(() => {
181
+ const wrapperRef = store.get(wrapperRefAtom);
182
+ if (!wrapperRef.current) return;
183
+ editor.update(() => {
184
+ const node = $getNearestNodeFromDOMNode(wrapperRef.current);
185
+ if (node) node.remove();
186
+ });
187
+ store.set(metaOpenAtom, false);
188
+ closeReplacePanel();
189
+ }, [
190
+ closeReplacePanel,
191
+ editor,
192
+ store
193
+ ]);
194
+ const handleOpen = useCallback(() => {
195
+ const src = store.get(srcAtom);
196
+ if (src) window.open(src, "_blank", "noopener,noreferrer");
197
+ }, [store]);
198
+ const handleDownload = useCallback(() => {
199
+ const src = store.get(srcAtom);
200
+ if (!src) return;
201
+ const link = document.createElement("a");
202
+ link.href = src;
203
+ link.download = store.get(altTextAtom) || "image";
204
+ link.click();
205
+ }, [store]);
206
+ const handleDuplicate = useCallback(() => {
207
+ withImageNode((node) => {
208
+ const copy = $createImageNode({
209
+ src: node.getSrc(),
210
+ altText: node.getAltText(),
211
+ width: node.getWidth(),
212
+ height: node.getHeight(),
213
+ caption: node.getCaption(),
214
+ thumbhash: node.getThumbhash(),
215
+ accent: node.getAccent()
216
+ });
217
+ node.insertAfter(copy);
218
+ });
219
+ }, [withImageNode]);
220
+ return {
221
+ commitMeta,
222
+ closeReplacePanel,
223
+ handleReplaceFile: useCallback(async (file) => {
224
+ if (!file || !file.type.startsWith("image/")) return;
225
+ store.set(replaceLoadingAtom, true);
226
+ store.set(replaceErrorAtom, null);
227
+ try {
228
+ const fallbackUploader = async (nextFile) => ({
229
+ src: await readAsDataUrl(nextFile),
230
+ altText: nextFile.name
231
+ });
232
+ const uploader = uploadImage ?? fallbackUploader;
233
+ const [result, meta] = await Promise.all([uploader(file), computeImageMeta(file)]);
234
+ withImageNode((node) => {
235
+ node.setSrc(result.src);
236
+ node.setAltText(result.altText ?? file.name);
237
+ node.setDimensions(result.width ?? meta.width, result.height ?? meta.height);
238
+ node.setThumbhash(result.thumbhash ?? meta.thumbhash);
239
+ });
240
+ closeReplacePanel();
241
+ } catch {
242
+ store.set(replaceErrorAtom, "Failed to replace image");
243
+ } finally {
244
+ store.set(replaceLoadingAtom, false);
245
+ }
246
+ }, [
247
+ closeReplacePanel,
248
+ store,
249
+ uploadImage,
250
+ withImageNode
251
+ ]),
252
+ handlePreviewUrl: useCallback(async () => {
253
+ const url = store.get(replaceUrlAtom).trim();
254
+ if (!url || !isSafeImageSrc(url)) {
255
+ store.set(replaceErrorAtom, "Invalid image URL");
256
+ return;
257
+ }
258
+ store.set(replaceLoadingAtom, true);
259
+ store.set(replaceErrorAtom, null);
260
+ try {
261
+ const meta = await loadImageByUrl(url);
262
+ store.set(replacePreviewAtom, url);
263
+ store.set(replaceMetaAtom, meta);
264
+ } catch {
265
+ store.set(replacePreviewAtom, null);
266
+ store.set(replaceMetaAtom, null);
267
+ store.set(replaceErrorAtom, "Image URL could not be loaded");
268
+ } finally {
269
+ store.set(replaceLoadingAtom, false);
270
+ }
271
+ }, [store]),
272
+ handleReplaceByUrl: useCallback(() => {
273
+ const preview = store.get(replacePreviewAtom);
274
+ if (!preview || !isSafeImageSrc(preview)) return;
275
+ const meta = store.get(replaceMetaAtom);
276
+ withImageNode((node) => {
277
+ node.setSrc(preview);
278
+ node.setDimensions(meta?.width, meta?.height);
279
+ node.setThumbhash(void 0);
280
+ });
281
+ closeReplacePanel();
282
+ }, [
283
+ closeReplacePanel,
284
+ store,
285
+ withImageNode
286
+ ]),
287
+ handleReplaceOpenChange: useCallback((nextOpen) => {
288
+ store.set(replaceOpenAtom, nextOpen);
289
+ if (!nextOpen) closeReplacePanel();
290
+ }, [closeReplacePanel, store]),
291
+ handleOpen,
292
+ handleDuplicate,
293
+ handleDownload,
294
+ handleDelete
295
+ };
295
296
  }
297
+ //#endregion
298
+ //#region src/EditMetaPopover.tsx
296
299
  function EditMetaPopover() {
297
- const editSrc = useAtomValue(editSrcAtom);
298
- const setEditSrc = useSetAtom(editSrcAtom);
299
- const editAltText = useAtomValue(editAltTextAtom);
300
- const setEditAltText = useSetAtom(editAltTextAtom);
301
- const editCaption = useAtomValue(editCaptionAtom);
302
- const setEditCaption = useSetAtom(editCaptionAtom);
303
- const setMetaOpen = useSetAtom(metaOpenAtom);
304
- const { commitMeta } = useImageActions();
305
- const captionInputRef = useRef(null);
306
- const focusCaptionOnOpen = useAtomValue(focusCaptionOnOpenAtom);
307
- const setFocusCaptionOnOpen = useSetAtom(focusCaptionOnOpenAtom);
308
- useEffect(() => {
309
- if (focusCaptionOnOpen && captionInputRef.current) {
310
- captionInputRef.current.focus();
311
- captionInputRef.current.select();
312
- setFocusCaptionOnOpen(false);
313
- }
314
- }, [focusCaptionOnOpen, setFocusCaptionOnOpen]);
315
- return /* @__PURE__ */ jsxs(Fragment, { children: [
316
- /* @__PURE__ */ jsxs("div", { className: `${editField} ${semanticClassNames.editField}`, children: [
317
- /* @__PURE__ */ jsx(
318
- ImageIcon,
319
- {
320
- className: `${editFieldIcon} ${semanticClassNames.editFieldIcon}`,
321
- size: 14
322
- }
323
- ),
324
- /* @__PURE__ */ jsx(
325
- "input",
326
- {
327
- className: `${editInput} ${semanticClassNames.editInput}`,
328
- placeholder: "Image URL",
329
- type: "url",
330
- value: editSrc,
331
- onChange: (event) => setEditSrc(event.target.value)
332
- }
333
- )
334
- ] }),
335
- /* @__PURE__ */ jsxs("div", { className: `${editField} ${semanticClassNames.editField}`, children: [
336
- /* @__PURE__ */ jsx(
337
- Type,
338
- {
339
- className: `${editFieldIcon} ${semanticClassNames.editFieldIcon}`,
340
- size: 14
341
- }
342
- ),
343
- /* @__PURE__ */ jsx(
344
- "input",
345
- {
346
- className: `${editInput} ${semanticClassNames.editInput}`,
347
- placeholder: "Alt text",
348
- type: "text",
349
- value: editAltText,
350
- onChange: (event) => setEditAltText(event.target.value)
351
- }
352
- )
353
- ] }),
354
- /* @__PURE__ */ jsxs("div", { className: `${editField} ${semanticClassNames.editField}`, children: [
355
- /* @__PURE__ */ jsx(
356
- Captions,
357
- {
358
- className: `${editFieldIcon} ${semanticClassNames.editFieldIcon}`,
359
- size: 14
360
- }
361
- ),
362
- /* @__PURE__ */ jsx(
363
- "input",
364
- {
365
- className: `${editInput} ${semanticClassNames.editInput}`,
366
- placeholder: "Caption (optional)",
367
- ref: captionInputRef,
368
- type: "text",
369
- value: editCaption,
370
- onChange: (event) => setEditCaption(event.target.value)
371
- }
372
- )
373
- ] }),
374
- /* @__PURE__ */ jsxs(ActionBar, { children: [
375
- /* @__PURE__ */ jsx(ActionButton, { onClick: () => setMetaOpen(false), children: "Close" }),
376
- /* @__PURE__ */ jsx(ActionButton, { variant: "accent", onClick: commitMeta, children: "Save" })
377
- ] })
378
- ] });
300
+ const editSrc = useAtomValue(editSrcAtom);
301
+ const setEditSrc = useSetAtom(editSrcAtom);
302
+ const editAltText = useAtomValue(editAltTextAtom);
303
+ const setEditAltText = useSetAtom(editAltTextAtom);
304
+ const editCaption = useAtomValue(editCaptionAtom);
305
+ const setEditCaption = useSetAtom(editCaptionAtom);
306
+ const setMetaOpen = useSetAtom(metaOpenAtom);
307
+ const { commitMeta } = useImageActions();
308
+ const captionInputRef = useRef(null);
309
+ const focusCaptionOnOpen = useAtomValue(focusCaptionOnOpenAtom);
310
+ const setFocusCaptionOnOpen = useSetAtom(focusCaptionOnOpenAtom);
311
+ useEffect(() => {
312
+ if (focusCaptionOnOpen && captionInputRef.current) {
313
+ captionInputRef.current.focus();
314
+ captionInputRef.current.select();
315
+ setFocusCaptionOnOpen(false);
316
+ }
317
+ }, [focusCaptionOnOpen, setFocusCaptionOnOpen]);
318
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
319
+ /* @__PURE__ */ jsxs("div", {
320
+ className: `${editField} ${semanticClassNames.editField}`,
321
+ children: [/* @__PURE__ */ jsx(ImageIcon, {
322
+ className: `${editFieldIcon} ${semanticClassNames.editFieldIcon}`,
323
+ size: 14
324
+ }), /* @__PURE__ */ jsx("input", {
325
+ className: `${editInput} ${semanticClassNames.editInput}`,
326
+ placeholder: "Image URL",
327
+ type: "url",
328
+ value: editSrc,
329
+ onChange: (event) => setEditSrc(event.target.value)
330
+ })]
331
+ }),
332
+ /* @__PURE__ */ jsxs("div", {
333
+ className: `${editField} ${semanticClassNames.editField}`,
334
+ children: [/* @__PURE__ */ jsx(Type, {
335
+ className: `${editFieldIcon} ${semanticClassNames.editFieldIcon}`,
336
+ size: 14
337
+ }), /* @__PURE__ */ jsx("input", {
338
+ className: `${editInput} ${semanticClassNames.editInput}`,
339
+ placeholder: "Alt text",
340
+ type: "text",
341
+ value: editAltText,
342
+ onChange: (event) => setEditAltText(event.target.value)
343
+ })]
344
+ }),
345
+ /* @__PURE__ */ jsxs("div", {
346
+ className: `${editField} ${semanticClassNames.editField}`,
347
+ children: [/* @__PURE__ */ jsx(Captions, {
348
+ className: `${editFieldIcon} ${semanticClassNames.editFieldIcon}`,
349
+ size: 14
350
+ }), /* @__PURE__ */ jsx("input", {
351
+ className: `${editInput} ${semanticClassNames.editInput}`,
352
+ placeholder: "Caption (optional)",
353
+ ref: captionInputRef,
354
+ type: "text",
355
+ value: editCaption,
356
+ onChange: (event) => setEditCaption(event.target.value)
357
+ })]
358
+ }),
359
+ /* @__PURE__ */ jsxs(ActionBar, { children: [/* @__PURE__ */ jsx(ActionButton, {
360
+ onClick: () => setMetaOpen(false),
361
+ children: "Close"
362
+ }), /* @__PURE__ */ jsx(ActionButton, {
363
+ variant: "accent",
364
+ onClick: commitMeta,
365
+ children: "Save"
366
+ })] })
367
+ ] });
379
368
  }
380
- const replaceModeItems = [
381
- { value: "upload", label: "Upload" },
382
- { value: "url", label: "URL" }
383
- ];
369
+ //#endregion
370
+ //#region src/ReplacePanel.tsx
371
+ var replaceModeItems = [{
372
+ value: "upload",
373
+ label: "Upload"
374
+ }, {
375
+ value: "url",
376
+ label: "URL"
377
+ }];
384
378
  function ReplacePanel() {
385
- const replaceMode = useAtomValue(replaceModeAtom);
386
- const setReplaceMode = useSetAtom(replaceModeAtom);
387
- const replaceUrl = useAtomValue(replaceUrlAtom);
388
- const setReplaceUrl = useSetAtom(replaceUrlAtom);
389
- const replacePreview$1 = useAtomValue(replacePreviewAtom);
390
- const replaceError = useAtomValue(replaceErrorAtom);
391
- const replaceLoading = useAtomValue(replaceLoadingAtom);
392
- const fileInputRef = useAtomValue(fileInputRefAtom);
393
- const { handleReplaceFile, handlePreviewUrl, handleReplaceByUrl } = useImageActions();
394
- return /* @__PURE__ */ jsxs(Fragment, { children: [
395
- /* @__PURE__ */ jsx(
396
- SegmentedControl,
397
- {
398
- fullWidth: true,
399
- items: replaceModeItems,
400
- value: replaceMode,
401
- onChange: setReplaceMode
402
- }
403
- ),
404
- replaceMode === "upload" ? /* @__PURE__ */ jsxs(Fragment, { children: [
405
- /* @__PURE__ */ jsx(
406
- "input",
407
- {
408
- accept: "image/*",
409
- ref: fileInputRef,
410
- style: { display: "none" },
411
- type: "file",
412
- onChange: (event) => {
413
- const file = event.currentTarget.files?.[0] ?? null;
414
- void handleReplaceFile(file);
415
- event.currentTarget.value = "";
416
- }
417
- }
418
- ),
419
- /* @__PURE__ */ jsx(
420
- "button",
421
- {
422
- className: `${replaceUploadArea} ${semanticClassNames.replaceUploadArea}`,
423
- type: "button",
424
- onClick: () => fileInputRef.current?.click(),
425
- children: replaceLoading ? /* @__PURE__ */ jsxs(Fragment, { children: [
426
- /* @__PURE__ */ jsx(Loader2, { size: 16 }),
427
- /* @__PURE__ */ jsx("span", { children: "Uploading image..." })
428
- ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
429
- /* @__PURE__ */ jsx(Upload, { size: 16 }),
430
- /* @__PURE__ */ jsx("span", { children: "Click to upload image" })
431
- ] })
432
- }
433
- )
434
- ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
435
- /* @__PURE__ */ jsxs("div", { className: `${editField} ${semanticClassNames.editField}`, children: [
436
- /* @__PURE__ */ jsx(
437
- ImageIcon,
438
- {
439
- className: `${editFieldIcon} ${semanticClassNames.editFieldIcon}`,
440
- size: 14
441
- }
442
- ),
443
- /* @__PURE__ */ jsx(
444
- "input",
445
- {
446
- className: `${editInput} ${semanticClassNames.editInput}`,
447
- placeholder: "https://example.com/image.jpg",
448
- type: "url",
449
- value: replaceUrl,
450
- onChange: (event) => setReplaceUrl(event.target.value),
451
- onKeyDown: (event) => {
452
- if (event.key === "Enter") {
453
- event.preventDefault();
454
- handlePreviewUrl();
455
- }
456
- }
457
- }
458
- )
459
- ] }),
460
- /* @__PURE__ */ jsxs(ActionBar, { children: [
461
- /* @__PURE__ */ jsx(
462
- ActionButton,
463
- {
464
- disabled: replaceLoading || !replaceUrl.trim(),
465
- onClick: handlePreviewUrl,
466
- children: "Preview"
467
- }
468
- ),
469
- /* @__PURE__ */ jsx(ActionButton, { disabled: !replacePreview$1, variant: "accent", onClick: handleReplaceByUrl, children: "Apply" })
470
- ] })
471
- ] }),
472
- replaceError && /* @__PURE__ */ jsx("span", { className: `${panelHint} ${semanticClassNames.panelHint}`, children: replaceError }),
473
- replacePreview$1 && /* @__PURE__ */ jsx("div", { className: `${replacePreview} ${semanticClassNames.replacePreview}`, children: /* @__PURE__ */ jsx("img", { alt: "Replace preview", src: replacePreview$1 }) })
474
- ] });
379
+ const replaceMode = useAtomValue(replaceModeAtom);
380
+ const setReplaceMode = useSetAtom(replaceModeAtom);
381
+ const replaceUrl = useAtomValue(replaceUrlAtom);
382
+ const setReplaceUrl = useSetAtom(replaceUrlAtom);
383
+ const replacePreview$1 = useAtomValue(replacePreviewAtom);
384
+ const replaceError = useAtomValue(replaceErrorAtom);
385
+ const replaceLoading = useAtomValue(replaceLoadingAtom);
386
+ const fileInputRef = useAtomValue(fileInputRefAtom);
387
+ const { handleReplaceFile, handlePreviewUrl, handleReplaceByUrl } = useImageActions();
388
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
389
+ /* @__PURE__ */ jsx(SegmentedControl, {
390
+ fullWidth: true,
391
+ items: replaceModeItems,
392
+ value: replaceMode,
393
+ onChange: setReplaceMode
394
+ }),
395
+ replaceMode === "upload" ? /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("input", {
396
+ accept: "image/*",
397
+ ref: fileInputRef,
398
+ style: { display: "none" },
399
+ type: "file",
400
+ onChange: (event) => {
401
+ handleReplaceFile(event.currentTarget.files?.[0] ?? null);
402
+ event.currentTarget.value = "";
403
+ }
404
+ }), /* @__PURE__ */ jsx("button", {
405
+ className: `${replaceUploadArea} ${semanticClassNames.replaceUploadArea}`,
406
+ type: "button",
407
+ onClick: () => fileInputRef.current?.click(),
408
+ children: replaceLoading ? /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(Loader2, { size: 16 }), /* @__PURE__ */ jsx("span", { children: "Uploading image..." })] }) : /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(Upload, { size: 16 }), /* @__PURE__ */ jsx("span", { children: "Click to upload image" })] })
409
+ })] }) : /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsxs("div", {
410
+ className: `${editField} ${semanticClassNames.editField}`,
411
+ children: [/* @__PURE__ */ jsx(ImageIcon, {
412
+ className: `${editFieldIcon} ${semanticClassNames.editFieldIcon}`,
413
+ size: 14
414
+ }), /* @__PURE__ */ jsx("input", {
415
+ className: `${editInput} ${semanticClassNames.editInput}`,
416
+ placeholder: "https://example.com/image.jpg",
417
+ type: "url",
418
+ value: replaceUrl,
419
+ onChange: (event) => setReplaceUrl(event.target.value),
420
+ onKeyDown: (event) => {
421
+ if (event.key === "Enter") {
422
+ event.preventDefault();
423
+ handlePreviewUrl();
424
+ }
425
+ }
426
+ })]
427
+ }), /* @__PURE__ */ jsxs(ActionBar, { children: [/* @__PURE__ */ jsx(ActionButton, {
428
+ disabled: replaceLoading || !replaceUrl.trim(),
429
+ onClick: handlePreviewUrl,
430
+ children: "Preview"
431
+ }), /* @__PURE__ */ jsx(ActionButton, {
432
+ disabled: !replacePreview$1,
433
+ variant: "accent",
434
+ onClick: handleReplaceByUrl,
435
+ children: "Apply"
436
+ })] })] }),
437
+ replaceError && /* @__PURE__ */ jsx("span", {
438
+ className: `_1n94osfw ${semanticClassNames.panelHint}`,
439
+ children: replaceError
440
+ }),
441
+ replacePreview$1 && /* @__PURE__ */ jsx("div", {
442
+ className: `_1n94osfv ${semanticClassNames.replacePreview}`,
443
+ children: /* @__PURE__ */ jsx("img", {
444
+ alt: "Replace preview",
445
+ src: replacePreview$1
446
+ })
447
+ })
448
+ ] });
475
449
  }
450
+ //#endregion
451
+ //#region src/ImageEditToolbar.tsx
476
452
  function ImageEditToolbar() {
477
- const toolbarVisible = useAtomValue(toolbarVisibleAtom);
478
- const metaOpen = useAtomValue(metaOpenAtom);
479
- const setMetaOpen = useSetAtom(metaOpenAtom);
480
- const replaceOpen = useAtomValue(replaceOpenAtom);
481
- const { handleReplaceOpenChange, handleOpen, handleDuplicate, handleDownload, handleDelete } = useImageActions();
482
- return /* @__PURE__ */ jsxs(
483
- "div",
484
- {
485
- className: `${editToolbar} ${semanticClassNames.editToolbar} ${toolbarVisible ? `${editToolbarVisible} ${semanticClassNames.editToolbarVisible}` : ""}`,
486
- children: [
487
- /* @__PURE__ */ jsxs(
488
- Popover,
489
- {
490
- open: metaOpen,
491
- onOpenChange: (nextOpen) => {
492
- setMetaOpen(nextOpen);
493
- if (nextOpen) handleReplaceOpenChange(false);
494
- },
495
- children: [
496
- /* @__PURE__ */ jsx(
497
- PopoverTrigger,
498
- {
499
- className: `${editToolbarButton} ${semanticClassNames.editToolbarButton}`,
500
- title: "Edit details",
501
- children: /* @__PURE__ */ jsx(Type, { size: 14 })
502
- }
503
- ),
504
- /* @__PURE__ */ jsx(PopoverPanel, { className: editPanel, side: "bottom", sideOffset: 8, children: /* @__PURE__ */ jsx(EditMetaPopover, {}) })
505
- ]
506
- }
507
- ),
508
- /* @__PURE__ */ jsxs(
509
- Popover,
510
- {
511
- open: replaceOpen,
512
- onOpenChange: (nextOpen) => {
513
- handleReplaceOpenChange(nextOpen);
514
- if (nextOpen) setMetaOpen(false);
515
- },
516
- children: [
517
- /* @__PURE__ */ jsx(
518
- PopoverTrigger,
519
- {
520
- className: `${editToolbarButton} ${semanticClassNames.editToolbarButton}`,
521
- title: "Replace image",
522
- children: /* @__PURE__ */ jsx(Replace, { size: 14 })
523
- }
524
- ),
525
- /* @__PURE__ */ jsx(PopoverPanel, { className: editPanel, side: "bottom", sideOffset: 8, children: /* @__PURE__ */ jsx(ReplacePanel, {}) })
526
- ]
527
- }
528
- ),
529
- /* @__PURE__ */ jsx(
530
- "button",
531
- {
532
- className: `${editToolbarButton} ${semanticClassNames.editToolbarButton}`,
533
- title: "Open source",
534
- type: "button",
535
- onClick: handleOpen,
536
- onMouseDown: (e) => {
537
- e.preventDefault();
538
- e.stopPropagation();
539
- },
540
- children: /* @__PURE__ */ jsx(ExternalLink, { size: 14 })
541
- }
542
- ),
543
- /* @__PURE__ */ jsx(
544
- "button",
545
- {
546
- className: `${editToolbarButton} ${semanticClassNames.editToolbarButton}`,
547
- title: "Duplicate",
548
- type: "button",
549
- onClick: handleDuplicate,
550
- onMouseDown: (e) => {
551
- e.preventDefault();
552
- e.stopPropagation();
553
- },
554
- children: /* @__PURE__ */ jsx(Copy, { size: 14 })
555
- }
556
- ),
557
- /* @__PURE__ */ jsx(
558
- "button",
559
- {
560
- className: `${editToolbarButton} ${semanticClassNames.editToolbarButton}`,
561
- title: "Download",
562
- type: "button",
563
- onClick: handleDownload,
564
- onMouseDown: (e) => {
565
- e.preventDefault();
566
- e.stopPropagation();
567
- },
568
- children: /* @__PURE__ */ jsx(Download, { size: 14 })
569
- }
570
- ),
571
- /* @__PURE__ */ jsx(
572
- "button",
573
- {
574
- className: `${editToolbarButton} ${semanticClassNames.editToolbarButton} ${editToolbarButtonDanger} ${semanticClassNames.editToolbarButtonDanger}`,
575
- title: "Remove image",
576
- type: "button",
577
- onClick: handleDelete,
578
- onMouseDown: (e) => {
579
- e.preventDefault();
580
- e.stopPropagation();
581
- },
582
- children: /* @__PURE__ */ jsx(Trash2, { size: 14 })
583
- }
584
- )
585
- ]
586
- }
587
- );
453
+ const toolbarVisible = useAtomValue(toolbarVisibleAtom);
454
+ const metaOpen = useAtomValue(metaOpenAtom);
455
+ const setMetaOpen = useSetAtom(metaOpenAtom);
456
+ const replaceOpen = useAtomValue(replaceOpenAtom);
457
+ const { handleReplaceOpenChange, handleOpen, handleDuplicate, handleDownload, handleDelete } = useImageActions();
458
+ return /* @__PURE__ */ jsxs("div", {
459
+ className: `${editToolbar} ${semanticClassNames.editToolbar} ${toolbarVisible ? `${editToolbarVisible} ${semanticClassNames.editToolbarVisible}` : ""}`,
460
+ children: [
461
+ /* @__PURE__ */ jsxs(Popover, {
462
+ open: metaOpen,
463
+ onOpenChange: (nextOpen) => {
464
+ setMetaOpen(nextOpen);
465
+ if (nextOpen) handleReplaceOpenChange(false);
466
+ },
467
+ children: [/* @__PURE__ */ jsx(PopoverTrigger, {
468
+ className: `${editToolbarButton} ${semanticClassNames.editToolbarButton}`,
469
+ title: "Edit details",
470
+ children: /* @__PURE__ */ jsx(Type, { size: 14 })
471
+ }), /* @__PURE__ */ jsx(PopoverPanel, {
472
+ className: editPanel,
473
+ side: "bottom",
474
+ sideOffset: 8,
475
+ children: /* @__PURE__ */ jsx(EditMetaPopover, {})
476
+ })]
477
+ }),
478
+ /* @__PURE__ */ jsxs(Popover, {
479
+ open: replaceOpen,
480
+ onOpenChange: (nextOpen) => {
481
+ handleReplaceOpenChange(nextOpen);
482
+ if (nextOpen) setMetaOpen(false);
483
+ },
484
+ children: [/* @__PURE__ */ jsx(PopoverTrigger, {
485
+ className: `${editToolbarButton} ${semanticClassNames.editToolbarButton}`,
486
+ title: "Replace image",
487
+ children: /* @__PURE__ */ jsx(Replace, { size: 14 })
488
+ }), /* @__PURE__ */ jsx(PopoverPanel, {
489
+ className: editPanel,
490
+ side: "bottom",
491
+ sideOffset: 8,
492
+ children: /* @__PURE__ */ jsx(ReplacePanel, {})
493
+ })]
494
+ }),
495
+ /* @__PURE__ */ jsx("button", {
496
+ className: `${editToolbarButton} ${semanticClassNames.editToolbarButton}`,
497
+ title: "Open source",
498
+ type: "button",
499
+ onClick: handleOpen,
500
+ onMouseDown: (e) => {
501
+ e.preventDefault();
502
+ e.stopPropagation();
503
+ },
504
+ children: /* @__PURE__ */ jsx(ExternalLink, { size: 14 })
505
+ }),
506
+ /* @__PURE__ */ jsx("button", {
507
+ className: `${editToolbarButton} ${semanticClassNames.editToolbarButton}`,
508
+ title: "Duplicate",
509
+ type: "button",
510
+ onClick: handleDuplicate,
511
+ onMouseDown: (e) => {
512
+ e.preventDefault();
513
+ e.stopPropagation();
514
+ },
515
+ children: /* @__PURE__ */ jsx(Copy, { size: 14 })
516
+ }),
517
+ /* @__PURE__ */ jsx("button", {
518
+ className: `${editToolbarButton} ${semanticClassNames.editToolbarButton}`,
519
+ title: "Download",
520
+ type: "button",
521
+ onClick: handleDownload,
522
+ onMouseDown: (e) => {
523
+ e.preventDefault();
524
+ e.stopPropagation();
525
+ },
526
+ children: /* @__PURE__ */ jsx(Download, { size: 14 })
527
+ }),
528
+ /* @__PURE__ */ jsx("button", {
529
+ className: `${editToolbarButton} ${semanticClassNames.editToolbarButton} ${editToolbarButtonDanger} ${semanticClassNames.editToolbarButtonDanger}`,
530
+ title: "Remove image",
531
+ type: "button",
532
+ onClick: handleDelete,
533
+ onMouseDown: (e) => {
534
+ e.preventDefault();
535
+ e.stopPropagation();
536
+ },
537
+ children: /* @__PURE__ */ jsx(Trash2, { size: 14 })
538
+ })
539
+ ]
540
+ });
588
541
  }
542
+ //#endregion
543
+ //#region src/ReplacePopover.tsx
589
544
  function ReplacePopover() {
590
- const replaceOpen = useAtomValue(replaceOpenAtom);
591
- const { handleReplaceOpenChange } = useImageActions();
592
- return /* @__PURE__ */ jsxs(Popover, { open: replaceOpen, onOpenChange: handleReplaceOpenChange, children: [
593
- /* @__PURE__ */ jsxs(
594
- PopoverTrigger,
595
- {
596
- className: `${editPlaceholder} ${semanticClassNames.editPlaceholder}`,
597
- children: [
598
- /* @__PURE__ */ jsx(ImageIcon, { size: 24 }),
599
- /* @__PURE__ */ jsx("span", { children: "Click to add image" })
600
- ]
601
- }
602
- ),
603
- /* @__PURE__ */ jsx(PopoverPanel, { className: editPanel, side: "bottom", sideOffset: 8, children: /* @__PURE__ */ jsx(ReplacePanel, {}) })
604
- ] });
545
+ const replaceOpen = useAtomValue(replaceOpenAtom);
546
+ const { handleReplaceOpenChange } = useImageActions();
547
+ return /* @__PURE__ */ jsxs(Popover, {
548
+ open: replaceOpen,
549
+ onOpenChange: handleReplaceOpenChange,
550
+ children: [/* @__PURE__ */ jsxs(PopoverTrigger, {
551
+ className: `${editPlaceholder} ${semanticClassNames.editPlaceholder}`,
552
+ children: [/* @__PURE__ */ jsx(ImageIcon, { size: 24 }), /* @__PURE__ */ jsx("span", { children: "Click to add image" })]
553
+ }), /* @__PURE__ */ jsx(PopoverPanel, {
554
+ className: editPanel,
555
+ side: "bottom",
556
+ sideOffset: 8,
557
+ children: /* @__PURE__ */ jsx(ReplacePanel, {})
558
+ })]
559
+ });
605
560
  }
606
- const frameStateSemanticClass = {
607
- loading: semanticClassNames.frameLoading,
608
- loaded: semanticClassNames.frameLoaded,
609
- error: semanticClassNames.frameError
561
+ //#endregion
562
+ //#region src/ImageEditRenderer.tsx
563
+ var frameStateSemanticClass = {
564
+ loading: semanticClassNames.frameLoading,
565
+ loaded: semanticClassNames.frameLoaded,
566
+ error: semanticClassNames.frameError
610
567
  };
611
568
  function ImageEditRenderer(props) {
612
- const mode = useRendererMode();
613
- if (mode !== "editor") {
614
- return /* @__PURE__ */ jsx(ImageRenderer, { ...props });
615
- }
616
- return /* @__PURE__ */ jsx(ImageEditRendererInner, { ...props });
569
+ if (useRendererMode() !== "editor") return /* @__PURE__ */ jsx(ImageRenderer, { ...props });
570
+ return /* @__PURE__ */ jsx(ImageEditRendererInner, { ...props });
617
571
  }
618
572
  function ImageEditRendererInner(props) {
619
- const [editor] = useLexicalComposerContext();
620
- const editable = editor.isEditable();
621
- if (!editable) {
622
- return /* @__PURE__ */ jsx(ImageRenderer, { ...props });
623
- }
624
- return /* @__PURE__ */ jsx(ImageEditProvider, { props, children: /* @__PURE__ */ jsx(ImageEditContent, {}) });
573
+ const [editor] = useLexicalComposerContext();
574
+ if (!editor.isEditable()) return /* @__PURE__ */ jsx(ImageRenderer, { ...props });
575
+ return /* @__PURE__ */ jsx(ImageEditProvider, {
576
+ props,
577
+ children: /* @__PURE__ */ jsx(ImageEditContent, {})
578
+ });
625
579
  }
626
580
  function ImageEditContent() {
627
- const wrapperRef = useAtomValue(wrapperRefAtom);
628
- const setHovering = useSetAtom(hoveringAtom);
629
- const src = useAtomValue(srcAtom);
630
- const loadState = useAtomValue(loadStateAtom);
631
- const setLoadState = useSetAtom(loadStateAtom);
632
- const frameStyle = useAtomValue(frameStyleAtom);
633
- const altText = useAtomValue(altTextAtom);
634
- const width = useAtomValue(widthAtom);
635
- const height = useAtomValue(heightAtom);
636
- const captionText = useAtomValue(captionTextAtom);
637
- const setMetaOpen = useSetAtom(metaOpenAtom);
638
- const setReplaceOpen = useSetAtom(replaceOpenAtom);
639
- const setFocusCaptionOnOpen = useSetAtom(focusCaptionOnOpenAtom);
640
- const handleCaptionClick = useCallback(
641
- (e) => {
642
- e.stopPropagation();
643
- const scrollY = window.scrollY;
644
- setReplaceOpen(false);
645
- setMetaOpen(true);
646
- setFocusCaptionOnOpen(true);
647
- requestAnimationFrame(() => {
648
- window.scrollTo({ top: scrollY });
649
- });
650
- },
651
- [setMetaOpen, setReplaceOpen, setFocusCaptionOnOpen]
652
- );
653
- return /* @__PURE__ */ jsxs(
654
- "div",
655
- {
656
- className: `${editTrigger} ${semanticClassNames.editTrigger}`,
657
- ref: wrapperRef,
658
- onMouseEnter: () => setHovering(true),
659
- onMouseLeave: () => setHovering(false),
660
- children: [
661
- src ? /* @__PURE__ */ jsxs("figure", { className: `${root} ${semanticClassNames.root}`, children: [
662
- /* @__PURE__ */ jsxs(
663
- "div",
664
- {
665
- className: `${frame} ${semanticClassNames.frame} ${frameEditMode} ${imageState[loadState]} ${frameStateSemanticClass[loadState]}`.trim(),
666
- style: frameStyle,
667
- children: [
668
- /* @__PURE__ */ jsx(
669
- "img",
670
- {
671
- alt: altText,
672
- className: `${image} ${loadState === "loaded" ? imageVisible : ""} ${semanticClassNames.image}`,
673
- height,
674
- loading: "lazy",
675
- src,
676
- width,
677
- onError: () => setLoadState("error"),
678
- onLoad: () => setLoadState("loaded")
679
- }
680
- ),
681
- loadState === "loading" && /* @__PURE__ */ jsx("span", { className: `${loader} ${semanticClassNames.loader}` }),
682
- loadState === "error" && /* @__PURE__ */ jsx("span", { className: `${errorBadge} ${semanticClassNames.errorBadge}`, children: "Image failed to load" })
683
- ]
684
- }
685
- ),
686
- captionText && /* @__PURE__ */ jsx(
687
- "figcaption",
688
- {
689
- className: `${caption} ${semanticClassNames.caption}`,
690
- onClick: handleCaptionClick,
691
- children: captionText
692
- }
693
- )
694
- ] }) : /* @__PURE__ */ jsx(ReplacePopover, {}),
695
- src && /* @__PURE__ */ jsx(ImageEditToolbar, {})
696
- ]
697
- }
698
- );
581
+ const wrapperRef = useAtomValue(wrapperRefAtom);
582
+ const setHovering = useSetAtom(hoveringAtom);
583
+ const src = useAtomValue(srcAtom);
584
+ const loadState = useAtomValue(loadStateAtom);
585
+ const setLoadState = useSetAtom(loadStateAtom);
586
+ const frameStyle = useAtomValue(frameStyleAtom);
587
+ const altText = useAtomValue(altTextAtom);
588
+ const width = useAtomValue(widthAtom);
589
+ const height = useAtomValue(heightAtom);
590
+ const captionText = useAtomValue(captionTextAtom);
591
+ const setMetaOpen = useSetAtom(metaOpenAtom);
592
+ const setReplaceOpen = useSetAtom(replaceOpenAtom);
593
+ const setFocusCaptionOnOpen = useSetAtom(focusCaptionOnOpenAtom);
594
+ const handleCaptionClick = useCallback((e) => {
595
+ e.stopPropagation();
596
+ const scrollY = window.scrollY;
597
+ setReplaceOpen(false);
598
+ setMetaOpen(true);
599
+ setFocusCaptionOnOpen(true);
600
+ requestAnimationFrame(() => {
601
+ window.scrollTo({ top: scrollY });
602
+ });
603
+ }, [
604
+ setMetaOpen,
605
+ setReplaceOpen,
606
+ setFocusCaptionOnOpen
607
+ ]);
608
+ return /* @__PURE__ */ jsxs("div", {
609
+ className: `${editTrigger} ${semanticClassNames.editTrigger}`,
610
+ ref: wrapperRef,
611
+ onMouseEnter: () => setHovering(true),
612
+ onMouseLeave: () => setHovering(false),
613
+ children: [src ? /* @__PURE__ */ jsxs("figure", {
614
+ className: `${root} ${semanticClassNames.root}`,
615
+ children: [/* @__PURE__ */ jsxs("div", {
616
+ className: `${frame} ${semanticClassNames.frame} ${frameEditMode} ${imageState[loadState]} ${frameStateSemanticClass[loadState]}`.trim(),
617
+ style: frameStyle,
618
+ children: [
619
+ /* @__PURE__ */ jsx("img", {
620
+ alt: altText,
621
+ className: `${image} ${loadState === "loaded" ? imageVisible : ""} ${semanticClassNames.image}`,
622
+ height,
623
+ loading: "lazy",
624
+ src,
625
+ width,
626
+ onError: () => setLoadState("error"),
627
+ onLoad: () => setLoadState("loaded")
628
+ }),
629
+ loadState === "loading" && /* @__PURE__ */ jsx("span", { className: `_1n94osfa ${semanticClassNames.loader}` }),
630
+ loadState === "error" && /* @__PURE__ */ jsx("span", {
631
+ className: `_1n94osfb ${semanticClassNames.errorBadge}`,
632
+ children: "Image failed to load"
633
+ })
634
+ ]
635
+ }), captionText && /* @__PURE__ */ jsx("figcaption", {
636
+ className: `_1n94osfc ${semanticClassNames.caption}`,
637
+ onClick: handleCaptionClick,
638
+ children: captionText
639
+ })]
640
+ }) : /* @__PURE__ */ jsx(ReplacePopover, {}), src && /* @__PURE__ */ jsx(ImageEditToolbar, {})]
641
+ });
699
642
  }
700
- export {
701
- ImageEditRenderer,
702
- ImageRenderer
703
- };
643
+ //#endregion
644
+ export { ImageEditRenderer, ImageRenderer };