@gudhub/ssg-web-components-library 1.0.99 → 1.0.101
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/package.json
CHANGED
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
import html from './edit-mode.html';
|
|
2
2
|
import './edit-mode.scss';
|
|
3
|
-
|
|
4
3
|
import './gudhub-login-popup.scss';
|
|
5
4
|
|
|
6
5
|
import { EditModeNotifications } from './EditModeNotifications.webcomponent.js';
|
|
7
|
-
|
|
8
6
|
import { isUndefined } from './Helpers.js';
|
|
9
7
|
|
|
10
8
|
class EditMode extends GHComponent {
|
|
@@ -13,6 +11,9 @@ class EditMode extends GHComponent {
|
|
|
13
11
|
|
|
14
12
|
this.notificationComponentRendered = false;
|
|
15
13
|
this.editModeActive = false;
|
|
14
|
+
|
|
15
|
+
this._arrayControlsCleanup = null;
|
|
16
|
+
this._ghIdClickCleanup = null;
|
|
16
17
|
}
|
|
17
18
|
|
|
18
19
|
onServerRender() {
|
|
@@ -23,16 +24,17 @@ class EditMode extends GHComponent {
|
|
|
23
24
|
if (!document.querySelector('script#gudhub-library')) {
|
|
24
25
|
let gudhubLibraryScript = document.createElement('script');
|
|
25
26
|
gudhubLibraryScript.id = 'gudhub-library';
|
|
26
|
-
gudhubLibraryScript.setAttribute('src', 'https://unpkg.com/@gudhub/core
|
|
27
|
+
gudhubLibraryScript.setAttribute('src', 'https://unpkg.com/@gudhub/core/umd/library.min.js');
|
|
27
28
|
document.body.append(gudhubLibraryScript);
|
|
28
29
|
}
|
|
30
|
+
|
|
29
31
|
this.editModeActive = !this.editModeActive;
|
|
30
32
|
|
|
31
|
-
if(this.editModeActive === true && this.notificationComponentRendered === false) {
|
|
33
|
+
if (this.editModeActive === true && this.notificationComponentRendered === false) {
|
|
32
34
|
this.renderNotificationsComponent();
|
|
33
35
|
}
|
|
34
36
|
|
|
35
|
-
if(this.editModeActive) {
|
|
37
|
+
if (this.editModeActive) {
|
|
36
38
|
this.initEditors();
|
|
37
39
|
} else {
|
|
38
40
|
this.disableEditors();
|
|
@@ -40,64 +42,59 @@ class EditMode extends GHComponent {
|
|
|
40
42
|
}
|
|
41
43
|
|
|
42
44
|
disableEditors() {
|
|
45
|
+
if (this._arrayControlsCleanup) {
|
|
46
|
+
this._arrayControlsCleanup();
|
|
47
|
+
this._arrayControlsCleanup = null;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (this._ghIdClickCleanup) {
|
|
51
|
+
this._ghIdClickCleanup();
|
|
52
|
+
this._ghIdClickCleanup = null;
|
|
53
|
+
}
|
|
54
|
+
|
|
43
55
|
const body = document.getElementsByTagName('body')[0];
|
|
44
56
|
body.classList.remove('edit-mode-enabled');
|
|
57
|
+
|
|
45
58
|
const elementsToEdit = document.querySelectorAll('[gh-id]');
|
|
46
|
-
if(elementsToEdit.length) {
|
|
59
|
+
if (elementsToEdit.length) {
|
|
47
60
|
elementsToEdit.forEach(element => {
|
|
48
|
-
if(element.classList.contains('mce-content-body')) {
|
|
61
|
+
if (element.classList.contains('mce-content-body')) {
|
|
49
62
|
tinymce.get(element.id).destroy();
|
|
50
63
|
}
|
|
51
64
|
element.replaceWith(element.cloneNode(true));
|
|
52
|
-
})
|
|
65
|
+
});
|
|
53
66
|
}
|
|
54
67
|
}
|
|
55
68
|
|
|
56
69
|
initEditors() {
|
|
57
70
|
const body = document.getElementsByTagName('body')[0];
|
|
58
71
|
body.classList.add('edit-mode-enabled');
|
|
72
|
+
|
|
59
73
|
const showLoginPopup = () => this.showLoginPopup();
|
|
60
74
|
const initGudHub = (auth_key) => this.initGudHub(auth_key);
|
|
61
|
-
if(!document.querySelector('script[id="tinymce_script"]')) {
|
|
62
|
-
const script = document.createElement('script');
|
|
63
75
|
|
|
76
|
+
if (!document.querySelector('script[id="tinymce_script"]')) {
|
|
77
|
+
const script = document.createElement('script');
|
|
64
78
|
script.setAttribute('src', 'https://cdn.tiny.cloud/1/ts08yt1lknwldsqs5d4iohomcmpfd2wolmcy7lao74r3ita3/tinymce/6/tinymce.min.js');
|
|
65
79
|
script.setAttribute('referrerpolicy', 'origin');
|
|
66
80
|
script.setAttribute('id', 'tinymce_script');
|
|
67
|
-
|
|
68
81
|
document.querySelector('head').appendChild(script);
|
|
69
82
|
}
|
|
70
83
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
let parentElement = element.parentElement;
|
|
81
|
-
while (parentElement) {
|
|
82
|
-
if (parentElement.tagName.includes('-')) {
|
|
83
|
-
return parentElement;
|
|
84
|
-
}
|
|
85
|
-
parentElement = parentElement.parentElement;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
return null;
|
|
89
|
-
})()
|
|
90
|
-
initTinyMce(element, parentGhComponent);
|
|
91
|
-
}
|
|
92
|
-
});
|
|
93
|
-
})
|
|
94
|
-
}
|
|
95
|
-
resolve();
|
|
96
|
-
});
|
|
84
|
+
if (!this._arrayControlsCleanup) {
|
|
85
|
+
this._arrayControlsCleanup = initArrayItemControls();
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (!this._ghIdClickCleanup) {
|
|
89
|
+
this._ghIdClickCleanup = initGhIdClickDelegation();
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return Promise.resolve();
|
|
97
93
|
|
|
98
94
|
function initTinyMce(element, parentGhComponent) {
|
|
99
95
|
const self = parentGhComponent;
|
|
100
|
-
|
|
96
|
+
|
|
97
|
+
tinymce.init({
|
|
101
98
|
target: element,
|
|
102
99
|
plugins: 'advlist autolink lists link image charmap preview anchor pagebreak',
|
|
103
100
|
toolbar_mode: 'floating',
|
|
@@ -112,39 +109,55 @@ class EditMode extends GHComponent {
|
|
|
112
109
|
setup: (editor) => {
|
|
113
110
|
editor.on('init', e => {
|
|
114
111
|
e.target.focus();
|
|
115
|
-
})
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
editor.on('remove', () => {
|
|
115
|
+
element.classList.remove('gh-id-editing');
|
|
116
|
+
});
|
|
117
|
+
|
|
116
118
|
editor.ui.registry.addButton('saveButton', {
|
|
117
119
|
text: 'Save',
|
|
118
120
|
onAction: async (_) => {
|
|
119
|
-
if(isUndefined(window.gudhub)) {
|
|
120
|
-
if(window.localStorage.getItem('gudhub_auth_key')) {
|
|
121
|
+
if (isUndefined(window.gudhub)) {
|
|
122
|
+
if (window.localStorage.getItem('gudhub_auth_key')) {
|
|
121
123
|
initGudHub(window.localStorage.getItem('gudhub_auth_key'));
|
|
122
124
|
} else {
|
|
123
125
|
showLoginPopup();
|
|
124
126
|
return;
|
|
125
127
|
}
|
|
126
128
|
}
|
|
127
|
-
let ids = await self.findIds();
|
|
128
129
|
|
|
130
|
+
let ids = await self.findIds();
|
|
129
131
|
const currentChapter = window?.constants?.currentChapter || 'pages';
|
|
130
132
|
|
|
131
|
-
const data = await gudhub.getDocument({
|
|
133
|
+
const data = await gudhub.getDocument({
|
|
134
|
+
app_id: ids.appId,
|
|
135
|
+
item_id: ids.itemId,
|
|
136
|
+
element_id: document.querySelector('html').getAttribute(`data-${currentChapter}-json_field_id`)
|
|
137
|
+
});
|
|
138
|
+
|
|
132
139
|
const json = JSON.parse(data.data);
|
|
140
|
+
|
|
133
141
|
const content = editor.getContent();
|
|
134
142
|
const ghId = element.getAttribute('gh-id');
|
|
135
|
-
|
|
143
|
+
|
|
144
|
+
if (ghId.indexOf('.') === -1) {
|
|
136
145
|
json[ghId] = content;
|
|
137
146
|
} else {
|
|
138
147
|
const findedJson = self.findValueByPath(json, ghId.substring(0, ghId.lastIndexOf('.')));
|
|
139
148
|
findedJson[ghId.substring(ghId.lastIndexOf('.') + 1, ghId.length)] = content;
|
|
140
149
|
}
|
|
141
|
-
|
|
150
|
+
|
|
151
|
+
await gudhub.createDocument({
|
|
152
|
+
app_id: ids.appId,
|
|
153
|
+
item_id: ids.itemId,
|
|
154
|
+
element_id: document.querySelector('html').getAttribute(`data-${currentChapter}-json_field_id`),
|
|
155
|
+
data: JSON.stringify(json)
|
|
156
|
+
});
|
|
142
157
|
|
|
143
158
|
window.dispatchEvent(new CustomEvent('add-edit-mode-notification', {
|
|
144
|
-
detail: {
|
|
145
|
-
|
|
146
|
-
}
|
|
147
|
-
}))
|
|
159
|
+
detail: { text: 'Saved' }
|
|
160
|
+
}));
|
|
148
161
|
}
|
|
149
162
|
});
|
|
150
163
|
|
|
@@ -170,42 +183,511 @@ class EditMode extends GHComponent {
|
|
|
170
183
|
|
|
171
184
|
editor.ui.registry.addButton('accentColor', {
|
|
172
185
|
text: 'Color',
|
|
173
|
-
onAction: function() {
|
|
186
|
+
onAction: function () {
|
|
174
187
|
editor.formatter.toggle('accentColor');
|
|
175
188
|
}
|
|
176
189
|
});
|
|
177
190
|
|
|
178
191
|
editor.ui.registry.addButton('accentBackground', {
|
|
179
192
|
text: 'Background',
|
|
180
|
-
onAction: function() {
|
|
193
|
+
onAction: function () {
|
|
181
194
|
editor.formatter.toggle('accentBackground');
|
|
182
195
|
}
|
|
183
196
|
});
|
|
184
197
|
}
|
|
185
|
-
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function initGhIdClickDelegation() {
|
|
202
|
+
const onClick = (e) => {
|
|
203
|
+
const element = e.target?.closest?.('[gh-id]');
|
|
204
|
+
const isTinyUI = e.target?.closest?.('.tox, .tox-toolbar, .tox-editor-container');
|
|
205
|
+
const isArrayPanel = e.target?.closest?.('#gh-array-panel');
|
|
206
|
+
|
|
207
|
+
if (!element && !isTinyUI && !isArrayPanel) {
|
|
208
|
+
document.querySelectorAll('.gh-id-editing').forEach(el => {
|
|
209
|
+
el.classList.remove('gh-id-editing');
|
|
210
|
+
|
|
211
|
+
if (el.classList.contains('mce-content-body')) {
|
|
212
|
+
const ed = tinymce.get(el.id);
|
|
213
|
+
if (ed) ed.destroy();
|
|
214
|
+
}
|
|
215
|
+
});
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
if (!element) return;
|
|
220
|
+
|
|
221
|
+
if (isUndefined(tinymce)) {
|
|
222
|
+
alert('TinyMCE not ready yet');
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
if (element.getAttribute('contenteditable') == 'true') return;
|
|
227
|
+
|
|
228
|
+
const parentGhComponent = (() => {
|
|
229
|
+
let parentElement = element;
|
|
230
|
+
while (parentElement) {
|
|
231
|
+
if (parentElement.tagName && parentElement.tagName.includes('-') && typeof parentElement.findIds === 'function') {
|
|
232
|
+
return parentElement;
|
|
233
|
+
}
|
|
234
|
+
parentElement = parentElement.parentElement;
|
|
235
|
+
}
|
|
236
|
+
return null;
|
|
237
|
+
})();
|
|
238
|
+
|
|
239
|
+
if (!parentGhComponent) return;
|
|
240
|
+
|
|
241
|
+
const panel = document.querySelector('#gh-array-panel');
|
|
242
|
+
if (panel) {
|
|
243
|
+
panel.style.display = 'none';
|
|
244
|
+
panel.setAttribute('aria-hidden', 'true');
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
document
|
|
248
|
+
.querySelectorAll('.gh-id-editing')
|
|
249
|
+
.forEach(el => el.classList.remove('gh-id-editing'));
|
|
250
|
+
|
|
251
|
+
element.classList.add('gh-id-editing');
|
|
252
|
+
initTinyMce(element, parentGhComponent);
|
|
253
|
+
};
|
|
254
|
+
|
|
255
|
+
document.addEventListener('click', onClick, true);
|
|
256
|
+
|
|
257
|
+
return () => document.removeEventListener('click', onClick, true);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
function initArrayItemControls({ root = document } = {}) {
|
|
261
|
+
function stopEvent(event) {
|
|
262
|
+
event.preventDefault();
|
|
263
|
+
event.stopPropagation();
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
function deepClone(value) {
|
|
267
|
+
try { return structuredClone(value); }
|
|
268
|
+
catch { return JSON.parse(JSON.stringify(value)); }
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
function getValueByPath(object, pathArray) {
|
|
272
|
+
let currentValue = object;
|
|
273
|
+
for (const key of pathArray) {
|
|
274
|
+
if (currentValue == null) return undefined;
|
|
275
|
+
currentValue = currentValue[key];
|
|
276
|
+
}
|
|
277
|
+
return currentValue;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
function setValueByPath(object, pathArray, value) {
|
|
281
|
+
if (!object || !pathArray?.length) return;
|
|
282
|
+
|
|
283
|
+
let current = object;
|
|
284
|
+
for (let i = 0; i < pathArray.length - 1; i++) {
|
|
285
|
+
const key = pathArray[i];
|
|
286
|
+
if (current[key] == null) {
|
|
287
|
+
current[key] = (typeof pathArray[i + 1] === 'number') ? [] : {};
|
|
288
|
+
}
|
|
289
|
+
current = current[key];
|
|
290
|
+
}
|
|
291
|
+
current[pathArray[pathArray.length - 1]] = value;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
function parseGhId(ghId) {
|
|
295
|
+
if (!ghId) return null;
|
|
296
|
+
|
|
297
|
+
const pathParts = ghId.split('.').map(part =>
|
|
298
|
+
String(+part) === part ? Number(part) : part
|
|
299
|
+
);
|
|
300
|
+
|
|
301
|
+
const numericIndexes = pathParts
|
|
302
|
+
.map((value, index) =>
|
|
303
|
+
typeof value === 'number' && Number.isFinite(value) ? index : -1
|
|
304
|
+
)
|
|
305
|
+
.filter(index => index !== -1);
|
|
306
|
+
|
|
307
|
+
if (!numericIndexes.length) return null;
|
|
308
|
+
|
|
309
|
+
const lastIndexPosition = numericIndexes[numericIndexes.length - 1];
|
|
310
|
+
|
|
311
|
+
return {
|
|
312
|
+
fullPath: pathParts,
|
|
313
|
+
arrayPath: pathParts.slice(0, lastIndexPosition),
|
|
314
|
+
itemIndexPosition: lastIndexPosition,
|
|
315
|
+
itemIndex: pathParts[lastIndexPosition],
|
|
316
|
+
};
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
let controlPanel = document.querySelector('#gh-array-panel');
|
|
320
|
+
|
|
321
|
+
if (!controlPanel) {
|
|
322
|
+
controlPanel = document.createElement('div');
|
|
323
|
+
controlPanel.id = 'gh-array-panel';
|
|
324
|
+
|
|
325
|
+
controlPanel.innerHTML = `
|
|
326
|
+
<button type="button" data-action="add" class="gh-panel-btn">
|
|
327
|
+
Add
|
|
328
|
+
</button>
|
|
329
|
+
<button type="button" data-action="remove" class="gh-panel-btn">
|
|
330
|
+
Remove
|
|
331
|
+
</button>
|
|
332
|
+
`;
|
|
333
|
+
|
|
334
|
+
document.body.appendChild(controlPanel);
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
async function findIdsFromElement(element) {
|
|
338
|
+
let parent = element.parentElement;
|
|
339
|
+
|
|
340
|
+
while (parent) {
|
|
341
|
+
if (
|
|
342
|
+
parent.tagName &&
|
|
343
|
+
parent.tagName.includes('-') &&
|
|
344
|
+
typeof parent.findIds === 'function'
|
|
345
|
+
) {
|
|
346
|
+
return parent.findIds();
|
|
347
|
+
}
|
|
348
|
+
parent = parent.parentElement;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
return null;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
function ensureGudhubInitialized() {
|
|
355
|
+
if (window.gudhub) return true;
|
|
356
|
+
|
|
357
|
+
const authKey = localStorage.getItem('gudhub_auth_key');
|
|
358
|
+
if (!authKey) {
|
|
359
|
+
showLoginPopup();
|
|
360
|
+
return false;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
initGudHub(authKey);
|
|
364
|
+
return true;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
let cachedDocumentKey = null;
|
|
368
|
+
let cachedJson = null;
|
|
369
|
+
|
|
370
|
+
async function loadJsonForElement(element) {
|
|
371
|
+
const ids = await findIdsFromElement(element);
|
|
372
|
+
if (!ids) return null;
|
|
373
|
+
|
|
374
|
+
const currentChapter = window?.constants?.currentChapter || 'pages';
|
|
375
|
+
const elementId = document.documentElement.getAttribute(`data-${currentChapter}-json_field_id`);
|
|
376
|
+
|
|
377
|
+
const cacheKey = `${ids.appId}:${ids.itemId}:${elementId}`;
|
|
378
|
+
|
|
379
|
+
if (cacheKey === cachedDocumentKey && cachedJson) {
|
|
380
|
+
return { ids, elementId, json: cachedJson };
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
const response = await gudhub.getDocument({
|
|
384
|
+
app_id: ids.appId,
|
|
385
|
+
item_id: ids.itemId,
|
|
386
|
+
element_id: elementId,
|
|
387
|
+
});
|
|
388
|
+
|
|
389
|
+
const parsedJson = JSON.parse(response.data);
|
|
390
|
+
|
|
391
|
+
cachedDocumentKey = cacheKey;
|
|
392
|
+
cachedJson = parsedJson;
|
|
393
|
+
|
|
394
|
+
return { ids, elementId, json: parsedJson };
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
function updateGhIdsIndexInside(rootEl, itemIndexPosition, newIndex) {
|
|
398
|
+
const nodes = rootEl.querySelectorAll('[gh-id]');
|
|
399
|
+
nodes.forEach(node => {
|
|
400
|
+
const ghId = node.getAttribute('gh-id');
|
|
401
|
+
if (!ghId) return;
|
|
402
|
+
|
|
403
|
+
const parts = ghId.split('.');
|
|
404
|
+
if (itemIndexPosition < 0 || itemIndexPosition >= parts.length) return;
|
|
405
|
+
|
|
406
|
+
parts[itemIndexPosition] = String(newIndex);
|
|
407
|
+
node.setAttribute('gh-id', parts.join('.'));
|
|
408
|
+
});
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
function getPathPartsFromGhId(ghId) {
|
|
412
|
+
return ghId.split('.').map(part =>
|
|
413
|
+
String(+part) === part ? Number(part) : part
|
|
414
|
+
);
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
function fillGhIdValuesFromJson(rootEl, json) {
|
|
418
|
+
const nodes = rootEl.querySelectorAll('[gh-id]');
|
|
419
|
+
nodes.forEach(node => {
|
|
420
|
+
const ghId = node.getAttribute('gh-id');
|
|
421
|
+
if (!ghId) return;
|
|
422
|
+
|
|
423
|
+
const path = getPathPartsFromGhId(ghId);
|
|
424
|
+
const value = getValueByPath(json, path);
|
|
425
|
+
|
|
426
|
+
node.innerHTML = value ?? '';
|
|
427
|
+
});
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
function getDomValue(node) {
|
|
431
|
+
if (node.id && window.tinymce) {
|
|
432
|
+
const ed = tinymce.get(node.id);
|
|
433
|
+
if (ed) return ed.getContent();
|
|
434
|
+
}
|
|
435
|
+
return node.innerHTML;
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
function syncDomToJsonForContainer(container, json) {
|
|
439
|
+
const nodes = container.querySelectorAll('[gh-id]');
|
|
440
|
+
nodes.forEach(node => {
|
|
441
|
+
const ghId = node.getAttribute('gh-id');
|
|
442
|
+
if (!ghId) return;
|
|
443
|
+
|
|
444
|
+
const path = getPathPartsFromGhId(ghId);
|
|
445
|
+
setValueByPath(json, path, getDomValue(node));
|
|
446
|
+
});
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
function rerenderArrayContainerFromJson({ targetElement, json, arrayPath }) {
|
|
450
|
+
const container = targetElement.closest('[gh-array]');
|
|
451
|
+
if (!container) return false;
|
|
452
|
+
|
|
453
|
+
const arr = getValueByPath(json, arrayPath);
|
|
454
|
+
if (!Array.isArray(arr)) return false;
|
|
455
|
+
|
|
456
|
+
const itemSelector = container.querySelector('[gh-item]') ? '[gh-item]' : '.item';
|
|
457
|
+
const firstItem = container.querySelector(itemSelector);
|
|
458
|
+
if (!firstItem) return false;
|
|
459
|
+
|
|
460
|
+
const template = firstItem.cloneNode(true);
|
|
461
|
+
|
|
462
|
+
const anyGh = template.querySelector('[gh-id]');
|
|
463
|
+
const parsed = anyGh ? parseGhId(anyGh.getAttribute('gh-id')) : null;
|
|
464
|
+
const itemIndexPosition = parsed?.itemIndexPosition;
|
|
465
|
+
if (typeof itemIndexPosition !== 'number') return false;
|
|
466
|
+
|
|
467
|
+
container.innerHTML = '';
|
|
468
|
+
|
|
469
|
+
for (let i = 0; i < arr.length; i++) {
|
|
470
|
+
const clone = template.cloneNode(true);
|
|
471
|
+
|
|
472
|
+
updateGhIdsIndexInside(clone, itemIndexPosition, i);
|
|
473
|
+
fillGhIdValuesFromJson(clone, json);
|
|
474
|
+
|
|
475
|
+
container.appendChild(clone);
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
return true;
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
let currentHover = null;
|
|
482
|
+
let hideTimer = null;
|
|
483
|
+
|
|
484
|
+
function scheduleHidePanel() {
|
|
485
|
+
clearTimeout(hideTimer);
|
|
486
|
+
hideTimer = setTimeout(() => hidePanel(), 250);
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
function cancelHidePanel() {
|
|
490
|
+
clearTimeout(hideTimer);
|
|
491
|
+
hideTimer = null;
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
function hidePanel() {
|
|
495
|
+
controlPanel.style.display = 'none';
|
|
496
|
+
currentHover = null;
|
|
497
|
+
lastHoveredItem = null;
|
|
498
|
+
|
|
499
|
+
document.querySelectorAll('.gh-item-hovered').forEach(el => el.classList.remove('gh-item-hovered'));
|
|
500
|
+
controlPanel.setAttribute('aria-hidden', 'true');
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
function showPanelOverItem(itemEl) {
|
|
504
|
+
const rect = itemEl.getBoundingClientRect();
|
|
505
|
+
|
|
506
|
+
const panelW = 220;
|
|
507
|
+
const panelH = 36;
|
|
508
|
+
const top = Math.max(8, rect.top - panelH - 8);
|
|
509
|
+
const left = Math.max(
|
|
510
|
+
8,
|
|
511
|
+
Math.min(window.innerWidth - panelW - 8, rect.left + (rect.width / 2) - (panelW / 2))
|
|
512
|
+
);
|
|
513
|
+
|
|
514
|
+
controlPanel.style.top = `${top}px`;
|
|
515
|
+
controlPanel.style.left = `${left}px`;
|
|
516
|
+
controlPanel.style.display = 'flex';
|
|
517
|
+
|
|
518
|
+
controlPanel.setAttribute('aria-hidden', 'false');
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
function getItemElementFromTarget(target) {
|
|
522
|
+
return target?.closest?.('[gh-array] [gh-item]') || target?.closest?.('[gh-array] .item');
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
function getAnyGhIdInsideItem(itemEl) {
|
|
526
|
+
return itemEl?.querySelector?.('[gh-id]') || null;
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
let animationFrameId = 0;
|
|
530
|
+
let lastHoveredItem = null;
|
|
531
|
+
|
|
532
|
+
function handlePointerMove(event) {
|
|
533
|
+
if (animationFrameId) return;
|
|
534
|
+
|
|
535
|
+
animationFrameId = requestAnimationFrame(async () => {
|
|
536
|
+
animationFrameId = 0;
|
|
537
|
+
|
|
538
|
+
if (controlPanel.contains(event.target)) {
|
|
539
|
+
cancelHidePanel();
|
|
540
|
+
return;
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
const hoveredItem = getItemElementFromTarget(event.target);
|
|
544
|
+
|
|
545
|
+
if (!hoveredItem) {
|
|
546
|
+
scheduleHidePanel();
|
|
547
|
+
return;
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
cancelHidePanel();
|
|
551
|
+
|
|
552
|
+
if (hoveredItem === lastHoveredItem) return;
|
|
553
|
+
lastHoveredItem = hoveredItem;
|
|
554
|
+
|
|
555
|
+
const anyGh = getAnyGhIdInsideItem(hoveredItem);
|
|
556
|
+
if (!anyGh) {
|
|
557
|
+
scheduleHidePanel();
|
|
558
|
+
return;
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
const parsedPath = parseGhId(anyGh.getAttribute('gh-id'));
|
|
562
|
+
if (!parsedPath) {
|
|
563
|
+
scheduleHidePanel();
|
|
564
|
+
return;
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
if (!ensureGudhubInitialized()) {
|
|
568
|
+
scheduleHidePanel();
|
|
569
|
+
return;
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
const loadedData = await loadJsonForElement(anyGh);
|
|
573
|
+
if (!loadedData) {
|
|
574
|
+
scheduleHidePanel();
|
|
575
|
+
return;
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
const targetArray = getValueByPath(loadedData.json, parsedPath.arrayPath);
|
|
579
|
+
if (!Array.isArray(targetArray)) {
|
|
580
|
+
scheduleHidePanel();
|
|
581
|
+
return;
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
currentHover = { element: anyGh, itemEl: hoveredItem, meta: parsedPath };
|
|
585
|
+
|
|
586
|
+
document.querySelectorAll('.gh-item-hovered').forEach(el => el.classList.remove('gh-item-hovered'));
|
|
587
|
+
hoveredItem.classList.add('gh-item-hovered');
|
|
588
|
+
|
|
589
|
+
showPanelOverItem(hoveredItem);
|
|
590
|
+
});
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
|
|
594
|
+
controlPanel.addEventListener('pointerdown', stopEvent, true);
|
|
595
|
+
controlPanel.addEventListener('pointerenter', () => {
|
|
596
|
+
cancelHidePanel();
|
|
597
|
+
}, true);
|
|
598
|
+
|
|
599
|
+
controlPanel.addEventListener('pointerleave', () => {
|
|
600
|
+
scheduleHidePanel();
|
|
601
|
+
}, true);
|
|
602
|
+
|
|
603
|
+
|
|
604
|
+
controlPanel.addEventListener('click', async event => {
|
|
605
|
+
stopEvent(event);
|
|
606
|
+
|
|
607
|
+
const button = event.target.closest('button[data-action]');
|
|
608
|
+
if (!button || !currentHover) return;
|
|
609
|
+
|
|
610
|
+
if (!ensureGudhubInitialized()) return;
|
|
611
|
+
|
|
612
|
+
const actionType = button.getAttribute('data-action');
|
|
613
|
+
|
|
614
|
+
const loadedData = await loadJsonForElement(currentHover.element);
|
|
615
|
+
if (!loadedData) return;
|
|
616
|
+
|
|
617
|
+
const { ids, elementId, json } = loadedData;
|
|
618
|
+
|
|
619
|
+
const container = currentHover.itemEl?.closest?.('[gh-array]') || currentHover.element.closest('[gh-array]');
|
|
620
|
+
if (container) {
|
|
621
|
+
syncDomToJsonForContainer(container, json);
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
const targetArray = getValueByPath(json, currentHover.meta.arrayPath);
|
|
625
|
+
if (!Array.isArray(targetArray)) return;
|
|
626
|
+
|
|
627
|
+
const itemIndex = currentHover.meta.itemIndex;
|
|
628
|
+
|
|
629
|
+
if (actionType === 'add') {
|
|
630
|
+
targetArray.splice(itemIndex + 1, 0, deepClone(targetArray[itemIndex] ?? {}));
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
if (actionType === 'remove') {
|
|
634
|
+
if (targetArray.length <= 1) return;
|
|
635
|
+
targetArray.splice(itemIndex, 1);
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
await gudhub.createDocument({
|
|
639
|
+
app_id: ids.appId,
|
|
640
|
+
item_id: ids.itemId,
|
|
641
|
+
element_id: elementId,
|
|
642
|
+
data: JSON.stringify(json),
|
|
643
|
+
});
|
|
644
|
+
|
|
645
|
+
cachedJson = json;
|
|
646
|
+
|
|
647
|
+
rerenderArrayContainerFromJson({
|
|
648
|
+
targetElement: currentHover.element,
|
|
649
|
+
json,
|
|
650
|
+
arrayPath: currentHover.meta.arrayPath,
|
|
651
|
+
});
|
|
652
|
+
|
|
653
|
+
lastHoveredItem = null;
|
|
654
|
+
hidePanel();
|
|
655
|
+
}, true);
|
|
656
|
+
|
|
657
|
+
root.addEventListener('pointermove', handlePointerMove, true);
|
|
658
|
+
window.addEventListener('scroll', scheduleHidePanel, true);
|
|
659
|
+
window.addEventListener('resize', scheduleHidePanel, true);
|
|
660
|
+
|
|
661
|
+
return () => {
|
|
662
|
+
root.removeEventListener('pointermove', handlePointerMove, true);
|
|
663
|
+
window.removeEventListener('scroll', scheduleHidePanel, true);
|
|
664
|
+
window.removeEventListener('resize', scheduleHidePanel, true);
|
|
665
|
+
hidePanel();
|
|
666
|
+
};
|
|
667
|
+
|
|
186
668
|
}
|
|
187
669
|
}
|
|
188
670
|
|
|
189
671
|
renderNotificationsComponent() {
|
|
190
|
-
if(!window.customElements.get('edit-mode-notifications')) {
|
|
672
|
+
if (!window.customElements.get('edit-mode-notifications')) {
|
|
191
673
|
window.customElements.define('edit-mode-notifications', EditModeNotifications);
|
|
192
674
|
}
|
|
193
675
|
|
|
194
|
-
if(!document.querySelector('edit-mode-notifications')) {
|
|
676
|
+
if (!document.querySelector('edit-mode-notifications')) {
|
|
195
677
|
document.body.append(document.createElement('edit-mode-notifications'));
|
|
196
678
|
}
|
|
197
679
|
}
|
|
198
680
|
|
|
199
681
|
initListeners() {
|
|
200
682
|
const listener = (e) => {
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
683
|
+
this.initGudHub(e.detail.data.auth_key);
|
|
684
|
+
window.localStorage.setItem('gudhub_auth_key', e.detail.data.auth_key);
|
|
685
|
+
window.removeEventListener('gudhub-login', listener, false);
|
|
204
686
|
}
|
|
205
687
|
window.addEventListener('gudhub-login', listener, false);
|
|
206
688
|
|
|
207
689
|
const outsideClickListener = (e) => {
|
|
208
|
-
if(e.target === document.querySelector('.gudhub-login-popup__bg')) {
|
|
690
|
+
if (e.target === document.querySelector('.gudhub-login-popup__bg')) {
|
|
209
691
|
this.hideLoginPopup();
|
|
210
692
|
window.removeEventListener('click', outsideClickListener, false);
|
|
211
693
|
}
|
|
@@ -218,25 +700,23 @@ class EditMode extends GHComponent {
|
|
|
218
700
|
let GudHub;
|
|
219
701
|
try {
|
|
220
702
|
GudHub = !isUndefined(GudHubLibrary) ? GudHubLibrary.GudHub : GudHub;
|
|
221
|
-
} catch(err) {
|
|
703
|
+
} catch (err) {
|
|
222
704
|
console.error('GHCOMPONENT ERROR: you need to import GudHub library (@gudhub/core) before using GHComponent!');
|
|
223
705
|
}
|
|
224
706
|
|
|
225
|
-
window.gudhub = !isUndefined(window.gudhub)
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
707
|
+
window.gudhub = !isUndefined(window.gudhub)
|
|
708
|
+
? window.gudhub
|
|
709
|
+
: new GudHub(isUndefined(auth_key) ? window.getConfig().auth_key : auth_key, {
|
|
710
|
+
server_url: 'https://app.gudhub.com/GudHub',
|
|
711
|
+
file_server_url: 'https://app.gudhub.com',
|
|
712
|
+
async_modules_path: 'build/latest/async_modules/',
|
|
713
|
+
});
|
|
230
714
|
|
|
231
715
|
this.hideLoginPopup();
|
|
232
716
|
}
|
|
233
717
|
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
connectLoginSript() {
|
|
239
|
-
if(!document.querySelector('script#gudhub_login_component')) {
|
|
718
|
+
connectLoginScript() {
|
|
719
|
+
if (!document.querySelector('script#gudhub_login_component')) {
|
|
240
720
|
const script = document.createElement('script');
|
|
241
721
|
script.id = 'gudhub-login-component';
|
|
242
722
|
script.src = 'https://unpkg.com/@gudhub/gh-login/index.js';
|
|
@@ -246,7 +726,7 @@ class EditMode extends GHComponent {
|
|
|
246
726
|
}
|
|
247
727
|
|
|
248
728
|
createLoginPopup() {
|
|
249
|
-
if(!document.querySelector('#gudhub_login_popup')) {
|
|
729
|
+
if (!document.querySelector('#gudhub_login_popup')) {
|
|
250
730
|
const div = document.createElement('div');
|
|
251
731
|
div.classList.add('gudhub-login-popup__bg');
|
|
252
732
|
div.id = 'gudhub_login_popup';
|
|
@@ -262,20 +742,19 @@ class EditMode extends GHComponent {
|
|
|
262
742
|
}
|
|
263
743
|
|
|
264
744
|
showLoginPopup() {
|
|
265
|
-
this.
|
|
745
|
+
this.connectLoginScript();
|
|
266
746
|
this.createLoginPopup();
|
|
267
747
|
document.querySelector('.gudhub-login-popup__bg').classList.add('active');
|
|
268
748
|
this.initListeners();
|
|
269
749
|
}
|
|
270
750
|
|
|
271
751
|
hideLoginPopup() {
|
|
272
|
-
if(document.querySelector('.gudhub-login-popup__bg')) {
|
|
752
|
+
if (document.querySelector('.gudhub-login-popup__bg')) {
|
|
273
753
|
document.querySelector('.gudhub-login-popup__bg').classList.remove('active');
|
|
274
754
|
}
|
|
275
755
|
}
|
|
276
|
-
|
|
277
756
|
}
|
|
278
757
|
|
|
279
|
-
if(!window.customElements.get('edit-mode')) {
|
|
758
|
+
if (!window.customElements.get('edit-mode')) {
|
|
280
759
|
window.customElements.define('edit-mode', EditMode);
|
|
281
|
-
}
|
|
760
|
+
}
|
|
@@ -1,8 +1,22 @@
|
|
|
1
1
|
body.edit-mode-enabled {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
2
|
+
.gh-item-hovered {
|
|
3
|
+
outline: 2px solid rgba(43, 97, 250, 0.6);
|
|
4
|
+
outline-offset: 2px;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
[gh-id]:hover {
|
|
8
|
+
outline: 1px dashed rgba(43, 97, 250, 0.5);
|
|
9
|
+
cursor: text;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
[gh-id].gh-id-editing {
|
|
13
|
+
outline: 2px solid #2b61fa;
|
|
14
|
+
outline-offset: 2px;
|
|
15
|
+
background: rgba(43, 97, 250, 0.05);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
.gh-item-hovered:has(.gh-id-editing) {
|
|
19
|
+
outline-color: transparent;
|
|
6
20
|
}
|
|
7
21
|
}
|
|
8
22
|
|
|
@@ -75,3 +89,64 @@ edit-mode {
|
|
|
75
89
|
color: #fff;
|
|
76
90
|
}
|
|
77
91
|
}
|
|
92
|
+
|
|
93
|
+
#gh-array-panel {
|
|
94
|
+
position: fixed;
|
|
95
|
+
z-index: 999;
|
|
96
|
+
display: none;
|
|
97
|
+
gap: 6px;
|
|
98
|
+
padding: 10px 12px;
|
|
99
|
+
border-radius: 10px;
|
|
100
|
+
background: rgba(255, 255, 255, 0.92);
|
|
101
|
+
color: black;
|
|
102
|
+
border: 1px solid lightgrey;
|
|
103
|
+
box-shadow: 0 10px 30px rgba(0,0,0,.25);
|
|
104
|
+
user-select: none;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
.gh-panel-btn {
|
|
108
|
+
all: unset;
|
|
109
|
+
cursor: pointer;
|
|
110
|
+
|
|
111
|
+
padding: 6px 12px;
|
|
112
|
+
border-radius: 8px;
|
|
113
|
+
border: 1px solid rgba(0,0,0,0.12);
|
|
114
|
+
background: rgba(255,255,255,0.7);
|
|
115
|
+
|
|
116
|
+
font: 14px system-ui, -apple-system, BlinkMacSystemFont, sans-serif;
|
|
117
|
+
color: #000;
|
|
118
|
+
|
|
119
|
+
transition:
|
|
120
|
+
transform 0.3s cubic-bezier(0.22, 1, 0.36, 1),
|
|
121
|
+
box-shadow 0.3s cubic-bezier(0.22, 1, 0.36, 1),
|
|
122
|
+
background-color 0.3s ease-in-out,
|
|
123
|
+
opacity 0.3s ease-in-out;
|
|
124
|
+
|
|
125
|
+
box-shadow:
|
|
126
|
+
0 1px 1px rgba(0,0,0,0.04),
|
|
127
|
+
0 4px 10px rgba(0,0,0,0.08);
|
|
128
|
+
|
|
129
|
+
will-change: transform;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
.gh-panel-btn:hover {
|
|
133
|
+
transform: translateY(-1px) scale(1.001);
|
|
134
|
+
background: rgba(255,255,255,0.9);
|
|
135
|
+
box-shadow:
|
|
136
|
+
0 4px 10px rgba(0,0,0,0.12),
|
|
137
|
+
0 8px 18px rgba(0,0,0,0.16);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
.gh-panel-btn:active {
|
|
141
|
+
transform: translateY(0) scale(0.97);
|
|
142
|
+
box-shadow:
|
|
143
|
+
0 2px 6px rgba(0,0,0,0.18);
|
|
144
|
+
opacity: 0.9;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
.gh-panel-btn:focus-visible {
|
|
148
|
+
outline: none;
|
|
149
|
+
box-shadow:
|
|
150
|
+
0 0 0 3px rgba(0, 125, 250, 0.35),
|
|
151
|
+
0 6px 16px rgba(0,0,0,0.15);
|
|
152
|
+
}
|
|
@@ -57,7 +57,7 @@ class ImageComponent extends GHComponent {
|
|
|
57
57
|
const buildImagePath = (meta, route) => {
|
|
58
58
|
const url = new URL(meta.url);
|
|
59
59
|
const extension = url.pathname.split('.').pop();
|
|
60
|
-
return `/assets/images${route}
|
|
60
|
+
return `/assets/images${route}${meta.file_name}.${extension}`;
|
|
61
61
|
};
|
|
62
62
|
|
|
63
63
|
try {
|
|
@@ -75,13 +75,16 @@ class ImageComponent extends GHComponent {
|
|
|
75
75
|
}
|
|
76
76
|
}
|
|
77
77
|
|
|
78
|
-
|
|
78
|
+
const normalizeUrlPath = (value) =>
|
|
79
|
+
value ? value.trim().replace(/\s+/g, '-') : value;
|
|
80
|
+
|
|
81
|
+
this.generatedImageSrc = normalizeUrlPath(relativeImagePath);
|
|
79
82
|
|
|
80
83
|
// Download image from GudHub (this.dataUrl) to cache (this.generatedImageSrc)
|
|
81
84
|
if (window?.imagesRegeneration) {
|
|
82
85
|
if (this.generatedImageSrc && this.dataUrl) {
|
|
83
86
|
try {
|
|
84
|
-
await fetch(`${this.generatedImageSrc}?source=${this.dataUrl}&mode=ssr`);
|
|
87
|
+
await fetch(`${this.generatedImageSrc}?source=${normalizeUrlPath(this.dataUrl)}&mode=ssr`);
|
|
85
88
|
this.src = this.generatedImageSrc;
|
|
86
89
|
} catch (error) {
|
|
87
90
|
console.error('Failed to fetch generatedImageSrc:', error);
|