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.
- package/README.md +131 -2
- package/dist/_chunks/{App-BrAGe1KP.js → App-ByYQ99dO.js} +4 -4
- package/dist/_chunks/{App-SlDn2xdO.mjs → App-C8q91Ico.mjs} +4 -4
- package/dist/_chunks/CustomBlocksPage-BkmF2iOQ.js +1518 -0
- package/dist/_chunks/CustomBlocksPage-rLRB05j0.mjs +1516 -0
- package/dist/_chunks/{LicensePage-C6TBYCWM.js → LicensePage-Bdn7zWU2.js} +30 -12
- package/dist/_chunks/{LicensePage-zaYXFrKs.mjs → LicensePage-DR_Cwyfw.mjs} +30 -12
- package/dist/_chunks/{LiveCollaborationPanel-yNC8e1Qb.mjs → LiveCollaborationPanel-0tplv17N.mjs} +1 -1
- package/dist/_chunks/{LiveCollaborationPanel-CGt57XG1.js → LiveCollaborationPanel-BUHIq0CQ.js} +1 -1
- package/dist/_chunks/{Settings-BH0Ttu_5.js → Settings-B5mffA2O.js} +1 -1
- package/dist/_chunks/{Settings-BXoP5tm9.mjs → Settings-FfpVHlpw.mjs} +1 -1
- package/dist/_chunks/{getTranslation-D2oq0PyC.mjs → getTranslation-Bk8tKbUs.mjs} +1 -1
- package/dist/_chunks/{getTranslation-D21ValYk.js → getTranslation-DOJ3x1SL.js} +1 -1
- package/dist/_chunks/{index-BnTvuHf2.js → index-C5DuaTDl.js} +15 -5
- package/dist/_chunks/{index-tbHD7slZ.mjs → index-DkpTkVe7.mjs} +15 -5
- package/dist/_chunks/{index-Czk5HVLJ.js → index-DzixAi6O.js} +1183 -16
- package/dist/_chunks/{index-CUTx2oym.mjs → index-zOiCW1bZ.mjs} +1183 -16
- package/dist/_chunks/{tools-D4Y_iEui.js → tools-BSMn5LLQ.js} +17 -2
- package/dist/_chunks/{tools-BGR6Cw4p.mjs → tools-uudZx91W.mjs} +17 -2
- package/dist/admin/index.js +1 -1
- package/dist/admin/index.mjs +1 -1
- package/dist/server/index.js +1172 -9
- package/dist/server/index.mjs +1172 -9
- package/dist/style.css +116 -7
- 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-
|
|
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-
|
|
9
|
+
const tools = require("./tools-BSMn5LLQ.js");
|
|
10
10
|
const admin = require("@strapi/strapi/admin");
|
|
11
|
-
const index = require("./index-
|
|
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">×</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:
|
|
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:
|
|
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:
|
|
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
|
-
|
|
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
|
-
/*
|
|
3472
|
+
/* Nothing found message - EditorJS controls visibility */
|
|
2345
3473
|
.ce-popover:not(.ce-popover--inline) .ce-popover__nothing-found-message {
|
|
2346
|
-
|
|
3474
|
+
padding: 16px !important;
|
|
3475
|
+
text-align: center !important;
|
|
3476
|
+
color: #64748b !important;
|
|
3477
|
+
font-size: 14px !important;
|
|
2347
3478
|
}
|
|
2348
3479
|
|
|
2349
|
-
/*
|
|
3480
|
+
/* Search styling - let EditorJS control visibility */
|
|
2350
3481
|
.ce-popover__search {
|
|
2351
|
-
|
|
2352
|
-
|
|
2353
|
-
|
|
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;
|