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