magic-editor-x 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 (37) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +890 -0
  3. package/dist/_chunks/App-B1FgOsWa.mjs +2143 -0
  4. package/dist/_chunks/App-mtrlABtd.js +2146 -0
  5. package/dist/_chunks/LicensePage-BnyWSrWs.js +375 -0
  6. package/dist/_chunks/LicensePage-CWH-AFR-.mjs +373 -0
  7. package/dist/_chunks/LiveCollaborationPanel-DbDHwr2C.js +222 -0
  8. package/dist/_chunks/LiveCollaborationPanel-ryjcDAA7.mjs +220 -0
  9. package/dist/_chunks/Settings-Bk9bxJTy.js +440 -0
  10. package/dist/_chunks/Settings-D-V2MLVm.mjs +438 -0
  11. package/dist/_chunks/de-CSrHZWEb.mjs +295 -0
  12. package/dist/_chunks/de-CzSo1oD2.js +295 -0
  13. package/dist/_chunks/en-DuQun2v4.mjs +295 -0
  14. package/dist/_chunks/en-DxIkVPUh.js +295 -0
  15. package/dist/_chunks/es-DAQ_97zx.js +273 -0
  16. package/dist/_chunks/es-DEB0CA8S.mjs +273 -0
  17. package/dist/_chunks/fr-Bqkhvdx2.mjs +273 -0
  18. package/dist/_chunks/fr-ChPabvNP.js +273 -0
  19. package/dist/_chunks/getTranslation-C4uWR0DB.mjs +50985 -0
  20. package/dist/_chunks/getTranslation-D35vbDap.js +51001 -0
  21. package/dist/_chunks/index-B5MzUyo0.mjs +2541 -0
  22. package/dist/_chunks/index-BRVqbnOb.mjs +4450 -0
  23. package/dist/_chunks/index-BiLy_f7C.js +2540 -0
  24. package/dist/_chunks/index-CQx7-dFP.js +4472 -0
  25. package/dist/_chunks/pt-BMoYltav.mjs +273 -0
  26. package/dist/_chunks/pt-Cm74LpyZ.js +273 -0
  27. package/dist/_chunks/tools-CjnQJ9w2.mjs +2155 -0
  28. package/dist/_chunks/tools-DNt2tioN.js +2186 -0
  29. package/dist/admin/index.js +3 -0
  30. package/dist/admin/index.mjs +4 -0
  31. package/dist/server/index.js +2554 -0
  32. package/dist/server/index.mjs +2544 -0
  33. package/dist/style.css +164 -0
  34. package/package.json +122 -0
  35. package/pics/collab-magiceditorX.png +0 -0
  36. package/pics/editorX.png +0 -0
  37. package/pics/liveCollabwidget1.png +0 -0
@@ -0,0 +1,2186 @@
1
+ "use strict";
2
+ const Header = require("@editorjs/header");
3
+ const Paragraph = require("@editorjs/paragraph");
4
+ const NestedList = require("@editorjs/nested-list");
5
+ const Checklist = require("@editorjs/checklist");
6
+ const Quote = require("@editorjs/quote");
7
+ const Warning = require("@editorjs/warning");
8
+ const Code = require("@editorjs/code");
9
+ const Delimiter = require("@editorjs/delimiter");
10
+ const Table = require("@editorjs/table");
11
+ const Embed = require("@editorjs/embed");
12
+ const Raw = require("@editorjs/raw");
13
+ const Image = require("@editorjs/image");
14
+ const SimpleImage = require("@editorjs/simple-image");
15
+ const LinkTool = require("@editorjs/link");
16
+ const Attaches = require("@editorjs/attaches");
17
+ const Personality = require("@editorjs/personality");
18
+ const Alert = require("editorjs-alert");
19
+ const ToggleBlock = require("editorjs-toggle-block");
20
+ const CodeFlask = require("@calumk/editorjs-codeflask");
21
+ const React = require("react");
22
+ const client = require("react-dom/client");
23
+ const jsxRuntime = require("react/jsx-runtime");
24
+ const styled = require("styled-components");
25
+ const Marker = require("@editorjs/marker");
26
+ const InlineCode = require("@editorjs/inline-code");
27
+ const Underline = require("@editorjs/underline");
28
+ const Strikethrough = require("@sotaproject/strikethrough");
29
+ const Tooltip = require("editorjs-tooltip");
30
+ const TextVariantTune = require("@editorjs/text-variant-tune");
31
+ const AlignmentTune = require("editorjs-text-alignment-blocktune");
32
+ const IndentTune = require("editorjs-indent-tune");
33
+ const Undo = require("editorjs-undo");
34
+ const DragDrop = require("editorjs-drag-drop");
35
+ const _interopDefault = (e) => e && e.__esModule ? e : { default: e };
36
+ const Header__default = /* @__PURE__ */ _interopDefault(Header);
37
+ const Paragraph__default = /* @__PURE__ */ _interopDefault(Paragraph);
38
+ const NestedList__default = /* @__PURE__ */ _interopDefault(NestedList);
39
+ const Checklist__default = /* @__PURE__ */ _interopDefault(Checklist);
40
+ const Quote__default = /* @__PURE__ */ _interopDefault(Quote);
41
+ const Warning__default = /* @__PURE__ */ _interopDefault(Warning);
42
+ const Code__default = /* @__PURE__ */ _interopDefault(Code);
43
+ const Delimiter__default = /* @__PURE__ */ _interopDefault(Delimiter);
44
+ const Table__default = /* @__PURE__ */ _interopDefault(Table);
45
+ const Embed__default = /* @__PURE__ */ _interopDefault(Embed);
46
+ const Raw__default = /* @__PURE__ */ _interopDefault(Raw);
47
+ const Image__default = /* @__PURE__ */ _interopDefault(Image);
48
+ const SimpleImage__default = /* @__PURE__ */ _interopDefault(SimpleImage);
49
+ const LinkTool__default = /* @__PURE__ */ _interopDefault(LinkTool);
50
+ const Attaches__default = /* @__PURE__ */ _interopDefault(Attaches);
51
+ const Personality__default = /* @__PURE__ */ _interopDefault(Personality);
52
+ const Alert__default = /* @__PURE__ */ _interopDefault(Alert);
53
+ const ToggleBlock__default = /* @__PURE__ */ _interopDefault(ToggleBlock);
54
+ const CodeFlask__default = /* @__PURE__ */ _interopDefault(CodeFlask);
55
+ const React__default = /* @__PURE__ */ _interopDefault(React);
56
+ const styled__default = /* @__PURE__ */ _interopDefault(styled);
57
+ const Marker__default = /* @__PURE__ */ _interopDefault(Marker);
58
+ const InlineCode__default = /* @__PURE__ */ _interopDefault(InlineCode);
59
+ const Underline__default = /* @__PURE__ */ _interopDefault(Underline);
60
+ const Strikethrough__default = /* @__PURE__ */ _interopDefault(Strikethrough);
61
+ const Tooltip__default = /* @__PURE__ */ _interopDefault(Tooltip);
62
+ const TextVariantTune__default = /* @__PURE__ */ _interopDefault(TextVariantTune);
63
+ const AlignmentTune__default = /* @__PURE__ */ _interopDefault(AlignmentTune);
64
+ const IndentTune__default = /* @__PURE__ */ _interopDefault(IndentTune);
65
+ const Undo__default = /* @__PURE__ */ _interopDefault(Undo);
66
+ const DragDrop__default = /* @__PURE__ */ _interopDefault(DragDrop);
67
+ class ButtonTool {
68
+ /**
69
+ * Enable paragraph-like behavior
70
+ */
71
+ static get pasteConfig() {
72
+ return { tags: ["BUTTON", "A"] };
73
+ }
74
+ /**
75
+ * Tool icon in the toolbox
76
+ */
77
+ static get toolbox() {
78
+ return {
79
+ title: "Button",
80
+ icon: '<svg width="17" height="15" viewBox="0 0 17 15" xmlns="http://www.w3.org/2000/svg"><path d="M1 2.5C1 1.67157 1.67157 1 2.5 1H14.5C15.3284 1 16 1.67157 16 2.5V12.5C16 13.3284 15.3284 14 14.5 14H2.5C1.67157 14 1 13.3284 1 12.5V2.5ZM3.5 5C3.22386 5 3 5.22386 3 5.5V9.5C3 9.77614 3.22386 10 3.5 10H13.5C13.7761 10 14 9.77614 14 9.5V5.5C14 5.22386 13.7761 5 13.5 5H3.5Z" fill="currentColor"/></svg>'
81
+ };
82
+ }
83
+ /**
84
+ * Creates an instance of ButtonTool
85
+ * @param {object} params - Constructor params
86
+ * @param {object} params.data - Previously saved data
87
+ * @param {object} params.config - Tool configuration
88
+ * @param {object} params.api - Editor.js API
89
+ */
90
+ constructor({ data, config, api }) {
91
+ this.api = api;
92
+ this.config = config || {};
93
+ this.data = {
94
+ text: data.text || "",
95
+ link: data.link || "",
96
+ style: data.style || "primary",
97
+ openInNewTab: data.openInNewTab !== false
98
+ };
99
+ this.wrapper = null;
100
+ this.styles = this._getDefaultStyles();
101
+ }
102
+ /**
103
+ * Returns default button styles
104
+ * @returns {object} Style configuration
105
+ */
106
+ _getDefaultStyles() {
107
+ return {
108
+ primary: {
109
+ background: "#3b82f6",
110
+ color: "#ffffff",
111
+ border: "none",
112
+ hoverBackground: "#2563eb"
113
+ },
114
+ secondary: {
115
+ background: "#64748b",
116
+ color: "#ffffff",
117
+ border: "none",
118
+ hoverBackground: "#475569"
119
+ },
120
+ outline: {
121
+ background: "transparent",
122
+ color: "#3b82f6",
123
+ border: "2px solid #3b82f6",
124
+ hoverBackground: "#eff6ff"
125
+ },
126
+ success: {
127
+ background: "#22c55e",
128
+ color: "#ffffff",
129
+ border: "none",
130
+ hoverBackground: "#16a34a"
131
+ },
132
+ danger: {
133
+ background: "#ef4444",
134
+ color: "#ffffff",
135
+ border: "none",
136
+ hoverBackground: "#dc2626"
137
+ }
138
+ };
139
+ }
140
+ /**
141
+ * Renders the tool UI
142
+ * @returns {HTMLElement} The tool wrapper element
143
+ */
144
+ render() {
145
+ this.wrapper = document.createElement("div");
146
+ this.wrapper.classList.add("cdx-button-tool");
147
+ this.wrapper.style.cssText = `
148
+ padding: 12px;
149
+ background: #f8fafc;
150
+ border-radius: 8px;
151
+ margin: 8px 0;
152
+ `;
153
+ const preview = document.createElement("div");
154
+ preview.style.cssText = "text-align: center; margin-bottom: 12px;";
155
+ const button = this._createButton();
156
+ preview.appendChild(button);
157
+ const form = document.createElement("div");
158
+ form.style.cssText = "display: flex; flex-direction: column; gap: 8px;";
159
+ const textInput = this._createInput("Button Text", this.data.text, (value) => {
160
+ this.data.text = value;
161
+ button.textContent = value || "Click Me";
162
+ });
163
+ const linkInput = this._createInput("Link URL", this.data.link, (value) => {
164
+ this.data.link = value;
165
+ });
166
+ const styleSelect = this._createStyleSelect((value) => {
167
+ this.data.style = value;
168
+ this._updateButtonStyle(button, value);
169
+ });
170
+ const newTabCheckbox = this._createCheckbox("Open in new tab", this.data.openInNewTab, (checked) => {
171
+ this.data.openInNewTab = checked;
172
+ });
173
+ form.appendChild(textInput);
174
+ form.appendChild(linkInput);
175
+ form.appendChild(styleSelect);
176
+ form.appendChild(newTabCheckbox);
177
+ this.wrapper.appendChild(preview);
178
+ this.wrapper.appendChild(form);
179
+ return this.wrapper;
180
+ }
181
+ /**
182
+ * Creates the button element
183
+ * @returns {HTMLElement} Button element
184
+ */
185
+ _createButton() {
186
+ const button = document.createElement("button");
187
+ button.type = "button";
188
+ button.textContent = this.data.text || "Click Me";
189
+ button.style.cssText = `
190
+ padding: 10px 24px;
191
+ font-size: 14px;
192
+ font-weight: 600;
193
+ border-radius: 6px;
194
+ cursor: pointer;
195
+ transition: all 0.2s ease;
196
+ outline: none;
197
+ `;
198
+ this._updateButtonStyle(button, this.data.style);
199
+ button.addEventListener("click", (e) => {
200
+ e.preventDefault();
201
+ e.stopPropagation();
202
+ });
203
+ return button;
204
+ }
205
+ /**
206
+ * Updates button styling
207
+ * @param {HTMLElement} button - Button element
208
+ * @param {string} style - Style name
209
+ */
210
+ _updateButtonStyle(button, style) {
211
+ const styleConfig = this.styles[style] || this.styles.primary;
212
+ button.style.backgroundColor = styleConfig.background;
213
+ button.style.color = styleConfig.color;
214
+ button.style.border = styleConfig.border;
215
+ }
216
+ /**
217
+ * Creates an input field with label
218
+ * @param {string} label - Input label
219
+ * @param {string} value - Initial value
220
+ * @param {Function} onChange - Change handler
221
+ * @returns {HTMLElement} Input container
222
+ */
223
+ _createInput(label, value, onChange) {
224
+ const container = document.createElement("div");
225
+ container.style.cssText = "display: flex; flex-direction: column; gap: 4px;";
226
+ const labelEl = document.createElement("label");
227
+ labelEl.textContent = label;
228
+ labelEl.style.cssText = "font-size: 12px; color: #64748b; font-weight: 500;";
229
+ const input = document.createElement("input");
230
+ input.type = "text";
231
+ input.value = value;
232
+ input.placeholder = label;
233
+ input.style.cssText = `
234
+ padding: 8px 12px;
235
+ border: 1px solid #e2e8f0;
236
+ border-radius: 6px;
237
+ font-size: 14px;
238
+ outline: none;
239
+ transition: border-color 0.2s;
240
+ `;
241
+ input.addEventListener("focus", () => {
242
+ input.style.borderColor = "#3b82f6";
243
+ });
244
+ input.addEventListener("blur", () => {
245
+ input.style.borderColor = "#e2e8f0";
246
+ });
247
+ input.addEventListener("input", (e) => {
248
+ onChange(e.target.value);
249
+ });
250
+ container.appendChild(labelEl);
251
+ container.appendChild(input);
252
+ return container;
253
+ }
254
+ /**
255
+ * Creates style select dropdown
256
+ * @param {Function} onChange - Change handler
257
+ * @returns {HTMLElement} Select container
258
+ */
259
+ _createStyleSelect(onChange) {
260
+ const container = document.createElement("div");
261
+ container.style.cssText = "display: flex; flex-direction: column; gap: 4px;";
262
+ const label = document.createElement("label");
263
+ label.textContent = "Button Style";
264
+ label.style.cssText = "font-size: 12px; color: #64748b; font-weight: 500;";
265
+ const select = document.createElement("select");
266
+ select.style.cssText = `
267
+ padding: 8px 12px;
268
+ border: 1px solid #e2e8f0;
269
+ border-radius: 6px;
270
+ font-size: 14px;
271
+ outline: none;
272
+ cursor: pointer;
273
+ background: white;
274
+ `;
275
+ const options = [
276
+ { value: "primary", label: "Primary (Blue)" },
277
+ { value: "secondary", label: "Secondary (Gray)" },
278
+ { value: "outline", label: "Outline" },
279
+ { value: "success", label: "Success (Green)" },
280
+ { value: "danger", label: "Danger (Red)" }
281
+ ];
282
+ options.forEach((opt) => {
283
+ const option = document.createElement("option");
284
+ option.value = opt.value;
285
+ option.textContent = opt.label;
286
+ option.selected = this.data.style === opt.value;
287
+ select.appendChild(option);
288
+ });
289
+ select.addEventListener("change", (e) => {
290
+ onChange(e.target.value);
291
+ });
292
+ container.appendChild(label);
293
+ container.appendChild(select);
294
+ return container;
295
+ }
296
+ /**
297
+ * Creates checkbox with label
298
+ * @param {string} label - Checkbox label
299
+ * @param {boolean} checked - Initial state
300
+ * @param {Function} onChange - Change handler
301
+ * @returns {HTMLElement} Checkbox container
302
+ */
303
+ _createCheckbox(label, checked, onChange) {
304
+ const container = document.createElement("div");
305
+ container.style.cssText = "display: flex; align-items: center; gap: 8px; margin-top: 4px;";
306
+ const checkbox = document.createElement("input");
307
+ checkbox.type = "checkbox";
308
+ checkbox.checked = checked;
309
+ checkbox.style.cssText = "width: 16px; height: 16px; cursor: pointer;";
310
+ checkbox.addEventListener("change", (e) => {
311
+ onChange(e.target.checked);
312
+ });
313
+ const labelEl = document.createElement("label");
314
+ labelEl.textContent = label;
315
+ labelEl.style.cssText = "font-size: 14px; color: #334155; cursor: pointer;";
316
+ labelEl.addEventListener("click", () => {
317
+ checkbox.checked = !checkbox.checked;
318
+ onChange(checkbox.checked);
319
+ });
320
+ container.appendChild(checkbox);
321
+ container.appendChild(labelEl);
322
+ return container;
323
+ }
324
+ /**
325
+ * Extracts data from the tool
326
+ * @returns {object} Saved data
327
+ */
328
+ save() {
329
+ return {
330
+ text: this.data.text,
331
+ link: this.data.link,
332
+ style: this.data.style,
333
+ openInNewTab: this.data.openInNewTab
334
+ };
335
+ }
336
+ /**
337
+ * Validates block data
338
+ * @param {object} savedData - Saved data to validate
339
+ * @returns {boolean} True if valid
340
+ */
341
+ validate(savedData) {
342
+ return savedData.text && savedData.text.trim().length > 0;
343
+ }
344
+ }
345
+ class HyperlinkTool {
346
+ /**
347
+ * Specifies this is an inline tool
348
+ */
349
+ static get isInline() {
350
+ return true;
351
+ }
352
+ /**
353
+ * CSS class for the tool icon
354
+ */
355
+ static get CSS() {
356
+ return "ce-inline-tool--hyperlink";
357
+ }
358
+ /**
359
+ * Sanitize config for Editor.js
360
+ */
361
+ static get sanitize() {
362
+ return {
363
+ a: {
364
+ href: true,
365
+ target: true,
366
+ rel: true
367
+ }
368
+ };
369
+ }
370
+ /**
371
+ * Creates an instance of HyperlinkTool
372
+ * @param {object} params - Constructor params
373
+ * @param {object} params.api - Editor.js API
374
+ * @param {object} params.config - Tool configuration
375
+ */
376
+ constructor({ api, config }) {
377
+ this.api = api;
378
+ this.config = config || {};
379
+ this.defaultTarget = this.config.target || "_blank";
380
+ this.defaultRel = this.config.rel || "noopener noreferrer";
381
+ this.availableTargets = this.config.availableTargets || ["_blank", "_self", "_parent", "_top"];
382
+ this.availableRels = this.config.availableRels || ["nofollow", "noreferrer", "noopener", "sponsored", "ugc"];
383
+ this.button = null;
384
+ this.popup = null;
385
+ this.state = false;
386
+ this.selectedRange = null;
387
+ this.existingLink = null;
388
+ }
389
+ /**
390
+ * Renders the toolbar button
391
+ * @returns {HTMLElement} Button element
392
+ */
393
+ render() {
394
+ this.button = document.createElement("button");
395
+ this.button.type = "button";
396
+ this.button.classList.add(this.api.styles.inlineToolButton);
397
+ this.button.classList.add(HyperlinkTool.CSS);
398
+ this.button.innerHTML = this._getIcon();
399
+ return this.button;
400
+ }
401
+ /**
402
+ * Returns the link icon SVG
403
+ * @returns {string} SVG icon
404
+ */
405
+ _getIcon() {
406
+ return `<svg width="14" height="10" viewBox="0 0 14 10" xmlns="http://www.w3.org/2000/svg">
407
+ <path d="M6 1.25H2.75C1.64543 1.25 0.75 2.14543 0.75 3.25V6.75C0.75 7.85457 1.64543 8.75 2.75 8.75H6" stroke="currentColor" stroke-width="1.5" fill="none"/>
408
+ <path d="M8 8.75H11.25C12.3546 8.75 13.25 7.85457 13.25 6.75V3.25C13.25 2.14543 12.3546 1.25 11.25 1.25H8" stroke="currentColor" stroke-width="1.5" fill="none"/>
409
+ <path d="M4 5H10" stroke="currentColor" stroke-width="1.5"/>
410
+ </svg>`;
411
+ }
412
+ /**
413
+ * Handles click/activation of the tool
414
+ * @param {Range} range - Current selection range
415
+ */
416
+ surround(range) {
417
+ if (this.state) {
418
+ this._unwrap(range);
419
+ } else {
420
+ this._showPopup(range);
421
+ }
422
+ }
423
+ /**
424
+ * Shows the link input popup
425
+ * @param {Range} range - Current selection range
426
+ */
427
+ _showPopup(range) {
428
+ this.selectedRange = range;
429
+ const parentAnchor = this.api.selection.findParentTag("A");
430
+ this.existingLink = parentAnchor;
431
+ this.popup = document.createElement("div");
432
+ this.popup.classList.add("ce-inline-tool-hyperlink-popup");
433
+ this.popup.style.cssText = `
434
+ position: absolute;
435
+ background: white;
436
+ border: 1px solid #e2e8f0;
437
+ border-radius: 8px;
438
+ box-shadow: 0 4px 12px rgba(0,0,0,0.15);
439
+ padding: 12px;
440
+ z-index: 999999;
441
+ min-width: 300px;
442
+ `;
443
+ const urlContainer = this._createInputContainer("URL", "url", parentAnchor?.href || "");
444
+ const targetContainer = this._createSelectContainer(
445
+ "Target",
446
+ "target",
447
+ this.availableTargets.map((t) => ({ value: t, label: t })),
448
+ parentAnchor?.target || this.defaultTarget
449
+ );
450
+ const relContainer = this._createRelContainer(parentAnchor?.rel || this.defaultRel);
451
+ const buttonsContainer = document.createElement("div");
452
+ buttonsContainer.style.cssText = "display: flex; gap: 8px; margin-top: 12px; justify-content: flex-end;";
453
+ const cancelBtn = document.createElement("button");
454
+ cancelBtn.type = "button";
455
+ cancelBtn.textContent = "Cancel";
456
+ cancelBtn.style.cssText = `
457
+ padding: 6px 12px;
458
+ border: 1px solid #e2e8f0;
459
+ border-radius: 6px;
460
+ background: white;
461
+ cursor: pointer;
462
+ font-size: 13px;
463
+ `;
464
+ cancelBtn.addEventListener("click", () => this._closePopup());
465
+ const saveBtn = document.createElement("button");
466
+ saveBtn.type = "button";
467
+ saveBtn.textContent = parentAnchor ? "Update" : "Add Link";
468
+ saveBtn.style.cssText = `
469
+ padding: 6px 12px;
470
+ border: none;
471
+ border-radius: 6px;
472
+ background: #3b82f6;
473
+ color: white;
474
+ cursor: pointer;
475
+ font-size: 13px;
476
+ font-weight: 500;
477
+ `;
478
+ saveBtn.addEventListener("click", () => this._saveLink());
479
+ if (parentAnchor) {
480
+ const removeBtn = document.createElement("button");
481
+ removeBtn.type = "button";
482
+ removeBtn.textContent = "Remove";
483
+ removeBtn.style.cssText = `
484
+ padding: 6px 12px;
485
+ border: 1px solid #ef4444;
486
+ border-radius: 6px;
487
+ background: white;
488
+ color: #ef4444;
489
+ cursor: pointer;
490
+ font-size: 13px;
491
+ `;
492
+ removeBtn.addEventListener("click", () => {
493
+ this._unwrap(range);
494
+ this._closePopup();
495
+ });
496
+ buttonsContainer.appendChild(removeBtn);
497
+ }
498
+ buttonsContainer.appendChild(cancelBtn);
499
+ buttonsContainer.appendChild(saveBtn);
500
+ this.popup.appendChild(urlContainer);
501
+ this.popup.appendChild(targetContainer);
502
+ this.popup.appendChild(relContainer);
503
+ this.popup.appendChild(buttonsContainer);
504
+ document.body.appendChild(this.popup);
505
+ this._positionPopup();
506
+ setTimeout(() => {
507
+ const urlInput = this.popup.querySelector('input[name="url"]');
508
+ if (urlInput) urlInput.focus();
509
+ }, 50);
510
+ setTimeout(() => {
511
+ document.addEventListener("click", this._handleOutsideClick);
512
+ }, 100);
513
+ }
514
+ /**
515
+ * Creates an input container
516
+ * @param {string} label - Input label
517
+ * @param {string} name - Input name
518
+ * @param {string} value - Initial value
519
+ * @returns {HTMLElement} Input container
520
+ */
521
+ _createInputContainer(label, name, value) {
522
+ const container = document.createElement("div");
523
+ container.style.cssText = "margin-bottom: 10px;";
524
+ const labelEl = document.createElement("label");
525
+ labelEl.textContent = label;
526
+ labelEl.style.cssText = "display: block; font-size: 12px; color: #64748b; margin-bottom: 4px; font-weight: 500;";
527
+ const input = document.createElement("input");
528
+ input.type = "text";
529
+ input.name = name;
530
+ input.value = value;
531
+ input.placeholder = "https://example.com";
532
+ input.style.cssText = `
533
+ width: 100%;
534
+ padding: 8px 10px;
535
+ border: 1px solid #e2e8f0;
536
+ border-radius: 6px;
537
+ font-size: 14px;
538
+ outline: none;
539
+ box-sizing: border-box;
540
+ `;
541
+ input.addEventListener("focus", () => {
542
+ input.style.borderColor = "#3b82f6";
543
+ });
544
+ input.addEventListener("blur", () => {
545
+ input.style.borderColor = "#e2e8f0";
546
+ });
547
+ container.appendChild(labelEl);
548
+ container.appendChild(input);
549
+ return container;
550
+ }
551
+ /**
552
+ * Creates a select container
553
+ * @param {string} label - Select label
554
+ * @param {string} name - Select name
555
+ * @param {Array} options - Select options
556
+ * @param {string} value - Initial value
557
+ * @returns {HTMLElement} Select container
558
+ */
559
+ _createSelectContainer(label, name, options, value) {
560
+ const container = document.createElement("div");
561
+ container.style.cssText = "margin-bottom: 10px;";
562
+ const labelEl = document.createElement("label");
563
+ labelEl.textContent = label;
564
+ labelEl.style.cssText = "display: block; font-size: 12px; color: #64748b; margin-bottom: 4px; font-weight: 500;";
565
+ const select = document.createElement("select");
566
+ select.name = name;
567
+ select.style.cssText = `
568
+ width: 100%;
569
+ padding: 8px 10px;
570
+ border: 1px solid #e2e8f0;
571
+ border-radius: 6px;
572
+ font-size: 14px;
573
+ outline: none;
574
+ background: white;
575
+ cursor: pointer;
576
+ box-sizing: border-box;
577
+ `;
578
+ options.forEach((opt) => {
579
+ const option = document.createElement("option");
580
+ option.value = opt.value;
581
+ option.textContent = opt.label;
582
+ option.selected = value === opt.value;
583
+ select.appendChild(option);
584
+ });
585
+ container.appendChild(labelEl);
586
+ container.appendChild(select);
587
+ return container;
588
+ }
589
+ /**
590
+ * Creates rel attribute checkboxes
591
+ * @param {string} currentRel - Current rel value
592
+ * @returns {HTMLElement} Rel container
593
+ */
594
+ _createRelContainer(currentRel) {
595
+ const container = document.createElement("div");
596
+ container.style.cssText = "margin-bottom: 8px;";
597
+ const label = document.createElement("label");
598
+ label.textContent = "Rel Attributes";
599
+ label.style.cssText = "display: block; font-size: 12px; color: #64748b; margin-bottom: 6px; font-weight: 500;";
600
+ container.appendChild(label);
601
+ const checkboxContainer = document.createElement("div");
602
+ checkboxContainer.style.cssText = "display: flex; flex-wrap: wrap; gap: 12px;";
603
+ const currentRels = currentRel ? currentRel.split(" ") : [];
604
+ this.availableRels.forEach((rel) => {
605
+ const wrapper = document.createElement("label");
606
+ wrapper.style.cssText = "display: flex; align-items: center; gap: 4px; cursor: pointer; font-size: 13px;";
607
+ const checkbox = document.createElement("input");
608
+ checkbox.type = "checkbox";
609
+ checkbox.name = "rel";
610
+ checkbox.value = rel;
611
+ checkbox.checked = currentRels.includes(rel);
612
+ checkbox.style.cssText = "cursor: pointer;";
613
+ wrapper.appendChild(checkbox);
614
+ wrapper.appendChild(document.createTextNode(rel));
615
+ checkboxContainer.appendChild(wrapper);
616
+ });
617
+ container.appendChild(checkboxContainer);
618
+ return container;
619
+ }
620
+ /**
621
+ * Positions the popup near the selection
622
+ */
623
+ _positionPopup() {
624
+ if (!this.popup) return;
625
+ const selection = window.getSelection();
626
+ if (!selection.rangeCount) return;
627
+ const range = selection.getRangeAt(0);
628
+ const rect = range.getBoundingClientRect();
629
+ const popupRect = this.popup.getBoundingClientRect();
630
+ let left = rect.left + rect.width / 2 - popupRect.width / 2;
631
+ let top = rect.bottom + 10 + window.scrollY;
632
+ if (left < 10) left = 10;
633
+ if (left + popupRect.width > window.innerWidth - 10) {
634
+ left = window.innerWidth - popupRect.width - 10;
635
+ }
636
+ this.popup.style.left = `${left}px`;
637
+ this.popup.style.top = `${top}px`;
638
+ }
639
+ /**
640
+ * Saves the link with user-entered values
641
+ */
642
+ _saveLink() {
643
+ if (!this.popup) return;
644
+ const urlInput = this.popup.querySelector('input[name="url"]');
645
+ const targetSelect = this.popup.querySelector('select[name="target"]');
646
+ const relCheckboxes = this.popup.querySelectorAll('input[name="rel"]:checked');
647
+ const url = urlInput?.value?.trim();
648
+ if (!url) {
649
+ urlInput.style.borderColor = "#ef4444";
650
+ return;
651
+ }
652
+ const target = targetSelect?.value || this.defaultTarget;
653
+ const rel = Array.from(relCheckboxes).map((cb) => cb.value).join(" ") || this.defaultRel;
654
+ this._closePopup();
655
+ if (this.selectedRange) {
656
+ const selection = window.getSelection();
657
+ selection.removeAllRanges();
658
+ selection.addRange(this.selectedRange);
659
+ }
660
+ if (this.existingLink) {
661
+ this.existingLink.href = url;
662
+ this.existingLink.target = target;
663
+ this.existingLink.rel = rel;
664
+ } else {
665
+ this._wrap(url, target, rel);
666
+ }
667
+ }
668
+ /**
669
+ * Wraps selection in anchor tag
670
+ * @param {string} href - Link URL
671
+ * @param {string} target - Link target
672
+ * @param {string} rel - Link rel
673
+ */
674
+ _wrap(href, target, rel) {
675
+ const selection = window.getSelection();
676
+ if (!selection.rangeCount) return;
677
+ const range = selection.getRangeAt(0);
678
+ const selectedText = range.extractContents();
679
+ const anchor = document.createElement("a");
680
+ anchor.href = href;
681
+ anchor.target = target;
682
+ anchor.rel = rel;
683
+ anchor.appendChild(selectedText);
684
+ range.insertNode(anchor);
685
+ selection.removeAllRanges();
686
+ const newRange = document.createRange();
687
+ newRange.selectNode(anchor);
688
+ selection.addRange(newRange);
689
+ }
690
+ /**
691
+ * Unwraps anchor tag from selection
692
+ * @param {Range} range - Selection range
693
+ */
694
+ _unwrap(range) {
695
+ const anchor = this.api.selection.findParentTag("A");
696
+ if (!anchor) return;
697
+ const text = anchor.textContent;
698
+ const textNode = document.createTextNode(text);
699
+ anchor.parentNode.replaceChild(textNode, anchor);
700
+ const selection = window.getSelection();
701
+ selection.removeAllRanges();
702
+ const newRange = document.createRange();
703
+ newRange.selectNode(textNode);
704
+ selection.addRange(newRange);
705
+ }
706
+ /**
707
+ * Closes the popup
708
+ */
709
+ _closePopup() {
710
+ if (this.popup) {
711
+ this.popup.remove();
712
+ this.popup = null;
713
+ }
714
+ document.removeEventListener("click", this._handleOutsideClick);
715
+ }
716
+ /**
717
+ * Handles clicks outside the popup
718
+ * @param {Event} e - Click event
719
+ */
720
+ _handleOutsideClick = (e) => {
721
+ if (this.popup && !this.popup.contains(e.target) && !this.button.contains(e.target)) {
722
+ this._closePopup();
723
+ }
724
+ };
725
+ /**
726
+ * Checks if current selection is inside a link
727
+ * @param {Selection} selection - Current selection
728
+ * @returns {boolean} True if inside link
729
+ */
730
+ checkState(selection) {
731
+ const anchor = this.api.selection.findParentTag("A");
732
+ this.state = !!anchor;
733
+ this.button.classList.toggle(this.api.styles.inlineToolButtonActive, this.state);
734
+ return this.state;
735
+ }
736
+ /**
737
+ * Shortcut key
738
+ */
739
+ get shortcut() {
740
+ return this.config.shortcut || "CMD+K";
741
+ }
742
+ /**
743
+ * Clean up on destroy
744
+ */
745
+ destroy() {
746
+ this._closePopup();
747
+ }
748
+ }
749
+ const API_BASE_URL = "https://magicapi.fitlex.me/api/magic-editor";
750
+ class MagicEditorAPI {
751
+ constructor(licenseKey) {
752
+ this.licenseKey = licenseKey;
753
+ this.baseUrl = API_BASE_URL;
754
+ }
755
+ async request(endpoint, options = {}) {
756
+ if (!this.licenseKey) {
757
+ throw new Error("No license key available");
758
+ }
759
+ let url = `${this.baseUrl}${endpoint}`;
760
+ if (!options.method || options.method === "GET") {
761
+ const separator = url.includes("?") ? "&" : "?";
762
+ url = `${url}${separator}licenseKey=${encodeURIComponent(this.licenseKey)}`;
763
+ }
764
+ const response = await fetch(url, {
765
+ ...options,
766
+ headers: {
767
+ "Content-Type": "application/json",
768
+ ...options.headers
769
+ }
770
+ });
771
+ const data = await response.json();
772
+ if (!response.ok) {
773
+ const error = new Error(data.error?.message || "API request failed");
774
+ error.code = data.error?.code;
775
+ error.status = response.status;
776
+ error.upgrade = data.upgrade;
777
+ throw error;
778
+ }
779
+ return data;
780
+ }
781
+ /**
782
+ * Perform text correction/transformation
783
+ * All AI actions go through this endpoint with different types
784
+ * @param {string} text - Text to process
785
+ * @param {string} type - Type: 'grammar' | 'style' | 'rewrite' | 'expand' | 'summarize' | 'continue' | 'translate'
786
+ * @param {object} options - Additional options (tone, language, etc.)
787
+ * @returns {Promise<object>} Result with corrected text
788
+ */
789
+ async correct(text, type = "grammar", options = {}) {
790
+ return this.request("/correct", {
791
+ method: "POST",
792
+ body: JSON.stringify({ text, type, options, licenseKey: this.licenseKey })
793
+ });
794
+ }
795
+ async getUsage() {
796
+ return this.request("/usage");
797
+ }
798
+ async getCredits() {
799
+ return this.request("/credits");
800
+ }
801
+ async getModels() {
802
+ return this.request("/models");
803
+ }
804
+ async getLimits() {
805
+ return this.request("/limits");
806
+ }
807
+ }
808
+ const ToolbarContainer = styled__default.default.div`
809
+ position: fixed;
810
+ background: white;
811
+ border: 1px solid #e2e8f0;
812
+ border-radius: 8px;
813
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1), 0 0 0 1px rgba(0, 0, 0, 0.05);
814
+ padding: 4px;
815
+ display: flex;
816
+ gap: 2px;
817
+ z-index: 9999;
818
+ font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
819
+ animation: slideUp 0.2s ease-out;
820
+
821
+ @keyframes slideUp {
822
+ from {
823
+ opacity: 0;
824
+ transform: translateY(4px);
825
+ }
826
+ to {
827
+ opacity: 1;
828
+ transform: translateY(0);
829
+ }
830
+ }
831
+
832
+ &::before {
833
+ content: '';
834
+ position: absolute;
835
+ top: -6px;
836
+ left: 50%;
837
+ transform: translateX(-50%);
838
+ width: 12px;
839
+ height: 12px;
840
+ background: white;
841
+ border-left: 1px solid #e2e8f0;
842
+ border-top: 1px solid #e2e8f0;
843
+ transform: translateX(-50%) rotate(45deg);
844
+ }
845
+ `;
846
+ const ActionButton = styled__default.default.button`
847
+ padding: 6px 12px;
848
+ display: flex;
849
+ align-items: center;
850
+ gap: 6px;
851
+ font-size: 13px;
852
+ font-weight: 500;
853
+ border: none;
854
+ border-radius: 6px;
855
+ background: transparent;
856
+ color: #334155;
857
+ cursor: pointer;
858
+ transition: all 0.15s ease;
859
+ white-space: nowrap;
860
+ position: relative;
861
+
862
+ &:hover:not(:disabled) {
863
+ background: #f8fafc;
864
+ color: #7C3AED;
865
+ }
866
+
867
+ &:active:not(:disabled) {
868
+ transform: scale(0.98);
869
+ }
870
+
871
+ &:disabled {
872
+ opacity: 0.5;
873
+ cursor: not-allowed;
874
+ }
875
+
876
+ svg {
877
+ width: 16px;
878
+ height: 16px;
879
+ flex-shrink: 0;
880
+ }
881
+ `;
882
+ const Divider = styled__default.default.div`
883
+ width: 1px;
884
+ height: 24px;
885
+ background: #e2e8f0;
886
+ margin: 0 4px;
887
+ align-self: center;
888
+ `;
889
+ const Submenu = styled__default.default.div`
890
+ position: absolute;
891
+ top: 100%;
892
+ left: 0;
893
+ margin-top: 4px;
894
+ background: white;
895
+ border: 1px solid #e2e8f0;
896
+ border-radius: 8px;
897
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
898
+ padding: 4px;
899
+ min-width: 160px;
900
+ z-index: 10000;
901
+ animation: slideDown 0.15s ease-out;
902
+
903
+ @keyframes slideDown {
904
+ from {
905
+ opacity: 0;
906
+ transform: translateY(-4px);
907
+ }
908
+ to {
909
+ opacity: 1;
910
+ transform: translateY(0);
911
+ }
912
+ }
913
+ `;
914
+ const SubmenuItem = styled__default.default.button`
915
+ width: 100%;
916
+ padding: 8px 12px;
917
+ display: flex;
918
+ align-items: center;
919
+ gap: 8px;
920
+ font-size: 13px;
921
+ border: none;
922
+ border-radius: 4px;
923
+ background: transparent;
924
+ color: #334155;
925
+ cursor: pointer;
926
+ text-align: left;
927
+ transition: all 0.1s ease;
928
+
929
+ &:hover {
930
+ background: #f8fafc;
931
+ color: #7C3AED;
932
+ }
933
+
934
+ svg {
935
+ width: 14px;
936
+ height: 14px;
937
+ }
938
+ `;
939
+ const LoadingSpinner = styled__default.default.div`
940
+ width: 14px;
941
+ height: 14px;
942
+ border: 2px solid #e2e8f0;
943
+ border-top-color: #7C3AED;
944
+ border-radius: 50%;
945
+ animation: spin 0.8s linear infinite;
946
+
947
+ @keyframes spin {
948
+ to { transform: rotate(360deg); }
949
+ }
950
+ `;
951
+ const AIInlineToolbar = ({
952
+ position,
953
+ onAction,
954
+ loading = false,
955
+ disabled = false,
956
+ onClose
957
+ }) => {
958
+ const [activeSubmenu, setActiveSubmenu] = React.useState(null);
959
+ const toolbarRef = React.useRef(null);
960
+ React.useEffect(() => {
961
+ const handleClickOutside = (e) => {
962
+ if (toolbarRef.current && !toolbarRef.current.contains(e.target)) {
963
+ onClose?.();
964
+ }
965
+ };
966
+ const handleEscape = (e) => {
967
+ if (e.key === "Escape") {
968
+ onClose?.();
969
+ }
970
+ };
971
+ document.addEventListener("mousedown", handleClickOutside);
972
+ document.addEventListener("keydown", handleEscape);
973
+ return () => {
974
+ document.removeEventListener("mousedown", handleClickOutside);
975
+ document.removeEventListener("keydown", handleEscape);
976
+ };
977
+ }, [onClose]);
978
+ const handleAction = (action, options = {}) => {
979
+ setActiveSubmenu(null);
980
+ onAction?.(action, options);
981
+ };
982
+ const toggleSubmenu = (menu) => {
983
+ setActiveSubmenu(activeSubmenu === menu ? null : menu);
984
+ };
985
+ return /* @__PURE__ */ jsxRuntime.jsxs(
986
+ ToolbarContainer,
987
+ {
988
+ ref: toolbarRef,
989
+ style: {
990
+ left: `${position.left}px`,
991
+ top: `${position.top}px`
992
+ },
993
+ children: [
994
+ /* @__PURE__ */ jsxRuntime.jsxs(
995
+ ActionButton,
996
+ {
997
+ onClick: () => handleAction("fix"),
998
+ disabled: disabled || loading,
999
+ title: "Fix grammar and spelling",
1000
+ children: [
1001
+ loading ? /* @__PURE__ */ jsxRuntime.jsx(LoadingSpinner, {}) : /* @__PURE__ */ jsxRuntime.jsx("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: /* @__PURE__ */ jsxRuntime.jsx("polyline", { points: "20 6 9 17 4 12" }) }),
1002
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: "Fix" })
1003
+ ]
1004
+ }
1005
+ ),
1006
+ /* @__PURE__ */ jsxRuntime.jsxs(
1007
+ ActionButton,
1008
+ {
1009
+ onClick: () => toggleSubmenu("rewrite"),
1010
+ disabled: disabled || loading,
1011
+ title: "Rewrite text with different style",
1012
+ style: { position: "relative" },
1013
+ children: [
1014
+ /* @__PURE__ */ jsxRuntime.jsx("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M12 3l1.912 5.813a2 2 0 001.275 1.275L21 12l-5.813 1.912a2 2 0 00-1.275 1.275L12 21l-1.912-5.813a2 2 0 00-1.275-1.275L3 12l5.813-1.912a2 2 0 001.275-1.275L12 3z" }) }),
1015
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: "Rewrite" }),
1016
+ /* @__PURE__ */ jsxRuntime.jsx("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", style: { width: "12px", height: "12px" }, children: /* @__PURE__ */ jsxRuntime.jsx("polyline", { points: "6 9 12 15 18 9" }) }),
1017
+ activeSubmenu === "rewrite" && /* @__PURE__ */ jsxRuntime.jsxs(Submenu, { children: [
1018
+ /* @__PURE__ */ jsxRuntime.jsxs(SubmenuItem, { onClick: () => handleAction("rewrite", { tone: "professional" }), children: [
1019
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: "💼" }),
1020
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: "Professional" })
1021
+ ] }),
1022
+ /* @__PURE__ */ jsxRuntime.jsxs(SubmenuItem, { onClick: () => handleAction("rewrite", { tone: "casual" }), children: [
1023
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: "😊" }),
1024
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: "Casual" })
1025
+ ] }),
1026
+ /* @__PURE__ */ jsxRuntime.jsxs(SubmenuItem, { onClick: () => handleAction("rewrite", { tone: "friendly" }), children: [
1027
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: "👋" }),
1028
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: "Friendly" })
1029
+ ] })
1030
+ ] })
1031
+ ]
1032
+ }
1033
+ ),
1034
+ /* @__PURE__ */ jsxRuntime.jsx(Divider, {}),
1035
+ /* @__PURE__ */ jsxRuntime.jsxs(
1036
+ ActionButton,
1037
+ {
1038
+ onClick: () => handleAction("expand"),
1039
+ disabled: disabled || loading,
1040
+ title: "Make text longer and more detailed",
1041
+ children: [
1042
+ /* @__PURE__ */ jsxRuntime.jsxs("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: [
1043
+ /* @__PURE__ */ jsxRuntime.jsx("polyline", { points: "7 13 12 18 17 13" }),
1044
+ /* @__PURE__ */ jsxRuntime.jsx("polyline", { points: "7 6 12 11 17 6" })
1045
+ ] }),
1046
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: "Expand" })
1047
+ ]
1048
+ }
1049
+ ),
1050
+ /* @__PURE__ */ jsxRuntime.jsxs(
1051
+ ActionButton,
1052
+ {
1053
+ onClick: () => handleAction("summarize"),
1054
+ disabled: disabled || loading,
1055
+ title: "Make text shorter and concise",
1056
+ children: [
1057
+ /* @__PURE__ */ jsxRuntime.jsxs("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: [
1058
+ /* @__PURE__ */ jsxRuntime.jsx("polyline", { points: "17 11 12 6 7 11" }),
1059
+ /* @__PURE__ */ jsxRuntime.jsx("polyline", { points: "17 18 12 13 7 18" })
1060
+ ] }),
1061
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: "Shorten" })
1062
+ ]
1063
+ }
1064
+ ),
1065
+ /* @__PURE__ */ jsxRuntime.jsx(Divider, {}),
1066
+ /* @__PURE__ */ jsxRuntime.jsxs(
1067
+ ActionButton,
1068
+ {
1069
+ onClick: () => handleAction("continue"),
1070
+ disabled: disabled || loading,
1071
+ title: "Continue writing from here",
1072
+ children: [
1073
+ /* @__PURE__ */ jsxRuntime.jsx("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M12 5v14M5 12l7 7 7-7" }) }),
1074
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: "Continue" })
1075
+ ]
1076
+ }
1077
+ ),
1078
+ /* @__PURE__ */ jsxRuntime.jsxs(
1079
+ ActionButton,
1080
+ {
1081
+ onClick: () => toggleSubmenu("translate"),
1082
+ disabled: disabled || loading,
1083
+ title: "Translate to another language",
1084
+ style: { position: "relative" },
1085
+ children: [
1086
+ /* @__PURE__ */ jsxRuntime.jsx("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M5 8l6 6M4 14l6-6 2-3M2 5h12M7 2h1M22 22l-5-10-5 10M14.5 17h6" }) }),
1087
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: "Translate" }),
1088
+ /* @__PURE__ */ jsxRuntime.jsx("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", style: { width: "12px", height: "12px" }, children: /* @__PURE__ */ jsxRuntime.jsx("polyline", { points: "6 9 12 15 18 9" }) }),
1089
+ activeSubmenu === "translate" && /* @__PURE__ */ jsxRuntime.jsxs(Submenu, { children: [
1090
+ /* @__PURE__ */ jsxRuntime.jsxs(SubmenuItem, { onClick: () => handleAction("translate", { language: "en" }), children: [
1091
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: "🇬🇧" }),
1092
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: "English" })
1093
+ ] }),
1094
+ /* @__PURE__ */ jsxRuntime.jsxs(SubmenuItem, { onClick: () => handleAction("translate", { language: "de" }), children: [
1095
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: "🇩🇪" }),
1096
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: "German" })
1097
+ ] }),
1098
+ /* @__PURE__ */ jsxRuntime.jsxs(SubmenuItem, { onClick: () => handleAction("translate", { language: "fr" }), children: [
1099
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: "🇫🇷" }),
1100
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: "French" })
1101
+ ] }),
1102
+ /* @__PURE__ */ jsxRuntime.jsxs(SubmenuItem, { onClick: () => handleAction("translate", { language: "es" }), children: [
1103
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: "🇪🇸" }),
1104
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: "Spanish" })
1105
+ ] })
1106
+ ] })
1107
+ ]
1108
+ }
1109
+ )
1110
+ ]
1111
+ }
1112
+ );
1113
+ };
1114
+ const slideIn = styled.keyframes`
1115
+ from {
1116
+ opacity: 0;
1117
+ transform: translateY(-20px);
1118
+ }
1119
+ to {
1120
+ opacity: 1;
1121
+ transform: translateY(0);
1122
+ }
1123
+ `;
1124
+ const slideOut = styled.keyframes`
1125
+ from {
1126
+ opacity: 1;
1127
+ transform: translateY(0);
1128
+ }
1129
+ to {
1130
+ opacity: 0;
1131
+ transform: translateY(-20px);
1132
+ }
1133
+ `;
1134
+ const ToastContainer = styled__default.default.div`
1135
+ position: fixed;
1136
+ top: 20px;
1137
+ right: 20px;
1138
+ z-index: 99999;
1139
+ display: flex;
1140
+ flex-direction: column;
1141
+ gap: 10px;
1142
+ pointer-events: none;
1143
+ `;
1144
+ const ToastItem = styled__default.default.div`
1145
+ display: flex;
1146
+ align-items: center;
1147
+ gap: 12px;
1148
+ padding: 12px 16px;
1149
+ background: white;
1150
+ border: 1px solid #e2e8f0;
1151
+ border-radius: 8px;
1152
+ box-shadow: 0 4px 16px rgba(0, 0, 0, 0.12);
1153
+ font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
1154
+ font-size: 14px;
1155
+ color: #334155;
1156
+ min-width: 280px;
1157
+ max-width: 400px;
1158
+ pointer-events: auto;
1159
+ animation: ${(props) => props.$closing ? slideOut : slideIn} 0.3s ease-out;
1160
+
1161
+ &.success {
1162
+ border-left: 3px solid #10b981;
1163
+ }
1164
+
1165
+ &.error {
1166
+ border-left: 3px solid #ef4444;
1167
+ }
1168
+
1169
+ &.info {
1170
+ border-left: 3px solid #3b82f6;
1171
+ }
1172
+
1173
+ &.warning {
1174
+ border-left: 3px solid #f59e0b;
1175
+ }
1176
+ `;
1177
+ const Icon = styled__default.default.div`
1178
+ flex-shrink: 0;
1179
+ width: 20px;
1180
+ height: 20px;
1181
+ display: flex;
1182
+ align-items: center;
1183
+ justify-content: center;
1184
+ font-size: 16px;
1185
+ `;
1186
+ const Message = styled__default.default.div`
1187
+ flex: 1;
1188
+ line-height: 1.4;
1189
+
1190
+ strong {
1191
+ font-weight: 600;
1192
+ display: block;
1193
+ margin-bottom: 2px;
1194
+ }
1195
+
1196
+ span {
1197
+ font-size: 13px;
1198
+ color: #64748b;
1199
+ }
1200
+ `;
1201
+ const CloseButton = styled__default.default.button`
1202
+ flex-shrink: 0;
1203
+ width: 20px;
1204
+ height: 20px;
1205
+ display: flex;
1206
+ align-items: center;
1207
+ justify-content: center;
1208
+ border: none;
1209
+ background: transparent;
1210
+ color: #94a3b8;
1211
+ cursor: pointer;
1212
+ border-radius: 4px;
1213
+ transition: all 0.15s ease;
1214
+
1215
+ &:hover {
1216
+ background: #f1f5f9;
1217
+ color: #334155;
1218
+ }
1219
+
1220
+ svg {
1221
+ width: 14px;
1222
+ height: 14px;
1223
+ }
1224
+ `;
1225
+ const UndoButton = styled__default.default.button`
1226
+ flex-shrink: 0;
1227
+ padding: 4px 10px;
1228
+ font-size: 12px;
1229
+ font-weight: 500;
1230
+ border: 1px solid #e2e8f0;
1231
+ background: white;
1232
+ color: #7C3AED;
1233
+ border-radius: 4px;
1234
+ cursor: pointer;
1235
+ transition: all 0.15s ease;
1236
+
1237
+ &:hover {
1238
+ background: #f5f3ff;
1239
+ border-color: #7C3AED;
1240
+ }
1241
+
1242
+ &:active {
1243
+ transform: scale(0.98);
1244
+ }
1245
+ `;
1246
+ class ToastManager {
1247
+ constructor() {
1248
+ this.toasts = [];
1249
+ this.listeners = [];
1250
+ }
1251
+ subscribe(listener) {
1252
+ this.listeners.push(listener);
1253
+ return () => {
1254
+ this.listeners = this.listeners.filter((l) => l !== listener);
1255
+ };
1256
+ }
1257
+ notify() {
1258
+ this.listeners.forEach((listener) => listener(this.toasts));
1259
+ }
1260
+ show(options) {
1261
+ const id = Date.now() + Math.random();
1262
+ const toast = {
1263
+ id,
1264
+ type: options.type || "info",
1265
+ icon: options.icon,
1266
+ message: options.message,
1267
+ description: options.description,
1268
+ duration: options.duration || 3e3,
1269
+ onUndo: options.onUndo,
1270
+ closeable: options.closeable !== false
1271
+ };
1272
+ this.toasts.push(toast);
1273
+ this.notify();
1274
+ if (toast.duration > 0) {
1275
+ setTimeout(() => {
1276
+ this.remove(id);
1277
+ }, toast.duration);
1278
+ }
1279
+ return id;
1280
+ }
1281
+ remove(id) {
1282
+ this.toasts = this.toasts.filter((t) => t.id !== id);
1283
+ this.notify();
1284
+ }
1285
+ success(message, options = {}) {
1286
+ return this.show({
1287
+ type: "success",
1288
+ icon: "✓",
1289
+ message,
1290
+ ...options
1291
+ });
1292
+ }
1293
+ error(message, options = {}) {
1294
+ return this.show({
1295
+ type: "error",
1296
+ icon: "✕",
1297
+ message,
1298
+ ...options
1299
+ });
1300
+ }
1301
+ info(message, options = {}) {
1302
+ return this.show({
1303
+ type: "info",
1304
+ icon: "ℹ",
1305
+ message,
1306
+ ...options
1307
+ });
1308
+ }
1309
+ warning(message, options = {}) {
1310
+ return this.show({
1311
+ type: "warning",
1312
+ icon: "⚠",
1313
+ message,
1314
+ ...options
1315
+ });
1316
+ }
1317
+ }
1318
+ const toastManager = new ToastManager();
1319
+ const Toast = ({ toast, onClose }) => {
1320
+ const [closing, setClosing] = React.useState(false);
1321
+ const handleClose = () => {
1322
+ setClosing(true);
1323
+ setTimeout(() => {
1324
+ onClose();
1325
+ }, 300);
1326
+ };
1327
+ const handleUndo = () => {
1328
+ toast.onUndo?.();
1329
+ handleClose();
1330
+ };
1331
+ return /* @__PURE__ */ jsxRuntime.jsxs(ToastItem, { className: toast.type, $closing: closing, children: [
1332
+ toast.icon && /* @__PURE__ */ jsxRuntime.jsx(Icon, { children: toast.icon }),
1333
+ /* @__PURE__ */ jsxRuntime.jsx(Message, { children: typeof toast.message === "string" ? toast.message : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1334
+ /* @__PURE__ */ jsxRuntime.jsx("strong", { children: toast.message }),
1335
+ toast.description && /* @__PURE__ */ jsxRuntime.jsx("span", { children: toast.description })
1336
+ ] }) }),
1337
+ toast.onUndo && /* @__PURE__ */ jsxRuntime.jsx(UndoButton, { onClick: handleUndo, children: "Undo" }),
1338
+ toast.closeable && /* @__PURE__ */ jsxRuntime.jsx(CloseButton, { onClick: handleClose, children: /* @__PURE__ */ jsxRuntime.jsxs("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: [
1339
+ /* @__PURE__ */ jsxRuntime.jsx("line", { x1: "18", y1: "6", x2: "6", y2: "18" }),
1340
+ /* @__PURE__ */ jsxRuntime.jsx("line", { x1: "6", y1: "6", x2: "18", y2: "18" })
1341
+ ] }) })
1342
+ ] });
1343
+ };
1344
+ const AIToast = () => {
1345
+ const [toasts, setToasts] = React.useState([]);
1346
+ React.useEffect(() => {
1347
+ const unsubscribe = toastManager.subscribe(setToasts);
1348
+ return unsubscribe;
1349
+ }, []);
1350
+ if (toasts.length === 0) {
1351
+ return null;
1352
+ }
1353
+ return /* @__PURE__ */ jsxRuntime.jsx(ToastContainer, { children: toasts.map((toast) => /* @__PURE__ */ jsxRuntime.jsx(
1354
+ Toast,
1355
+ {
1356
+ toast,
1357
+ onClose: () => toastManager.remove(toast.id)
1358
+ },
1359
+ toast.id
1360
+ )) });
1361
+ };
1362
+ class AIAssistantTool {
1363
+ static get isInline() {
1364
+ return true;
1365
+ }
1366
+ static get CSS() {
1367
+ return "ce-inline-tool--ai-assistant";
1368
+ }
1369
+ static get sanitize() {
1370
+ return {
1371
+ span: {
1372
+ class: "ai-suggestion",
1373
+ "data-original": true,
1374
+ "data-corrected": true,
1375
+ "data-type": true
1376
+ }
1377
+ };
1378
+ }
1379
+ static get title() {
1380
+ return "KI-Assistent";
1381
+ }
1382
+ constructor({ api, config }) {
1383
+ this.api = api;
1384
+ this.config = config || {};
1385
+ this.getLicenseKey = this.config.getLicenseKey || (() => window.__MAGIC_EDITOR_LICENSE_KEY__);
1386
+ this.button = null;
1387
+ this.toolbar = null;
1388
+ this.toolbarRoot = null;
1389
+ this.state = false;
1390
+ this.selectedRange = null;
1391
+ this.selectedText = "";
1392
+ this.currentBlockIndex = null;
1393
+ this.apiClient = null;
1394
+ this.isLoading = false;
1395
+ this.lastAction = null;
1396
+ }
1397
+ _initAPIClient() {
1398
+ const licenseKey = this.getLicenseKey();
1399
+ if (licenseKey && !this.apiClient) {
1400
+ this.apiClient = new MagicEditorAPI(licenseKey);
1401
+ }
1402
+ return this.apiClient;
1403
+ }
1404
+ render() {
1405
+ this.button = document.createElement("button");
1406
+ this.button.type = "button";
1407
+ this.button.classList.add(this.api.styles.inlineToolButton);
1408
+ this.button.classList.add(AIAssistantTool.CSS);
1409
+ this.button.innerHTML = this._getIcon();
1410
+ this.button.title = "KI-Assistent (⌘+Shift+G)";
1411
+ return this.button;
1412
+ }
1413
+ _getIcon() {
1414
+ return `<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
1415
+ <path d="M12 3l1.912 5.813a2 2 0 001.275 1.275L21 12l-5.813 1.912a2 2 0 00-1.275 1.275L12 21l-1.912-5.813a2 2 0 00-1.275-1.275L3 12l5.813-1.912a2 2 0 001.275-1.275L12 3z"/>
1416
+ <path d="M5 3v4"/>
1417
+ <path d="M3 5h4"/>
1418
+ <path d="M19 17v4"/>
1419
+ <path d="M17 19h4"/>
1420
+ </svg>`;
1421
+ }
1422
+ surround(range) {
1423
+ const selection = window.getSelection();
1424
+ let text = "";
1425
+ if (selection.rangeCount > 0) {
1426
+ this.selectedRange = selection.getRangeAt(0).cloneRange();
1427
+ text = selection.toString().trim();
1428
+ }
1429
+ if (!text) {
1430
+ const currentBlockIndex = this.api.blocks.getCurrentBlockIndex();
1431
+ const currentBlock = this.api.blocks.getBlockByIndex(currentBlockIndex);
1432
+ if (currentBlock) {
1433
+ this.currentBlockIndex = currentBlockIndex;
1434
+ const blockElement = currentBlock.holder?.querySelector("[contenteditable]") || currentBlock.holder?.querySelector(".ce-paragraph") || currentBlock.holder?.querySelector(".ce-header");
1435
+ if (blockElement) {
1436
+ text = blockElement.textContent?.trim() || "";
1437
+ if (text) {
1438
+ const range2 = document.createRange();
1439
+ range2.selectNodeContents(blockElement);
1440
+ selection.removeAllRanges();
1441
+ selection.addRange(range2);
1442
+ this.selectedRange = range2.cloneRange();
1443
+ }
1444
+ }
1445
+ }
1446
+ }
1447
+ this.selectedText = text;
1448
+ if (!text) {
1449
+ toastManager.warning("Bitte Text auswählen");
1450
+ return;
1451
+ }
1452
+ this._showInlineToolbar();
1453
+ }
1454
+ async _showInlineToolbar() {
1455
+ if (!this._initAPIClient()) {
1456
+ toastManager.error("Keine Lizenz verfügbar");
1457
+ return;
1458
+ }
1459
+ const selection = window.getSelection();
1460
+ if (!selection.rangeCount) return;
1461
+ const rect = selection.getRangeAt(0).getBoundingClientRect();
1462
+ const position = {
1463
+ left: rect.left + rect.width / 2 - 200,
1464
+ // Center toolbar (approximate width)
1465
+ top: rect.bottom + 8
1466
+ };
1467
+ if (position.left < 10) position.left = 10;
1468
+ if (position.left + 400 > window.innerWidth - 10) {
1469
+ position.left = window.innerWidth - 410;
1470
+ }
1471
+ this.toolbar = document.createElement("div");
1472
+ this.toolbar.classList.add("ai-inline-toolbar-container");
1473
+ document.body.appendChild(this.toolbar);
1474
+ this.toolbarRoot = client.createRoot(this.toolbar);
1475
+ this.toolbarRoot.render(
1476
+ React__default.default.createElement(AIInlineToolbar, {
1477
+ position,
1478
+ onAction: this._handleAction.bind(this),
1479
+ loading: this.isLoading,
1480
+ onClose: this._closeToolbar.bind(this)
1481
+ })
1482
+ );
1483
+ }
1484
+ async _handleAction(action, options = {}) {
1485
+ if (!this.apiClient || !this.selectedText) return;
1486
+ this.isLoading = true;
1487
+ this._updateToolbar();
1488
+ try {
1489
+ let result;
1490
+ let originalText = this.selectedText;
1491
+ switch (action) {
1492
+ case "fix":
1493
+ result = await this.apiClient.correct(this.selectedText, "grammar");
1494
+ if (result.data.hasChanges) {
1495
+ this._replaceText(result.data.corrected);
1496
+ toastManager.success(`✓ ${result.data.changes.length} Korrekturen angewendet`, {
1497
+ description: `${result.usage?.remaining || 0} Credits übrig`,
1498
+ onUndo: () => this._replaceText(originalText),
1499
+ duration: 5e3
1500
+ });
1501
+ } else {
1502
+ toastManager.info("Text ist bereits korrekt");
1503
+ }
1504
+ break;
1505
+ case "rewrite":
1506
+ result = await this.apiClient.rewrite(this.selectedText, options);
1507
+ if (result.data.rewritten) {
1508
+ this._replaceText(result.data.rewritten);
1509
+ toastManager.success(`✨ Text umgeschrieben (${options.tone || "standard"})`, {
1510
+ onUndo: () => this._replaceText(originalText),
1511
+ duration: 5e3
1512
+ });
1513
+ }
1514
+ break;
1515
+ case "expand":
1516
+ result = await this.apiClient.expand(this.selectedText);
1517
+ if (result.data.expanded) {
1518
+ this._replaceText(result.data.expanded);
1519
+ toastManager.success("📈 Text erweitert", {
1520
+ onUndo: () => this._replaceText(originalText),
1521
+ duration: 5e3
1522
+ });
1523
+ }
1524
+ break;
1525
+ case "summarize":
1526
+ result = await this.apiClient.summarize(this.selectedText);
1527
+ if (result.data.summary) {
1528
+ this._replaceText(result.data.summary);
1529
+ toastManager.success("📉 Text zusammengefasst", {
1530
+ onUndo: () => this._replaceText(originalText),
1531
+ duration: 5e3
1532
+ });
1533
+ }
1534
+ break;
1535
+ case "continue":
1536
+ result = await this.apiClient.continueWriting(this.selectedText);
1537
+ if (result.data.continuation) {
1538
+ this._appendText(" " + result.data.continuation);
1539
+ toastManager.success("✍️ Text fortgesetzt", {
1540
+ onUndo: () => this._replaceText(originalText),
1541
+ duration: 5e3
1542
+ });
1543
+ }
1544
+ break;
1545
+ case "translate":
1546
+ const langNames = { en: "Englisch", de: "Deutsch", fr: "Französisch", es: "Spanisch" };
1547
+ result = await this.apiClient.translate(this.selectedText, options.language);
1548
+ if (result.data.translated) {
1549
+ this._replaceText(result.data.translated);
1550
+ toastManager.success(`🌍 Übersetzt zu ${langNames[options.language] || options.language}`, {
1551
+ onUndo: () => this._replaceText(originalText),
1552
+ duration: 5e3
1553
+ });
1554
+ }
1555
+ break;
1556
+ default:
1557
+ console.warn("Unknown action:", action);
1558
+ }
1559
+ this._closeToolbar();
1560
+ } catch (err) {
1561
+ console.error("[AI Assistant] Action failed:", err);
1562
+ const isLimitError = err.code === "DAILY_LIMIT_EXCEEDED" || err.code === "MONTHLY_LIMIT_EXCEEDED";
1563
+ if (isLimitError) {
1564
+ toastManager.warning("Limit erreicht", {
1565
+ description: err.message,
1566
+ duration: 5e3
1567
+ });
1568
+ } else {
1569
+ toastManager.error("Fehler", {
1570
+ description: err.message || "Aktion fehlgeschlagen",
1571
+ duration: 4e3
1572
+ });
1573
+ }
1574
+ } finally {
1575
+ this.isLoading = false;
1576
+ this._updateToolbar();
1577
+ }
1578
+ }
1579
+ _replaceText(newText) {
1580
+ if (!this.selectedRange) return;
1581
+ try {
1582
+ const selection = window.getSelection();
1583
+ selection.removeAllRanges();
1584
+ selection.addRange(this.selectedRange);
1585
+ document.execCommand("insertText", false, newText);
1586
+ this.api.blocks.getBlockByIndex(this.api.blocks.getCurrentBlockIndex())?.dispatchChange?.();
1587
+ } catch (err) {
1588
+ console.error("[AI Assistant] Failed to replace text:", err);
1589
+ }
1590
+ }
1591
+ _appendText(additionalText) {
1592
+ if (!this.selectedRange) return;
1593
+ try {
1594
+ const selection = window.getSelection();
1595
+ const range = this.selectedRange.cloneRange();
1596
+ range.collapse(false);
1597
+ selection.removeAllRanges();
1598
+ selection.addRange(range);
1599
+ document.execCommand("insertText", false, additionalText);
1600
+ this.api.blocks.getBlockByIndex(this.api.blocks.getCurrentBlockIndex())?.dispatchChange?.();
1601
+ } catch (err) {
1602
+ console.error("[AI Assistant] Failed to append text:", err);
1603
+ }
1604
+ }
1605
+ _updateToolbar() {
1606
+ if (this.toolbarRoot && this.toolbar) {
1607
+ const selection = window.getSelection();
1608
+ const rect = selection.rangeCount > 0 ? selection.getRangeAt(0).getBoundingClientRect() : { left: 0, bottom: 0, width: 0 };
1609
+ const position = {
1610
+ left: rect.left + rect.width / 2 - 200,
1611
+ top: rect.bottom + 8
1612
+ };
1613
+ this.toolbarRoot.render(
1614
+ React__default.default.createElement(AIInlineToolbar, {
1615
+ position,
1616
+ onAction: this._handleAction.bind(this),
1617
+ loading: this.isLoading,
1618
+ onClose: this._closeToolbar.bind(this)
1619
+ })
1620
+ );
1621
+ }
1622
+ }
1623
+ _closeToolbar() {
1624
+ if (this.toolbarRoot) {
1625
+ this.toolbarRoot.unmount();
1626
+ this.toolbarRoot = null;
1627
+ }
1628
+ if (this.toolbar) {
1629
+ this.toolbar.remove();
1630
+ this.toolbar = null;
1631
+ }
1632
+ this.isLoading = false;
1633
+ this.selectedRange = null;
1634
+ this.selectedText = "";
1635
+ this.currentBlockIndex = null;
1636
+ }
1637
+ checkState(selection) {
1638
+ this.state = selection && !selection.isCollapsed;
1639
+ return this.state;
1640
+ }
1641
+ get shortcut() {
1642
+ return "CMD+SHIFT+G";
1643
+ }
1644
+ destroy() {
1645
+ this._closeToolbar();
1646
+ }
1647
+ }
1648
+ class MediaLibAdapter {
1649
+ // Track if we're currently opening from user action vs restore
1650
+ static _isUserAction = false;
1651
+ /**
1652
+ * Toolbox configuration
1653
+ */
1654
+ static get toolbox() {
1655
+ return {
1656
+ title: "Image (Media Library)",
1657
+ icon: `<svg xmlns="http://www.w3.org/2000/svg" width="17" height="15" viewBox="0 0 336 276">
1658
+ <path d="M291 150.242V79c0-18.778-15.222-34-34-34H79c-18.778 0-34 15.222-34 34v42.264l67.179-44.192 80.398 71.614 56.686-29.14L291 150.242zm-.345 51.622l-42.3-30.246-56.3 29.884-80.773-66.925L45 174.187V197c0 18.778 15.222 34 34 34h178c17.126 0 31.295-12.663 33.655-29.136zM79 0h178c43.63 0 79 35.37 79 79v118c0 43.63-35.37 79-79 79H79c-43.63 0-79-35.37-79-79V79C0 35.37 35.37 0 79 0z"/>
1659
+ </svg>`
1660
+ };
1661
+ }
1662
+ /**
1663
+ * Tool is not inline
1664
+ */
1665
+ static get isInline() {
1666
+ return false;
1667
+ }
1668
+ /**
1669
+ * Constructor
1670
+ * @param {object} params - Tool parameters
1671
+ * @param {object} params.api - EditorJS API
1672
+ * @param {object} params.config - Tool config
1673
+ * @param {object} params.data - Block data (when restoring from saved state)
1674
+ */
1675
+ constructor({ api, config, data }) {
1676
+ this.api = api;
1677
+ this.config = config || {};
1678
+ this.data = data || {};
1679
+ this._isRestore = !!(data && data.type === "mediaLibraryStrapi");
1680
+ }
1681
+ /**
1682
+ * Render tool
1683
+ * Opens Media Library dialog only when user clicks the toolbox
1684
+ */
1685
+ render() {
1686
+ const wrapper = document.createElement("div");
1687
+ wrapper.classList.add("media-lib-placeholder");
1688
+ if (!this._isRestore) {
1689
+ const currentIndex = this.api.blocks.getCurrentBlockIndex();
1690
+ if (this.config.mediaLibToggleFunc) {
1691
+ setTimeout(() => {
1692
+ this.config.mediaLibToggleFunc(currentIndex);
1693
+ }, 50);
1694
+ }
1695
+ wrapper.innerHTML = '<p style="color: #7C3AED; text-align: center; padding: 20px; font-size: 14px;">📷 Media Library wird geöffnet...</p>';
1696
+ } else {
1697
+ wrapper.innerHTML = '<p style="color: #999; text-align: center; padding: 10px; font-size: 12px;">⚠️ Ungültiger Block wird entfernt...</p>';
1698
+ setTimeout(() => {
1699
+ try {
1700
+ const currentIndex = this.api.blocks.getCurrentBlockIndex();
1701
+ this.api.blocks.delete(currentIndex);
1702
+ } catch (e) {
1703
+ console.warn("[Magic Editor X] Could not auto-delete mediaLib block:", e);
1704
+ }
1705
+ }, 100);
1706
+ }
1707
+ return wrapper;
1708
+ }
1709
+ /**
1710
+ * Save data
1711
+ * Returns undefined to prevent saving this placeholder block
1712
+ */
1713
+ save() {
1714
+ return void 0;
1715
+ }
1716
+ /**
1717
+ * Validate saved data
1718
+ * Always return false so this block type is never persisted
1719
+ */
1720
+ validate(savedData) {
1721
+ return false;
1722
+ }
1723
+ /**
1724
+ * Sanitizer config
1725
+ */
1726
+ static get sanitize() {
1727
+ return {
1728
+ type: false
1729
+ };
1730
+ }
1731
+ }
1732
+ const getAuthToken = () => {
1733
+ try {
1734
+ const token = sessionStorage.getItem("jwtToken");
1735
+ if (token) {
1736
+ return JSON.parse(token);
1737
+ }
1738
+ const localToken = localStorage.getItem("jwtToken");
1739
+ if (localToken) {
1740
+ return JSON.parse(localToken);
1741
+ }
1742
+ return null;
1743
+ } catch (e) {
1744
+ console.warn("[Magic Editor X] Could not get auth token:", e);
1745
+ return null;
1746
+ }
1747
+ };
1748
+ const getTools = ({ mediaLibToggleFunc, pluginId }) => {
1749
+ const token = getAuthToken();
1750
+ const authHeader = token ? `Bearer ${token}` : "";
1751
+ return {
1752
+ // ============================================
1753
+ // OFFICIAL BLOCK TOOLS (16 Tools)
1754
+ // ============================================
1755
+ /**
1756
+ * Header Tool
1757
+ * @see https://github.com/editor-js/header
1758
+ */
1759
+ header: {
1760
+ class: Header__default.default,
1761
+ inlineToolbar: true,
1762
+ tunes: ["alignmentTune"],
1763
+ config: {
1764
+ placeholder: "Enter a heading",
1765
+ levels: [1, 2, 3, 4, 5, 6],
1766
+ defaultLevel: 2
1767
+ },
1768
+ shortcut: "CMD+SHIFT+H"
1769
+ },
1770
+ /**
1771
+ * Paragraph Tool (default)
1772
+ * @see https://github.com/editor-js/paragraph
1773
+ */
1774
+ paragraph: {
1775
+ class: Paragraph__default.default,
1776
+ inlineToolbar: true,
1777
+ tunes: ["alignmentTune", "indentTune"],
1778
+ config: {
1779
+ placeholder: "Start writing or press Tab to add a block...",
1780
+ preserveBlank: true
1781
+ }
1782
+ },
1783
+ /**
1784
+ * Nested List Tool
1785
+ * @see https://github.com/editor-js/nested-list
1786
+ */
1787
+ list: {
1788
+ class: NestedList__default.default,
1789
+ inlineToolbar: true,
1790
+ tunes: ["indentTune"],
1791
+ config: {
1792
+ defaultStyle: "unordered"
1793
+ },
1794
+ shortcut: "CMD+SHIFT+L"
1795
+ },
1796
+ /**
1797
+ * Checklist Tool
1798
+ * @see https://github.com/editor-js/checklist
1799
+ */
1800
+ checklist: {
1801
+ class: Checklist__default.default,
1802
+ inlineToolbar: true,
1803
+ shortcut: "CMD+SHIFT+C"
1804
+ },
1805
+ /**
1806
+ * Quote Tool
1807
+ * @see https://github.com/editor-js/quote
1808
+ */
1809
+ quote: {
1810
+ class: Quote__default.default,
1811
+ inlineToolbar: true,
1812
+ tunes: ["alignmentTune"],
1813
+ config: {
1814
+ quotePlaceholder: "Enter a quote",
1815
+ captionPlaceholder: "Quote author"
1816
+ },
1817
+ shortcut: "CMD+SHIFT+Q"
1818
+ },
1819
+ /**
1820
+ * Warning Tool
1821
+ * @see https://github.com/editor-js/warning
1822
+ */
1823
+ warning: {
1824
+ class: Warning__default.default,
1825
+ inlineToolbar: true,
1826
+ config: {
1827
+ titlePlaceholder: "Warning title",
1828
+ messagePlaceholder: "Warning message"
1829
+ },
1830
+ shortcut: "CMD+SHIFT+W"
1831
+ },
1832
+ /**
1833
+ * Code Tool (basic)
1834
+ * @see https://github.com/editor-js/code
1835
+ */
1836
+ code: {
1837
+ class: Code__default.default,
1838
+ config: {
1839
+ placeholder: "Enter code here..."
1840
+ },
1841
+ shortcut: "CMD+SHIFT+P"
1842
+ },
1843
+ /**
1844
+ * Delimiter Tool
1845
+ * @see https://github.com/editor-js/delimiter
1846
+ */
1847
+ delimiter: {
1848
+ class: Delimiter__default.default,
1849
+ shortcut: "CMD+SHIFT+D"
1850
+ },
1851
+ /**
1852
+ * Table Tool
1853
+ * @see https://github.com/editor-js/table
1854
+ */
1855
+ table: {
1856
+ class: Table__default.default,
1857
+ inlineToolbar: true,
1858
+ config: {
1859
+ rows: 2,
1860
+ cols: 3,
1861
+ withHeadings: true
1862
+ },
1863
+ shortcut: "CMD+SHIFT+T"
1864
+ },
1865
+ /**
1866
+ * Embed Tool
1867
+ * @see https://github.com/editor-js/embed
1868
+ */
1869
+ embed: {
1870
+ class: Embed__default.default,
1871
+ config: {
1872
+ services: {
1873
+ youtube: true,
1874
+ vimeo: true,
1875
+ twitter: true,
1876
+ instagram: true,
1877
+ codepen: true,
1878
+ codesandbox: true,
1879
+ github: true,
1880
+ gfycat: true,
1881
+ imgur: true,
1882
+ pinterest: true,
1883
+ twitch: true,
1884
+ miro: true,
1885
+ figma: true,
1886
+ aparat: true,
1887
+ facebook: true
1888
+ }
1889
+ }
1890
+ },
1891
+ /**
1892
+ * Raw HTML Tool
1893
+ * @see https://github.com/editor-js/raw
1894
+ */
1895
+ raw: {
1896
+ class: Raw__default.default,
1897
+ config: {
1898
+ placeholder: "Enter raw HTML..."
1899
+ }
1900
+ },
1901
+ /**
1902
+ * Link Tool
1903
+ * @see https://github.com/editor-js/link
1904
+ */
1905
+ linkTool: {
1906
+ class: LinkTool__default.default,
1907
+ config: {
1908
+ endpoint: `/api/${pluginId}/link`,
1909
+ headers: {
1910
+ Authorization: authHeader
1911
+ }
1912
+ }
1913
+ },
1914
+ /**
1915
+ * Image Tool (with Upload)
1916
+ * @see https://github.com/editor-js/image
1917
+ */
1918
+ image: {
1919
+ class: Image__default.default,
1920
+ config: {
1921
+ field: "files.image",
1922
+ additionalRequestData: {
1923
+ data: JSON.stringify({})
1924
+ },
1925
+ additionalRequestHeaders: {
1926
+ Authorization: authHeader
1927
+ },
1928
+ endpoints: {
1929
+ byUrl: `/api/${pluginId}/image/byUrl`
1930
+ },
1931
+ uploader: {
1932
+ async uploadByFile(file) {
1933
+ const formData = new FormData();
1934
+ formData.append("data", JSON.stringify({}));
1935
+ formData.append("files.image", file);
1936
+ try {
1937
+ const response = await fetch(`/api/${pluginId}/image/byFile`, {
1938
+ method: "POST",
1939
+ headers: {
1940
+ Authorization: authHeader
1941
+ },
1942
+ body: formData
1943
+ });
1944
+ return await response.json();
1945
+ } catch (error) {
1946
+ console.error("[Magic Editor X] Upload error:", error);
1947
+ return { success: 0, message: error.message };
1948
+ }
1949
+ }
1950
+ },
1951
+ captionPlaceholder: "Image caption",
1952
+ buttonContent: "Select image"
1953
+ }
1954
+ },
1955
+ /**
1956
+ * Simple Image Tool (URL only)
1957
+ * @see https://github.com/editor-js/simple-image
1958
+ */
1959
+ simpleImage: {
1960
+ class: SimpleImage__default.default
1961
+ },
1962
+ /**
1963
+ * Attaches Tool
1964
+ * @see https://github.com/editor-js/attaches
1965
+ */
1966
+ attaches: {
1967
+ class: Attaches__default.default,
1968
+ config: {
1969
+ endpoint: `/api/${pluginId}/file/upload`,
1970
+ field: "file",
1971
+ types: "*",
1972
+ buttonText: "Select file to upload",
1973
+ errorMessage: "File upload failed",
1974
+ additionalRequestHeaders: {
1975
+ Authorization: authHeader
1976
+ }
1977
+ }
1978
+ },
1979
+ /**
1980
+ * Personality Tool
1981
+ * @see https://github.com/editor-js/personality
1982
+ */
1983
+ personality: {
1984
+ class: Personality__default.default,
1985
+ config: {
1986
+ endpoint: `/api/${pluginId}/image/byFile`,
1987
+ field: "files.image",
1988
+ additionalRequestHeaders: {
1989
+ Authorization: authHeader
1990
+ },
1991
+ namePlaceholder: "Name",
1992
+ descriptionPlaceholder: "Description / Title",
1993
+ linkPlaceholder: "Link (optional)"
1994
+ }
1995
+ },
1996
+ /**
1997
+ * Media Library Tool (Custom)
1998
+ * Strapi Media Library integration
1999
+ */
2000
+ mediaLib: {
2001
+ class: MediaLibAdapter,
2002
+ config: {
2003
+ mediaLibToggleFunc
2004
+ }
2005
+ },
2006
+ // ============================================
2007
+ // COMMUNITY BLOCK TOOLS (4 Tools)
2008
+ // ============================================
2009
+ /**
2010
+ * Alert Tool
2011
+ * @see https://github.com/vishaltelangre/editorjs-alert
2012
+ * Colorful alert boxes (info, success, warning, danger)
2013
+ */
2014
+ alert: {
2015
+ class: Alert__default.default,
2016
+ inlineToolbar: true,
2017
+ config: {
2018
+ defaultType: "info",
2019
+ messagePlaceholder: "Enter your message..."
2020
+ },
2021
+ shortcut: "CMD+SHIFT+A"
2022
+ },
2023
+ /**
2024
+ * Toggle Block Tool
2025
+ * @see https://github.com/kommitters/editorjs-toggle-block
2026
+ * Collapsible/expandable content blocks (FAQ, Accordions)
2027
+ */
2028
+ toggle: {
2029
+ class: ToggleBlock__default.default,
2030
+ inlineToolbar: true,
2031
+ config: {
2032
+ placeholder: "Toggle title",
2033
+ defaultContent: "Toggle content..."
2034
+ }
2035
+ },
2036
+ /**
2037
+ * CodeFlask Tool (with Syntax Highlighting)
2038
+ * @see https://github.com/calumk/editorjs-codeflask
2039
+ * Code blocks with syntax highlighting
2040
+ */
2041
+ codeFlask: {
2042
+ class: CodeFlask__default.default,
2043
+ config: {
2044
+ language: "javascript"
2045
+ }
2046
+ },
2047
+ /**
2048
+ * Button Tool (Custom Implementation)
2049
+ * CTA buttons with customizable text, link, and style
2050
+ * Secure implementation without eval()
2051
+ */
2052
+ button: {
2053
+ class: ButtonTool,
2054
+ inlineToolbar: false
2055
+ },
2056
+ // ============================================
2057
+ // OFFICIAL INLINE TOOLS (3 Tools)
2058
+ // ============================================
2059
+ /**
2060
+ * Marker (Highlight) Tool
2061
+ * @see https://github.com/editor-js/marker
2062
+ */
2063
+ marker: {
2064
+ class: Marker__default.default,
2065
+ shortcut: "CMD+SHIFT+M"
2066
+ },
2067
+ /**
2068
+ * Inline Code Tool
2069
+ * @see https://github.com/editor-js/inline-code
2070
+ */
2071
+ inlineCode: {
2072
+ class: InlineCode__default.default,
2073
+ shortcut: "CMD+SHIFT+I"
2074
+ },
2075
+ /**
2076
+ * Underline Tool
2077
+ * @see https://github.com/editor-js/underline
2078
+ */
2079
+ underline: {
2080
+ class: Underline__default.default,
2081
+ shortcut: "CMD+U"
2082
+ },
2083
+ // ============================================
2084
+ // COMMUNITY INLINE TOOLS (3 Tools)
2085
+ // ============================================
2086
+ /**
2087
+ * Strikethrough Tool
2088
+ * @see https://github.com/nicosrm/strikethrough
2089
+ */
2090
+ strikethrough: {
2091
+ class: Strikethrough__default.default,
2092
+ shortcut: "CMD+SHIFT+S"
2093
+ },
2094
+ /**
2095
+ * Tooltip Tool
2096
+ * @see https://github.com/kommitters/editorjs-tooltip
2097
+ * Add tooltips to text
2098
+ */
2099
+ tooltip: {
2100
+ class: Tooltip__default.default,
2101
+ config: {
2102
+ location: "top",
2103
+ highlightColor: "#FFEFD5",
2104
+ underline: true,
2105
+ backgroundColor: "#1e293b",
2106
+ textColor: "#ffffff"
2107
+ }
2108
+ },
2109
+ /**
2110
+ * Hyperlink Tool (Custom Implementation)
2111
+ * Links with target and rel attributes
2112
+ * Secure implementation without eval()
2113
+ */
2114
+ hyperlink: {
2115
+ class: HyperlinkTool,
2116
+ config: {
2117
+ shortcut: "CMD+K",
2118
+ target: "_blank",
2119
+ rel: "noopener noreferrer",
2120
+ availableTargets: ["_blank", "_self", "_parent", "_top"],
2121
+ availableRels: ["nofollow", "noreferrer", "noopener", "sponsored", "ugc"]
2122
+ }
2123
+ },
2124
+ /**
2125
+ * AI Assistant Tool (Custom Implementation)
2126
+ * AI-powered text corrections (grammar, style, rewrite)
2127
+ */
2128
+ aiAssistant: {
2129
+ class: AIAssistantTool,
2130
+ config: {
2131
+ apiBaseUrl: "https://magicapi.fitlex.me/api/magic-editor",
2132
+ getLicenseKey: () => window.__MAGIC_EDITOR_LICENSE_KEY__
2133
+ },
2134
+ shortcut: "CMD+SHIFT+G"
2135
+ },
2136
+ // ============================================
2137
+ // TUNES (3 Tunes)
2138
+ // ============================================
2139
+ /**
2140
+ * Text Variant Tune
2141
+ * @see https://github.com/editor-js/text-variant-tune
2142
+ */
2143
+ textVariant: TextVariantTune__default.default,
2144
+ /**
2145
+ * Alignment Tune
2146
+ * @see https://github.com/kaaaaaaaaaaai/editorjs-alignment-blocktune
2147
+ * Text alignment (left, center, right, justify)
2148
+ */
2149
+ alignmentTune: {
2150
+ class: AlignmentTune__default.default,
2151
+ config: {
2152
+ default: "left",
2153
+ blocks: {
2154
+ header: "center"
2155
+ }
2156
+ }
2157
+ },
2158
+ /**
2159
+ * Indent Tune
2160
+ * @see https://github.com/kommitters/editorjs-indent-tune
2161
+ * Block indentation
2162
+ */
2163
+ indentTune: {
2164
+ class: IndentTune__default.default,
2165
+ config: {
2166
+ maxIndent: 5,
2167
+ indentSize: 30,
2168
+ multiblock: true,
2169
+ tuneName: "indentTune"
2170
+ }
2171
+ }
2172
+ };
2173
+ };
2174
+ const initUndoRedo = (editor) => {
2175
+ return new Undo__default.default({ editor });
2176
+ };
2177
+ const initDragDrop = (editor) => {
2178
+ return new DragDrop__default.default(editor);
2179
+ };
2180
+ exports.AIInlineToolbar = AIInlineToolbar;
2181
+ exports.AIToast = AIToast;
2182
+ exports.MagicEditorAPI = MagicEditorAPI;
2183
+ exports.getTools = getTools;
2184
+ exports.initDragDrop = initDragDrop;
2185
+ exports.initUndoRedo = initUndoRedo;
2186
+ exports.toastManager = toastManager;