magic-editor-x 1.3.7 → 1.4.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 (25) hide show
  1. package/README.md +131 -2
  2. package/dist/_chunks/{App-BrAGe1KP.js → App-ByYQ99dO.js} +4 -4
  3. package/dist/_chunks/{App-SlDn2xdO.mjs → App-C8q91Ico.mjs} +4 -4
  4. package/dist/_chunks/CustomBlocksPage-BkmF2iOQ.js +1518 -0
  5. package/dist/_chunks/CustomBlocksPage-rLRB05j0.mjs +1516 -0
  6. package/dist/_chunks/{LicensePage-C6TBYCWM.js → LicensePage-Bdn7zWU2.js} +30 -12
  7. package/dist/_chunks/{LicensePage-zaYXFrKs.mjs → LicensePage-DR_Cwyfw.mjs} +30 -12
  8. package/dist/_chunks/{LiveCollaborationPanel-yNC8e1Qb.mjs → LiveCollaborationPanel-0tplv17N.mjs} +1 -1
  9. package/dist/_chunks/{LiveCollaborationPanel-CGt57XG1.js → LiveCollaborationPanel-BUHIq0CQ.js} +1 -1
  10. package/dist/_chunks/{Settings-BH0Ttu_5.js → Settings-B5mffA2O.js} +1 -1
  11. package/dist/_chunks/{Settings-BXoP5tm9.mjs → Settings-FfpVHlpw.mjs} +1 -1
  12. package/dist/_chunks/{getTranslation-D2oq0PyC.mjs → getTranslation-Bk8tKbUs.mjs} +1 -1
  13. package/dist/_chunks/{getTranslation-D21ValYk.js → getTranslation-DOJ3x1SL.js} +1 -1
  14. package/dist/_chunks/{index-BnTvuHf2.js → index-C5DuaTDl.js} +15 -5
  15. package/dist/_chunks/{index-tbHD7slZ.mjs → index-DkpTkVe7.mjs} +15 -5
  16. package/dist/_chunks/{index-Czk5HVLJ.js → index-DzixAi6O.js} +1183 -16
  17. package/dist/_chunks/{index-CUTx2oym.mjs → index-zOiCW1bZ.mjs} +1183 -16
  18. package/dist/_chunks/{tools-D4Y_iEui.js → tools-BSMn5LLQ.js} +17 -2
  19. package/dist/_chunks/{tools-BGR6Cw4p.mjs → tools-uudZx91W.mjs} +17 -2
  20. package/dist/admin/index.js +1 -1
  21. package/dist/admin/index.mjs +1 -1
  22. package/dist/server/index.js +1172 -9
  23. package/dist/server/index.mjs +1172 -9
  24. package/dist/style.css +116 -7
  25. package/package.json +1 -1
@@ -2,13 +2,13 @@
2
2
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
3
  const jsxRuntime = require("react/jsx-runtime");
4
4
  const React = require("react");
5
- const getTranslation = require("./getTranslation-D21ValYk.js");
5
+ const getTranslation = require("./getTranslation-DOJ3x1SL.js");
6
6
  const styled = require("styled-components");
7
7
  const outline = require("@heroicons/react/24/outline");
8
8
  const EditorJS = require("@editorjs/editorjs");
9
- const tools = require("./tools-D4Y_iEui.js");
9
+ const tools = require("./tools-BSMn5LLQ.js");
10
10
  const admin = require("@strapi/strapi/admin");
11
- const index = require("./index-BnTvuHf2.js");
11
+ const index = require("./index-C5DuaTDl.js");
12
12
  const socket_ioClient = require("socket.io-client");
13
13
  const Y = require("yjs");
14
14
  const yIndexeddb = require("y-indexeddb");
@@ -35,6 +35,732 @@ const React__default = /* @__PURE__ */ _interopDefault(React);
35
35
  const styled__default = /* @__PURE__ */ _interopDefault(styled);
36
36
  const EditorJS__default = /* @__PURE__ */ _interopDefault(EditorJS);
37
37
  const Y__namespace = /* @__PURE__ */ _interopNamespace(Y);
38
+ const ICONS = {
39
+ toolbox: '<svg width="20" height="20" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M19 3H5C3.89543 3 3 3.89543 3 5V19C3 20.1046 3.89543 21 5 21H19C20.1046 21 21 20.1046 21 19V5C21 3.89543 20.1046 3 19 3Z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><path d="M12 8V16" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><path d="M8 12H16" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>',
40
+ linked: '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>',
41
+ unlink: '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M18.84 12.25l1.72-1.71h-.02a5.004 5.004 0 00-.12-7.07 5.006 5.006 0 00-6.95 0l-1.72 1.71" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><path d="M5.17 11.75l-1.71 1.71a5.004 5.004 0 00.12 7.07 5.006 5.006 0 006.95 0l1.71-1.71" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><path d="M8 2v3M2 8h3M16 22v-3M19 16h3" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>',
42
+ refresh: '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M23 4v6h-6" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><path d="M1 20v-6h6" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><path d="M3.51 9a9 9 0 0 1 14.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0 0 20.49 15" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>'
43
+ };
44
+ const STYLES = `
45
+ .embedded-entry-block {
46
+ border: 2px dashed #e2e8f0;
47
+ border-radius: 8px;
48
+ padding: 16px;
49
+ margin: 8px 0;
50
+ background: #f8fafc;
51
+ transition: all 0.2s ease;
52
+ }
53
+
54
+ .embedded-entry-block:hover {
55
+ border-color: #4945FF;
56
+ background: #f1f5f9;
57
+ }
58
+
59
+ .embedded-entry-block--selected {
60
+ border-color: #4945FF;
61
+ border-style: solid;
62
+ background: #eef2ff;
63
+ }
64
+
65
+ .embedded-entry-block__placeholder {
66
+ display: flex;
67
+ flex-direction: column;
68
+ align-items: center;
69
+ justify-content: center;
70
+ min-height: 100px;
71
+ cursor: pointer;
72
+ color: #64748b;
73
+ }
74
+
75
+ .embedded-entry-block__placeholder:hover {
76
+ color: #4945FF;
77
+ }
78
+
79
+ .embedded-entry-block__placeholder-icon {
80
+ margin-bottom: 8px;
81
+ opacity: 0.7;
82
+ }
83
+
84
+ .embedded-entry-block__placeholder-text {
85
+ font-size: 14px;
86
+ font-weight: 500;
87
+ }
88
+
89
+ .embedded-entry-block__placeholder-hint {
90
+ font-size: 12px;
91
+ margin-top: 4px;
92
+ opacity: 0.7;
93
+ }
94
+
95
+ .embedded-entry-block__preview {
96
+ display: flex;
97
+ align-items: flex-start;
98
+ gap: 12px;
99
+ }
100
+
101
+ .embedded-entry-block__preview-icon {
102
+ flex-shrink: 0;
103
+ width: 40px;
104
+ height: 40px;
105
+ display: flex;
106
+ align-items: center;
107
+ justify-content: center;
108
+ background: #4945FF;
109
+ color: white;
110
+ border-radius: 8px;
111
+ }
112
+
113
+ .embedded-entry-block__preview-content {
114
+ flex: 1;
115
+ min-width: 0;
116
+ }
117
+
118
+ .embedded-entry-block__preview-type {
119
+ font-size: 11px;
120
+ font-weight: 600;
121
+ text-transform: uppercase;
122
+ color: #4945FF;
123
+ letter-spacing: 0.5px;
124
+ margin-bottom: 4px;
125
+ }
126
+
127
+ .embedded-entry-block__preview-title {
128
+ font-size: 16px;
129
+ font-weight: 600;
130
+ color: #1e293b;
131
+ margin-bottom: 4px;
132
+ overflow: hidden;
133
+ text-overflow: ellipsis;
134
+ white-space: nowrap;
135
+ }
136
+
137
+ .embedded-entry-block__preview-fields {
138
+ font-size: 13px;
139
+ color: #64748b;
140
+ display: flex;
141
+ flex-wrap: wrap;
142
+ gap: 8px;
143
+ }
144
+
145
+ .embedded-entry-block__preview-field {
146
+ background: #e2e8f0;
147
+ padding: 2px 8px;
148
+ border-radius: 4px;
149
+ font-size: 12px;
150
+ }
151
+
152
+ .embedded-entry-block__preview-field-label {
153
+ font-weight: 600;
154
+ margin-right: 4px;
155
+ }
156
+
157
+ .embedded-entry-block__actions {
158
+ display: flex;
159
+ gap: 4px;
160
+ margin-left: 12px;
161
+ }
162
+
163
+ .embedded-entry-block__action {
164
+ padding: 6px;
165
+ background: #fff;
166
+ border: 1px solid #e2e8f0;
167
+ border-radius: 4px;
168
+ cursor: pointer;
169
+ color: #64748b;
170
+ transition: all 0.15s ease;
171
+ }
172
+
173
+ .embedded-entry-block__action:hover {
174
+ background: #f1f5f9;
175
+ color: #4945FF;
176
+ border-color: #4945FF;
177
+ }
178
+
179
+ .embedded-entry-block__action--danger:hover {
180
+ color: #ef4444;
181
+ border-color: #ef4444;
182
+ background: #fef2f2;
183
+ }
184
+
185
+ /* Picker Modal Styles */
186
+ .embedded-entry-picker {
187
+ position: fixed;
188
+ top: 0;
189
+ left: 0;
190
+ right: 0;
191
+ bottom: 0;
192
+ background: rgba(0,0,0,0.5);
193
+ display: flex;
194
+ align-items: center;
195
+ justify-content: center;
196
+ z-index: 999999;
197
+ }
198
+
199
+ .embedded-entry-picker__modal {
200
+ background: white;
201
+ border-radius: 12px;
202
+ box-shadow: 0 20px 60px rgba(0,0,0,0.2);
203
+ width: 90%;
204
+ max-width: 600px;
205
+ max-height: 80vh;
206
+ display: flex;
207
+ flex-direction: column;
208
+ overflow: hidden;
209
+ }
210
+
211
+ .embedded-entry-picker__header {
212
+ padding: 16px 20px;
213
+ border-bottom: 1px solid #e2e8f0;
214
+ display: flex;
215
+ align-items: center;
216
+ justify-content: space-between;
217
+ }
218
+
219
+ .embedded-entry-picker__title {
220
+ font-size: 18px;
221
+ font-weight: 600;
222
+ color: #1e293b;
223
+ }
224
+
225
+ .embedded-entry-picker__close {
226
+ padding: 4px;
227
+ cursor: pointer;
228
+ color: #64748b;
229
+ background: none;
230
+ border: none;
231
+ font-size: 24px;
232
+ line-height: 1;
233
+ }
234
+
235
+ .embedded-entry-picker__search {
236
+ padding: 12px 20px;
237
+ border-bottom: 1px solid #e2e8f0;
238
+ }
239
+
240
+ .embedded-entry-picker__search-input {
241
+ width: 100%;
242
+ padding: 10px 14px;
243
+ border: 1px solid #e2e8f0;
244
+ border-radius: 8px;
245
+ font-size: 14px;
246
+ outline: none;
247
+ }
248
+
249
+ .embedded-entry-picker__search-input:focus {
250
+ border-color: #4945FF;
251
+ box-shadow: 0 0 0 3px rgba(73, 69, 255, 0.1);
252
+ }
253
+
254
+ .embedded-entry-picker__list {
255
+ flex: 1;
256
+ overflow-y: auto;
257
+ padding: 8px;
258
+ }
259
+
260
+ .embedded-entry-picker__item {
261
+ padding: 12px 16px;
262
+ border-radius: 8px;
263
+ cursor: pointer;
264
+ display: flex;
265
+ align-items: center;
266
+ gap: 12px;
267
+ transition: background 0.15s ease;
268
+ }
269
+
270
+ .embedded-entry-picker__item:hover {
271
+ background: #f1f5f9;
272
+ }
273
+
274
+ .embedded-entry-picker__item-icon {
275
+ width: 36px;
276
+ height: 36px;
277
+ background: #4945FF;
278
+ color: white;
279
+ border-radius: 6px;
280
+ display: flex;
281
+ align-items: center;
282
+ justify-content: center;
283
+ flex-shrink: 0;
284
+ }
285
+
286
+ .embedded-entry-picker__item-content {
287
+ flex: 1;
288
+ min-width: 0;
289
+ }
290
+
291
+ .embedded-entry-picker__item-title {
292
+ font-weight: 500;
293
+ color: #1e293b;
294
+ margin-bottom: 2px;
295
+ overflow: hidden;
296
+ text-overflow: ellipsis;
297
+ white-space: nowrap;
298
+ }
299
+
300
+ .embedded-entry-picker__item-meta {
301
+ font-size: 12px;
302
+ color: #64748b;
303
+ }
304
+
305
+ .embedded-entry-picker__empty {
306
+ padding: 40px 20px;
307
+ text-align: center;
308
+ color: #64748b;
309
+ }
310
+
311
+ .embedded-entry-picker__loading {
312
+ padding: 40px 20px;
313
+ text-align: center;
314
+ color: #64748b;
315
+ }
316
+
317
+ .embedded-entry-picker__error {
318
+ padding: 20px;
319
+ text-align: center;
320
+ color: #ef4444;
321
+ background: #fef2f2;
322
+ margin: 8px;
323
+ border-radius: 8px;
324
+ }
325
+ `;
326
+ class EmbeddedEntryTool {
327
+ /**
328
+ * Toolbox configuration
329
+ */
330
+ static get toolbox() {
331
+ return {
332
+ title: "Embedded Entry",
333
+ icon: ICONS.toolbox
334
+ };
335
+ }
336
+ /**
337
+ * Enable read-only mode support
338
+ */
339
+ static get isReadOnlySupported() {
340
+ return true;
341
+ }
342
+ /**
343
+ * Paste configuration (disabled)
344
+ */
345
+ static get pasteConfig() {
346
+ return false;
347
+ }
348
+ /**
349
+ * Constructor
350
+ * @param {object} params - Editor.js tool params
351
+ * @param {object} params.data - Saved block data
352
+ * @param {object} params.api - Editor.js API
353
+ * @param {object} params.config - Tool configuration
354
+ * @param {boolean} params.readOnly - Read-only mode flag
355
+ */
356
+ constructor({ data, api, config, readOnly }) {
357
+ this.api = api;
358
+ this.readOnly = readOnly;
359
+ this.config = config || {};
360
+ this.contentType = this.config.contentType || null;
361
+ this.displayFields = this.config.displayFields || ["id", "title", "name"];
362
+ this.allowedTypes = this.config.allowedTypes || null;
363
+ this.titleField = this.config.titleField || "title";
364
+ this.previewFields = this.config.previewFields || [];
365
+ this.data = {
366
+ entry: data?.entry || null,
367
+ contentType: data?.contentType || this.contentType,
368
+ displayMode: data?.displayMode || "card"
369
+ // card, inline, minimal
370
+ };
371
+ this.wrapper = null;
372
+ this.picker = null;
373
+ this._injectStyles();
374
+ }
375
+ /**
376
+ * Inject CSS styles into document
377
+ */
378
+ _injectStyles() {
379
+ const styleId = "embedded-entry-tool-styles";
380
+ if (!document.getElementById(styleId)) {
381
+ const style = document.createElement("style");
382
+ style.id = styleId;
383
+ style.textContent = STYLES;
384
+ document.head.appendChild(style);
385
+ }
386
+ }
387
+ /**
388
+ * Render the block
389
+ * @returns {HTMLElement}
390
+ */
391
+ render() {
392
+ this.wrapper = document.createElement("div");
393
+ this.wrapper.classList.add("embedded-entry-block");
394
+ if (this.data.entry) {
395
+ this._renderPreview();
396
+ } else {
397
+ this._renderPlaceholder();
398
+ }
399
+ return this.wrapper;
400
+ }
401
+ /**
402
+ * Render placeholder (no entry selected)
403
+ */
404
+ _renderPlaceholder() {
405
+ if (this.readOnly) {
406
+ this.wrapper.innerHTML = `
407
+ <div class="embedded-entry-block__placeholder">
408
+ <span class="embedded-entry-block__placeholder-text">No entry selected</span>
409
+ </div>
410
+ `;
411
+ return;
412
+ }
413
+ const placeholder = document.createElement("div");
414
+ placeholder.classList.add("embedded-entry-block__placeholder");
415
+ placeholder.innerHTML = `
416
+ <div class="embedded-entry-block__placeholder-icon">${ICONS.toolbox}</div>
417
+ <span class="embedded-entry-block__placeholder-text">Click to embed an entry</span>
418
+ <span class="embedded-entry-block__placeholder-hint">${this.contentType ? this.contentType : "Select any content type"}</span>
419
+ `;
420
+ placeholder.addEventListener("click", () => this._openPicker());
421
+ this.wrapper.innerHTML = "";
422
+ this.wrapper.appendChild(placeholder);
423
+ }
424
+ /**
425
+ * Render entry preview
426
+ */
427
+ _renderPreview() {
428
+ const entry = this.data.entry;
429
+ const typeDisplay = this._formatContentType(this.data.contentType || entry.contentType);
430
+ const title = entry[this.titleField] || entry.title || entry.name || `Entry #${entry.id}`;
431
+ let fieldsHtml = "";
432
+ const fieldsToShow = this.previewFields.length > 0 ? this.previewFields : this.displayFields;
433
+ fieldsToShow.forEach((field) => {
434
+ if (entry[field] !== void 0 && field !== this.titleField) {
435
+ const value = typeof entry[field] === "object" ? JSON.stringify(entry[field]).substring(0, 50) : String(entry[field]).substring(0, 100);
436
+ fieldsHtml += `
437
+ <span class="embedded-entry-block__preview-field">
438
+ <span class="embedded-entry-block__preview-field-label">${field}:</span>
439
+ ${value}
440
+ </span>
441
+ `;
442
+ }
443
+ });
444
+ const actionsHtml = this.readOnly ? "" : `
445
+ <div class="embedded-entry-block__actions">
446
+ <button class="embedded-entry-block__action" data-action="change" title="Change entry">
447
+ ${ICONS.refresh}
448
+ </button>
449
+ <button class="embedded-entry-block__action embedded-entry-block__action--danger" data-action="remove" title="Remove entry">
450
+ ${ICONS.unlink}
451
+ </button>
452
+ </div>
453
+ `;
454
+ this.wrapper.innerHTML = `
455
+ <div class="embedded-entry-block__preview">
456
+ <div class="embedded-entry-block__preview-icon">
457
+ ${ICONS.linked}
458
+ </div>
459
+ <div class="embedded-entry-block__preview-content">
460
+ <div class="embedded-entry-block__preview-type">${typeDisplay}</div>
461
+ <div class="embedded-entry-block__preview-title">${this._escapeHtml(title)}</div>
462
+ ${fieldsHtml ? `<div class="embedded-entry-block__preview-fields">${fieldsHtml}</div>` : ""}
463
+ </div>
464
+ ${actionsHtml}
465
+ </div>
466
+ `;
467
+ if (!this.readOnly) {
468
+ const changeBtn = this.wrapper.querySelector('[data-action="change"]');
469
+ const removeBtn = this.wrapper.querySelector('[data-action="remove"]');
470
+ if (changeBtn) {
471
+ changeBtn.addEventListener("click", (e) => {
472
+ e.stopPropagation();
473
+ this._openPicker();
474
+ });
475
+ }
476
+ if (removeBtn) {
477
+ removeBtn.addEventListener("click", (e) => {
478
+ e.stopPropagation();
479
+ this._removeEntry();
480
+ });
481
+ }
482
+ }
483
+ this.wrapper.classList.add("embedded-entry-block--selected");
484
+ }
485
+ /**
486
+ * Open the entry picker modal
487
+ */
488
+ async _openPicker() {
489
+ if (this.readOnly) return;
490
+ this.picker = document.createElement("div");
491
+ this.picker.classList.add("embedded-entry-picker");
492
+ this.picker.innerHTML = `
493
+ <div class="embedded-entry-picker__modal">
494
+ <div class="embedded-entry-picker__header">
495
+ <span class="embedded-entry-picker__title">Select Entry</span>
496
+ <button class="embedded-entry-picker__close">&times;</button>
497
+ </div>
498
+ <div class="embedded-entry-picker__search">
499
+ <input type="text" class="embedded-entry-picker__search-input" placeholder="Search entries...">
500
+ </div>
501
+ <div class="embedded-entry-picker__list">
502
+ <div class="embedded-entry-picker__loading">Loading entries...</div>
503
+ </div>
504
+ </div>
505
+ `;
506
+ document.body.appendChild(this.picker);
507
+ const closeBtn = this.picker.querySelector(".embedded-entry-picker__close");
508
+ const searchInput = this.picker.querySelector(".embedded-entry-picker__search-input");
509
+ closeBtn.addEventListener("click", () => this._closePicker());
510
+ this.picker.addEventListener("click", (e) => {
511
+ if (e.target === this.picker) this._closePicker();
512
+ });
513
+ let searchTimeout;
514
+ searchInput.addEventListener("input", () => {
515
+ clearTimeout(searchTimeout);
516
+ searchTimeout = setTimeout(() => {
517
+ this._loadEntries(searchInput.value);
518
+ }, 300);
519
+ });
520
+ setTimeout(() => searchInput.focus(), 100);
521
+ await this._loadEntries();
522
+ }
523
+ /**
524
+ * Close the picker modal
525
+ */
526
+ _closePicker() {
527
+ if (this.picker) {
528
+ this.picker.remove();
529
+ this.picker = null;
530
+ }
531
+ }
532
+ /**
533
+ * Load entries from Strapi API
534
+ * @param {string} search - Search query
535
+ */
536
+ async _loadEntries(search = "") {
537
+ const listContainer = this.picker?.querySelector(".embedded-entry-picker__list");
538
+ if (!listContainer) return;
539
+ listContainer.innerHTML = '<div class="embedded-entry-picker__loading">Loading entries...</div>';
540
+ try {
541
+ const token = this._getAuthToken();
542
+ if (!token) {
543
+ throw new Error("Not authenticated");
544
+ }
545
+ let entries = [];
546
+ if (this.contentType) {
547
+ entries = await this._fetchContentTypeEntries(this.contentType, search, token);
548
+ } else {
549
+ const contentTypes = await this._fetchContentTypes(token);
550
+ for (const ct of contentTypes) {
551
+ if (this.allowedTypes && !this.allowedTypes.includes(ct.uid)) continue;
552
+ const ctEntries = await this._fetchContentTypeEntries(ct.uid, search, token, 5);
553
+ entries = entries.concat(ctEntries.map((e) => ({ ...e, contentType: ct.uid })));
554
+ }
555
+ }
556
+ if (entries.length === 0) {
557
+ listContainer.innerHTML = '<div class="embedded-entry-picker__empty">No entries found</div>';
558
+ return;
559
+ }
560
+ listContainer.innerHTML = "";
561
+ entries.forEach((entry) => {
562
+ const item = this._createEntryItem(entry);
563
+ listContainer.appendChild(item);
564
+ });
565
+ } catch (error) {
566
+ console.error("[EmbeddedEntryTool] Error loading entries:", error);
567
+ listContainer.innerHTML = `<div class="embedded-entry-picker__error">Error: ${error.message}</div>`;
568
+ }
569
+ }
570
+ /**
571
+ * Fetch content types from Strapi
572
+ * @param {string} token - Auth token
573
+ * @returns {Array}
574
+ */
575
+ async _fetchContentTypes(token) {
576
+ try {
577
+ const response = await fetch("/api/magic-editor-x/content-types", {
578
+ headers: {
579
+ "Authorization": `Bearer ${token}`
580
+ }
581
+ });
582
+ if (!response.ok) throw new Error("Failed to fetch content types");
583
+ const data = await response.json();
584
+ return data.contentTypes || [];
585
+ } catch (error) {
586
+ console.error("[EmbeddedEntryTool] Error fetching content types:", error);
587
+ return [];
588
+ }
589
+ }
590
+ /**
591
+ * Fetch entries from a specific content type
592
+ * @param {string} contentType - Content type UID
593
+ * @param {string} search - Search query
594
+ * @param {string} token - Auth token
595
+ * @param {number} limit - Max entries to fetch
596
+ * @returns {Array}
597
+ */
598
+ async _fetchContentTypeEntries(contentType, search, token, limit = 20) {
599
+ try {
600
+ const params = new URLSearchParams({
601
+ "pagination[pageSize]": limit.toString()
602
+ });
603
+ if (search) {
604
+ params.append("filters[$or][0][title][$containsi]", search);
605
+ params.append("filters[$or][1][name][$containsi]", search);
606
+ }
607
+ const apiPath = this._contentTypeToApiPath(contentType);
608
+ const response = await fetch(`/api/${apiPath}?${params.toString()}`, {
609
+ headers: {
610
+ "Authorization": `Bearer ${token}`
611
+ }
612
+ });
613
+ if (!response.ok) {
614
+ return [];
615
+ }
616
+ const data = await response.json();
617
+ let entries = [];
618
+ if (Array.isArray(data)) {
619
+ entries = data;
620
+ } else if (data.data) {
621
+ entries = Array.isArray(data.data) ? data.data : [data.data];
622
+ }
623
+ return entries.map((entry) => ({
624
+ ...entry,
625
+ contentType
626
+ }));
627
+ } catch (error) {
628
+ console.error(`[EmbeddedEntryTool] Error fetching ${contentType}:`, error);
629
+ return [];
630
+ }
631
+ }
632
+ /**
633
+ * Convert content type UID to API path
634
+ * @param {string} uid - Content type UID (e.g., api::article.article)
635
+ * @returns {string} API path (e.g., articles)
636
+ */
637
+ _contentTypeToApiPath(uid) {
638
+ const parts = uid.split("::");
639
+ if (parts.length > 1) {
640
+ const name = parts[1].split(".")[0];
641
+ return name.endsWith("s") ? name : `${name}s`;
642
+ }
643
+ return uid;
644
+ }
645
+ /**
646
+ * Create entry list item element
647
+ * @param {object} entry - Entry data
648
+ * @returns {HTMLElement}
649
+ */
650
+ _createEntryItem(entry) {
651
+ const item = document.createElement("div");
652
+ item.classList.add("embedded-entry-picker__item");
653
+ const title = entry.title || entry.name || entry.label || `Entry #${entry.id}`;
654
+ const type = this._formatContentType(entry.contentType);
655
+ item.innerHTML = `
656
+ <div class="embedded-entry-picker__item-icon">${ICONS.linked}</div>
657
+ <div class="embedded-entry-picker__item-content">
658
+ <div class="embedded-entry-picker__item-title">${this._escapeHtml(title)}</div>
659
+ <div class="embedded-entry-picker__item-meta">${type} · ID: ${entry.documentId || entry.id}</div>
660
+ </div>
661
+ `;
662
+ item.addEventListener("click", () => {
663
+ this._selectEntry(entry);
664
+ });
665
+ return item;
666
+ }
667
+ /**
668
+ * Select an entry
669
+ * @param {object} entry - Entry data
670
+ */
671
+ _selectEntry(entry) {
672
+ this.data.entry = entry;
673
+ this.data.contentType = entry.contentType;
674
+ this._closePicker();
675
+ this._renderPreview();
676
+ this.api.blocks.update(this.api.blocks.getCurrentBlockIndex(), this.data);
677
+ }
678
+ /**
679
+ * Remove selected entry
680
+ */
681
+ _removeEntry() {
682
+ this.data.entry = null;
683
+ this.wrapper.classList.remove("embedded-entry-block--selected");
684
+ this._renderPlaceholder();
685
+ }
686
+ /**
687
+ * Format content type UID to display name
688
+ * @param {string} uid - Content type UID
689
+ * @returns {string}
690
+ */
691
+ _formatContentType(uid) {
692
+ if (!uid) return "Unknown Type";
693
+ const parts = uid.split("::");
694
+ if (parts.length > 1) {
695
+ const name = parts[1].split(".")[0];
696
+ return name.charAt(0).toUpperCase() + name.slice(1);
697
+ }
698
+ return uid;
699
+ }
700
+ /**
701
+ * Escape HTML special characters
702
+ * @param {string} text - Text to escape
703
+ * @returns {string}
704
+ */
705
+ _escapeHtml(text) {
706
+ const div = document.createElement("div");
707
+ div.textContent = text;
708
+ return div.innerHTML;
709
+ }
710
+ /**
711
+ * Get authentication token from storage
712
+ * @returns {string|null}
713
+ */
714
+ _getAuthToken() {
715
+ try {
716
+ const token = sessionStorage.getItem("jwtToken");
717
+ if (token) return JSON.parse(token);
718
+ const localToken = localStorage.getItem("jwtToken");
719
+ if (localToken) return JSON.parse(localToken);
720
+ return null;
721
+ } catch (e) {
722
+ return null;
723
+ }
724
+ }
725
+ /**
726
+ * Save block data
727
+ * @returns {object}
728
+ */
729
+ save() {
730
+ return {
731
+ entry: this.data.entry ? {
732
+ id: this.data.entry.id,
733
+ documentId: this.data.entry.documentId,
734
+ title: this.data.entry.title || this.data.entry.name,
735
+ ...this._extractDisplayFields(this.data.entry)
736
+ } : null,
737
+ contentType: this.data.contentType,
738
+ displayMode: this.data.displayMode
739
+ };
740
+ }
741
+ /**
742
+ * Extract configured display fields from entry
743
+ * @param {object} entry - Entry data
744
+ * @returns {object}
745
+ */
746
+ _extractDisplayFields(entry) {
747
+ const result = {};
748
+ this.displayFields.forEach((field) => {
749
+ if (entry[field] !== void 0) {
750
+ result[field] = entry[field];
751
+ }
752
+ });
753
+ return result;
754
+ }
755
+ /**
756
+ * Validate saved data
757
+ * @param {object} savedData - Data to validate
758
+ * @returns {boolean}
759
+ */
760
+ validate(savedData) {
761
+ return true;
762
+ }
763
+ }
38
764
  const getBackendUrl = () => {
39
765
  return window.location.origin;
40
766
  };
@@ -1345,6 +2071,390 @@ const useVersionHistory = () => {
1345
2071
  createSnapshot
1346
2072
  };
1347
2073
  };
2074
+ const DEFAULT_ICONS = {
2075
+ simple: '<svg width="20" height="20" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><rect x="3" y="3" width="18" height="18" rx="2" stroke="currentColor" stroke-width="2"/><path d="M7 8h10M7 12h6M7 16h8" stroke="currentColor" stroke-width="2" stroke-linecap="round"/></svg>',
2076
+ "embedded-entry": '<svg width="20" height="20" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M19 3H5C3.89543 3 3 3.89543 3 5V19C3 20.1046 3.89543 21 5 21H19C20.1046 21 21 20.1046 21 19V5C21 3.89543 20.1046 3 19 3Z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><path d="M12 8V16" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><path d="M8 12H16" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>'
2077
+ };
2078
+ function createBlockClass(blockConfig) {
2079
+ if (!blockConfig || !blockConfig.name) {
2080
+ console.warn("[BlockFactory] Invalid block configuration:", blockConfig);
2081
+ return null;
2082
+ }
2083
+ switch (blockConfig.blockType) {
2084
+ case "embedded-entry":
2085
+ return createEmbeddedEntryBlock(blockConfig);
2086
+ case "simple":
2087
+ default:
2088
+ return createSimpleBlock(blockConfig);
2089
+ }
2090
+ }
2091
+ function createEmbeddedEntryBlock(config) {
2092
+ class CustomEmbeddedEntryTool extends EmbeddedEntryTool {
2093
+ static get toolbox() {
2094
+ return {
2095
+ title: config.label || config.name,
2096
+ icon: config.icon || DEFAULT_ICONS["embedded-entry"]
2097
+ };
2098
+ }
2099
+ }
2100
+ return {
2101
+ name: config.name,
2102
+ class: CustomEmbeddedEntryTool,
2103
+ config: {
2104
+ contentType: config.contentType,
2105
+ displayFields: config.displayFields || ["title", "name", "id"],
2106
+ titleField: config.titleField || "title",
2107
+ previewFields: config.previewFields || [],
2108
+ allowedTypes: config.allowedTypes || null
2109
+ },
2110
+ inlineToolbar: false,
2111
+ tunes: config.tunes || [],
2112
+ shortcut: config.shortcut || null
2113
+ };
2114
+ }
2115
+ function createSimpleBlock(config) {
2116
+ class SimpleBlockTool {
2117
+ /**
2118
+ * Toolbox configuration
2119
+ */
2120
+ static get toolbox() {
2121
+ return {
2122
+ title: config.label || config.name,
2123
+ icon: config.icon || DEFAULT_ICONS.simple
2124
+ };
2125
+ }
2126
+ /**
2127
+ * Enable read-only mode
2128
+ */
2129
+ static get isReadOnlySupported() {
2130
+ return true;
2131
+ }
2132
+ /**
2133
+ * Constructor
2134
+ * @param {object} params - Editor.js tool params
2135
+ */
2136
+ constructor({ data, api, readOnly }) {
2137
+ this.api = api;
2138
+ this.readOnly = readOnly;
2139
+ this.config = config;
2140
+ this.data = {
2141
+ content: data?.content || "",
2142
+ ...this._getDefaultFieldValues(),
2143
+ ...data
2144
+ };
2145
+ this.wrapper = null;
2146
+ }
2147
+ /**
2148
+ * Get default values for configured fields
2149
+ * @returns {object}
2150
+ */
2151
+ _getDefaultFieldValues() {
2152
+ const defaults = {};
2153
+ if (config.fields) {
2154
+ config.fields.forEach((field) => {
2155
+ if (field.default !== void 0) {
2156
+ defaults[field.name] = field.default;
2157
+ }
2158
+ });
2159
+ }
2160
+ return defaults;
2161
+ }
2162
+ /**
2163
+ * Render the block
2164
+ * @returns {HTMLElement}
2165
+ */
2166
+ render() {
2167
+ this.wrapper = document.createElement("div");
2168
+ this.wrapper.classList.add("simple-block", `simple-block--${config.name}`);
2169
+ if (config.styles) {
2170
+ Object.assign(this.wrapper.style, config.styles);
2171
+ }
2172
+ if (config.template) {
2173
+ this._renderFromTemplate();
2174
+ } else {
2175
+ this._renderDefault();
2176
+ }
2177
+ return this.wrapper;
2178
+ }
2179
+ /**
2180
+ * Render from HTML template
2181
+ */
2182
+ _renderFromTemplate() {
2183
+ let html = config.template;
2184
+ Object.keys(this.data).forEach((key) => {
2185
+ const regex = new RegExp(`\\{\\{${key}\\}\\}`, "g");
2186
+ html = html.replace(regex, this._escapeHtml(String(this.data[key])));
2187
+ });
2188
+ this.wrapper.innerHTML = html;
2189
+ if (!this.readOnly && config.fields) {
2190
+ config.fields.forEach((field) => {
2191
+ const el = this.wrapper.querySelector(`[data-field="${field.name}"]`);
2192
+ if (el && field.editable !== false) {
2193
+ el.contentEditable = true;
2194
+ el.addEventListener("blur", () => {
2195
+ this.data[field.name] = el.textContent;
2196
+ });
2197
+ }
2198
+ });
2199
+ }
2200
+ }
2201
+ /**
2202
+ * Render default layout
2203
+ */
2204
+ _renderDefault() {
2205
+ this.wrapper.style.cssText = `
2206
+ border: 2px dashed #e2e8f0;
2207
+ border-radius: 8px;
2208
+ padding: 16px;
2209
+ margin: 8px 0;
2210
+ background: #f8fafc;
2211
+ `;
2212
+ const header = document.createElement("div");
2213
+ header.style.cssText = `
2214
+ font-size: 12px;
2215
+ font-weight: 600;
2216
+ text-transform: uppercase;
2217
+ color: #4945FF;
2218
+ margin-bottom: 8px;
2219
+ letter-spacing: 0.5px;
2220
+ `;
2221
+ header.textContent = config.label || config.name;
2222
+ this.wrapper.appendChild(header);
2223
+ const content = document.createElement("div");
2224
+ content.className = "simple-block__content";
2225
+ content.contentEditable = !this.readOnly;
2226
+ content.style.cssText = `
2227
+ min-height: 40px;
2228
+ outline: none;
2229
+ font-size: 14px;
2230
+ color: #1e293b;
2231
+ `;
2232
+ content.textContent = this.data.content || "";
2233
+ content.dataset.placeholder = config.placeholder || "Enter content...";
2234
+ if (!this.data.content) {
2235
+ content.style.color = "#94a3b8";
2236
+ content.textContent = config.placeholder || "Enter content...";
2237
+ }
2238
+ content.addEventListener("focus", () => {
2239
+ if (!this.data.content) {
2240
+ content.textContent = "";
2241
+ content.style.color = "#1e293b";
2242
+ }
2243
+ });
2244
+ content.addEventListener("blur", () => {
2245
+ this.data.content = content.textContent;
2246
+ if (!this.data.content) {
2247
+ content.style.color = "#94a3b8";
2248
+ content.textContent = config.placeholder || "Enter content...";
2249
+ }
2250
+ });
2251
+ this.wrapper.appendChild(content);
2252
+ if (config.fields) {
2253
+ config.fields.forEach((field) => {
2254
+ this.wrapper.appendChild(this._createFieldInput(field));
2255
+ });
2256
+ }
2257
+ }
2258
+ /**
2259
+ * Create input for a field
2260
+ * @param {object} field - Field configuration
2261
+ * @returns {HTMLElement}
2262
+ */
2263
+ _createFieldInput(field) {
2264
+ const container = document.createElement("div");
2265
+ container.style.cssText = "margin-top: 12px;";
2266
+ const label = document.createElement("label");
2267
+ label.style.cssText = `
2268
+ display: block;
2269
+ font-size: 12px;
2270
+ font-weight: 500;
2271
+ color: #64748b;
2272
+ margin-bottom: 4px;
2273
+ `;
2274
+ label.textContent = field.label || field.name;
2275
+ container.appendChild(label);
2276
+ let input;
2277
+ switch (field.type) {
2278
+ case "select":
2279
+ input = document.createElement("select");
2280
+ input.style.cssText = `
2281
+ width: 100%;
2282
+ padding: 8px 12px;
2283
+ border: 1px solid #e2e8f0;
2284
+ border-radius: 6px;
2285
+ font-size: 14px;
2286
+ background: white;
2287
+ `;
2288
+ (field.options || []).forEach((opt) => {
2289
+ const option = document.createElement("option");
2290
+ option.value = typeof opt === "object" ? opt.value : opt;
2291
+ option.textContent = typeof opt === "object" ? opt.label : opt;
2292
+ if (option.value === this.data[field.name]) {
2293
+ option.selected = true;
2294
+ }
2295
+ input.appendChild(option);
2296
+ });
2297
+ input.addEventListener("change", () => {
2298
+ this.data[field.name] = input.value;
2299
+ });
2300
+ break;
2301
+ case "color":
2302
+ input = document.createElement("input");
2303
+ input.type = "color";
2304
+ input.value = this.data[field.name] || "#4945FF";
2305
+ input.style.cssText = `
2306
+ width: 100%;
2307
+ height: 40px;
2308
+ border: 1px solid #e2e8f0;
2309
+ border-radius: 6px;
2310
+ cursor: pointer;
2311
+ `;
2312
+ input.addEventListener("change", () => {
2313
+ this.data[field.name] = input.value;
2314
+ });
2315
+ break;
2316
+ case "checkbox":
2317
+ input = document.createElement("input");
2318
+ input.type = "checkbox";
2319
+ input.checked = this.data[field.name] || false;
2320
+ input.style.cssText = `
2321
+ width: 18px;
2322
+ height: 18px;
2323
+ cursor: pointer;
2324
+ `;
2325
+ input.addEventListener("change", () => {
2326
+ this.data[field.name] = input.checked;
2327
+ });
2328
+ break;
2329
+ case "textarea":
2330
+ input = document.createElement("textarea");
2331
+ input.value = this.data[field.name] || "";
2332
+ input.rows = 3;
2333
+ input.style.cssText = `
2334
+ width: 100%;
2335
+ padding: 8px 12px;
2336
+ border: 1px solid #e2e8f0;
2337
+ border-radius: 6px;
2338
+ font-size: 14px;
2339
+ resize: vertical;
2340
+ font-family: inherit;
2341
+ `;
2342
+ input.addEventListener("blur", () => {
2343
+ this.data[field.name] = input.value;
2344
+ });
2345
+ break;
2346
+ default:
2347
+ input = document.createElement("input");
2348
+ input.type = field.type || "text";
2349
+ input.value = this.data[field.name] || "";
2350
+ input.placeholder = field.placeholder || "";
2351
+ input.style.cssText = `
2352
+ width: 100%;
2353
+ padding: 8px 12px;
2354
+ border: 1px solid #e2e8f0;
2355
+ border-radius: 6px;
2356
+ font-size: 14px;
2357
+ `;
2358
+ input.addEventListener("blur", () => {
2359
+ this.data[field.name] = input.value;
2360
+ });
2361
+ }
2362
+ if (this.readOnly && input) {
2363
+ input.disabled = true;
2364
+ }
2365
+ container.appendChild(input);
2366
+ return container;
2367
+ }
2368
+ /**
2369
+ * Escape HTML special characters
2370
+ * @param {string} text - Text to escape
2371
+ * @returns {string}
2372
+ */
2373
+ _escapeHtml(text) {
2374
+ const div = document.createElement("div");
2375
+ div.textContent = text;
2376
+ return div.innerHTML;
2377
+ }
2378
+ /**
2379
+ * Save block data
2380
+ * @returns {object}
2381
+ */
2382
+ save() {
2383
+ return {
2384
+ ...this.data,
2385
+ blockType: "simple",
2386
+ blockName: config.name
2387
+ };
2388
+ }
2389
+ /**
2390
+ * Validate block data
2391
+ * @returns {boolean}
2392
+ */
2393
+ validate() {
2394
+ if (config.fields) {
2395
+ for (const field of config.fields) {
2396
+ if (field.required && !this.data[field.name]) {
2397
+ return false;
2398
+ }
2399
+ }
2400
+ }
2401
+ return true;
2402
+ }
2403
+ }
2404
+ return {
2405
+ name: config.name,
2406
+ class: SimpleBlockTool,
2407
+ config: {},
2408
+ inlineToolbar: config.inlineToolbar !== false,
2409
+ tunes: config.tunes || [],
2410
+ shortcut: config.shortcut || null
2411
+ };
2412
+ }
2413
+ function createBlockClasses(blockConfigs) {
2414
+ if (!Array.isArray(blockConfigs)) {
2415
+ return [];
2416
+ }
2417
+ return blockConfigs.map(createBlockClass).filter(Boolean);
2418
+ }
2419
+ function useCustomBlocks() {
2420
+ const { get } = admin.useFetchClient();
2421
+ const [customBlocks, setCustomBlocks] = React.useState([]);
2422
+ const [isLoading, setIsLoading] = React.useState(true);
2423
+ const [error, setError] = React.useState(null);
2424
+ const [lastFetched, setLastFetched] = React.useState(null);
2425
+ const fetchCustomBlocks = React.useCallback(async () => {
2426
+ try {
2427
+ setIsLoading(true);
2428
+ setError(null);
2429
+ const response = await get(`/${index.PLUGIN_ID}/custom-blocks?enabledOnly=true`);
2430
+ const blocksData = response.data?.data || [];
2431
+ const blockClasses = createBlockClasses(blocksData);
2432
+ console.log(`[useCustomBlocks] Loaded ${blockClasses.length} custom blocks`);
2433
+ setCustomBlocks(blockClasses);
2434
+ setLastFetched(/* @__PURE__ */ new Date());
2435
+ return blockClasses;
2436
+ } catch (err) {
2437
+ console.error("[useCustomBlocks] Error loading custom blocks:", err);
2438
+ setError(err.message || "Failed to load custom blocks");
2439
+ return [];
2440
+ } finally {
2441
+ setIsLoading(false);
2442
+ }
2443
+ }, [get]);
2444
+ const refresh = React.useCallback(() => {
2445
+ return fetchCustomBlocks();
2446
+ }, [fetchCustomBlocks]);
2447
+ React.useEffect(() => {
2448
+ fetchCustomBlocks();
2449
+ }, [fetchCustomBlocks]);
2450
+ return {
2451
+ customBlocks,
2452
+ isLoading,
2453
+ error,
2454
+ lastFetched,
2455
+ refresh
2456
+ };
2457
+ }
1348
2458
  const Overlay$1 = styled__default.default.div`
1349
2459
  position: fixed;
1350
2460
  top: 0;
@@ -2291,44 +3401,62 @@ const EditorJSGlobalStyles = styled.createGlobalStyle`
2291
3401
  z-index: 99999 !important;
2292
3402
  }
2293
3403
 
3404
+ /* ============================================
3405
+ NESTED POPOVER FIX - Remove min-width constraint
3406
+ ============================================ */
3407
+ .ce-popover--inline .ce-popover--nested .ce-popover__container,
3408
+ .ce-popover--inline .ce-popover--nested.ce-popover--nested-level-1 .ce-popover__container {
3409
+ min-width: 0 !important;
3410
+ }
3411
+
2294
3412
  /* ============================================
2295
3413
  TOOLBOX POPOVER (Plus Button) - CRITICAL FIX
2296
3414
  Make sure items are visible and properly displayed
2297
3415
  ============================================ */
2298
3416
 
2299
3417
  .ce-popover:not(.ce-popover--inline) {
2300
- display: block !important;
3418
+ display: flex !important;
3419
+ flex-direction: column !important;
2301
3420
  visibility: visible !important;
2302
3421
  opacity: 1 !important;
2303
3422
  z-index: 99999 !important;
2304
3423
  }
2305
3424
 
2306
3425
  .ce-popover--opened {
2307
- display: block !important;
3426
+ display: flex !important;
3427
+ flex-direction: column !important;
2308
3428
  visibility: visible !important;
2309
3429
  opacity: 1 !important;
2310
3430
  }
2311
3431
 
2312
3432
  .ce-popover__container {
2313
- display: block !important;
3433
+ display: flex !important;
3434
+ flex-direction: column !important;
2314
3435
  visibility: visible !important;
2315
3436
  opacity: 1 !important;
3437
+ flex: 1 !important;
3438
+ overflow: hidden !important;
2316
3439
  }
2317
3440
 
2318
3441
  .ce-popover__items {
2319
3442
  display: block !important;
2320
3443
  visibility: visible !important;
2321
3444
  opacity: 1 !important;
2322
- max-height: 400px !important;
3445
+ flex: 1 !important;
2323
3446
  overflow-y: auto !important;
2324
3447
  }
2325
3448
 
3449
+ /* Popover items - don't force display, let EditorJS control visibility for search */
2326
3450
  .ce-popover-item {
2327
- display: flex !important;
2328
3451
  visibility: visible !important;
2329
3452
  opacity: 1 !important;
2330
3453
  }
2331
3454
 
3455
+ /* Only show flex when not hidden by search */
3456
+ .ce-popover-item:not([hidden]) {
3457
+ display: flex !important;
3458
+ }
3459
+
2332
3460
  .ce-popover-item__icon {
2333
3461
  display: flex !important;
2334
3462
  visibility: visible !important;
@@ -2341,16 +3469,48 @@ const EditorJSGlobalStyles = styled.createGlobalStyle`
2341
3469
  opacity: 1 !important;
2342
3470
  }
2343
3471
 
2344
- /* Hide empty/nothing-found message for main toolbox */
3472
+ /* Nothing found message - EditorJS controls visibility */
2345
3473
  .ce-popover:not(.ce-popover--inline) .ce-popover__nothing-found-message {
2346
- display: none !important;
3474
+ padding: 16px !important;
3475
+ text-align: center !important;
3476
+ color: #64748b !important;
3477
+ font-size: 14px !important;
2347
3478
  }
2348
3479
 
2349
- /* Make sure search is visible */
3480
+ /* Search styling - let EditorJS control visibility */
2350
3481
  .ce-popover__search {
2351
- display: block !important;
2352
- visibility: visible !important;
2353
- opacity: 1 !important;
3482
+ padding: 12px !important;
3483
+ margin: 0 !important;
3484
+ border-bottom: 1px solid #e2e8f0 !important;
3485
+ background: white !important;
3486
+ }
3487
+
3488
+ /* Hide the search icon */
3489
+ .ce-popover__search-icon {
3490
+ display: none !important;
3491
+ }
3492
+
3493
+ .ce-popover__search-input {
3494
+ width: 100% !important;
3495
+ padding: 10px 14px !important;
3496
+ border: 1px solid #e2e8f0 !important;
3497
+ border-radius: 10px !important;
3498
+ background: #f8fafc !important;
3499
+ font-size: 14px !important;
3500
+ color: #334155 !important;
3501
+ outline: none !important;
3502
+ transition: all 0.15s ease !important;
3503
+ box-sizing: border-box !important;
3504
+ }
3505
+
3506
+ .ce-popover__search-input:focus {
3507
+ border-color: #7C3AED !important;
3508
+ background: white !important;
3509
+ box-shadow: 0 0 0 3px rgba(124, 58, 237, 0.1) !important;
3510
+ }
3511
+
3512
+ .ce-popover__search-input::placeholder {
3513
+ color: #94a3b8 !important;
2354
3514
  }
2355
3515
 
2356
3516
  /* ============================================
@@ -4028,6 +5188,7 @@ const Editor = React.forwardRef(({
4028
5188
  const t = (id, defaultMessage) => formatMessage({ id: getTranslation.getTranslation(id), defaultMessage });
4029
5189
  const { licenseData, tier: licenseTier } = useLicense();
4030
5190
  const { isAvailable: isWebtoolsAvailable, openLinkPicker: webtoolsOpenLinkPicker } = useWebtoolsLinks();
5191
+ const { customBlocks, isLoading: isLoadingCustomBlocks } = useCustomBlocks();
4031
5192
  const editorRef = React.useRef(null);
4032
5193
  const editorInstanceRef = React.useRef(null);
4033
5194
  const containerRef = React.useRef(null);
@@ -4960,12 +6121,18 @@ const Editor = React.forwardRef(({
4960
6121
  await navigator.clipboard.writeText(JSON.stringify(data, null, 2));
4961
6122
  }, [isReady]);
4962
6123
  React.useEffect(() => {
6124
+ if (isLoadingCustomBlocks) {
6125
+ console.log("[Magic Editor X] Waiting for custom blocks to load...");
6126
+ return;
6127
+ }
4963
6128
  if (editorRef.current && !editorInstanceRef.current) {
4964
6129
  const tools$1 = tools.getTools({
4965
6130
  mediaLibToggleFunc,
4966
6131
  pluginId: index.PLUGIN_ID,
4967
- openLinkPicker: isWebtoolsAvailable ? webtoolsOpenLinkPicker : null
6132
+ openLinkPicker: isWebtoolsAvailable ? webtoolsOpenLinkPicker : null,
6133
+ customBlocks: customBlocks || []
4968
6134
  });
6135
+ console.log("[Magic Editor X] Custom blocks loaded:", customBlocks?.length || 0);
4969
6136
  let initialData = void 0;
4970
6137
  if (value) {
4971
6138
  try {
@@ -5149,7 +6316,7 @@ const Editor = React.forwardRef(({
5149
6316
  }
5150
6317
  document.body.classList.remove("editor-fullscreen");
5151
6318
  };
5152
- }, []);
6319
+ }, [isLoadingCustomBlocks, customBlocks]);
5153
6320
  React.useEffect(() => {
5154
6321
  const editor = editorInstanceRef.current;
5155
6322
  if (!editor || !isReady) return;