alchemy-widget 0.2.4 → 0.2.6

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/CHANGELOG.md CHANGED
@@ -1,3 +1,16 @@
1
+ ## 0.2.6 (2023-02-26)
2
+
3
+ * Replace *EasyMDE* markdown editor with *Toast editor*
4
+ * Implement the `valueHasContent` method
5
+ * Make sure widgets keep a link to the `conduit` instance
6
+
7
+ ## 0.2.5 (2023-02-11)
8
+
9
+ * Add `code_type` field to the `Sourcecode` widget
10
+ * Allow setting the source of an `HTML` widget via its config dialog
11
+ * Make `paste` widget action only append copied widgets to a container
12
+ * Add `replace` widget action to replace a widget with anything in the current clipboard
13
+
1
14
  ## 0.2.4 (2023-01-30)
2
15
 
3
16
  * Fix the tree structure generation of the `al-toc` element
@@ -428,4 +428,20 @@ al-toc {
428
428
 
429
429
  .aw-hidden {
430
430
  opacity: 0.8;
431
+ }
432
+
433
+ al-widget[type="markdown"] {
434
+ .markdown-editor-container {
435
+ background: white;
436
+ }
437
+
438
+ .ProseMirror {
439
+ font-size: 1.1rem;
440
+ --default-font-family: "Roboto", sans-serif;
441
+ font-family: var(--font-family, var(--default-font-family));
442
+ }
443
+
444
+ .toastui-editor-toolbar .toastui-editor-md-tab-container .toastui-editor-tabs {
445
+ display: none;
446
+ }
431
447
  }
@@ -364,7 +364,7 @@ Base.setMethod(async function save() {
364
364
  *
365
365
  * @author Jelle De Loecker <jelle@elevenways.be>
366
366
  * @since 0.2.0
367
- * @version 0.2.1
367
+ * @version 0.2.5
368
368
  */
369
369
  Base.setMethod(async function copyConfigToClipboard() {
370
370
 
@@ -375,7 +375,10 @@ Base.setMethod(async function copyConfigToClipboard() {
375
375
  }
376
376
 
377
377
  value._altype = 'widget';
378
- value.type = this.type;
378
+
379
+ if (this.type) {
380
+ value.type = this.type;
381
+ }
379
382
 
380
383
  let dried = JSON.dry(value, null, '\t');
381
384
 
@@ -389,11 +392,11 @@ Base.setMethod(async function copyConfigToClipboard() {
389
392
  });
390
393
 
391
394
  /**
392
- * Get configuration from the clipboard and return it if it's valid
395
+ * Get any current configuration from the clipboard
393
396
  *
394
397
  * @author Jelle De Loecker <jelle@elevenways.be>
395
398
  * @since 0.2.0
396
- * @version 0.2.1
399
+ * @version 0.2.5
397
400
  */
398
401
  Base.setMethod(async function getConfigFromClipboard() {
399
402
 
@@ -428,7 +431,35 @@ Base.setMethod(async function getConfigFromClipboard() {
428
431
  return false;
429
432
  }
430
433
 
431
- if (result.type != this.type && !this.can_be_removed) {
434
+ return result;
435
+ });
436
+
437
+ /**
438
+ * Get configuration from the clipboard and return it if it's valid
439
+ *
440
+ * @author Jelle De Loecker <jelle@elevenways.be>
441
+ * @since 0.2.5
442
+ * @version 0.2.5
443
+ */
444
+ Base.setMethod(async function getReplaceableConfigFromClipboard() {
445
+
446
+ let result = await this.getConfigFromClipboard();
447
+
448
+ if (result.type == this.type) {
449
+ return result;
450
+ }
451
+
452
+ if (result.type == 'container') {
453
+ return false;
454
+ }
455
+
456
+ if (!this.can_be_removed) {
457
+ return false;
458
+ }
459
+
460
+ let parent_widget = this.parent_container;
461
+
462
+ if (!parent_widget || !parent_widget.editing || !parent_widget.addWidget) {
432
463
  return false;
433
464
  }
434
465
 
@@ -440,13 +471,46 @@ Base.setMethod(async function getConfigFromClipboard() {
440
471
  *
441
472
  * @author Jelle De Loecker <jelle@elevenways.be>
442
473
  * @since 0.2.0
443
- * @version 0.2.0
474
+ * @version 0.2.5
444
475
  */
445
476
  Base.setMethod(async function pasteConfigFromClipboard() {
446
477
 
447
- let result = await this.getConfigFromClipboard();
478
+ let result = await this.getReplaceableConfigFromClipboard();
448
479
 
449
480
  if (result) {
450
- this.value = result;
481
+ this.replaceWithConfig(config);
482
+ }
483
+ });
484
+
485
+ /**
486
+ * Apply the given config
487
+ *
488
+ * @author Jelle De Loecker <jelle@elevenways.be>
489
+ * @since 0.2.0
490
+ * @version 0.2.5
491
+ */
492
+ Base.setMethod(async function replaceWithConfig(config) {
493
+
494
+ if (!config?.type) {
495
+ throw new Error('Unable to replace, config type is empty');
496
+ }
497
+
498
+ if (this.type == config.type) {
499
+ this.value = config;
500
+ return true;
501
+ }
502
+
503
+ let parent_widget = this.parent_container;
504
+
505
+ // If this isn't in an editable widget, it can't be replaced.
506
+ // (Might be a hard-coded widget type)
507
+ if (!parent_widget || !parent_widget.editing || !parent_widget.addWidget) {
508
+ return false;
509
+ }
510
+
511
+ let new_widget = parent_widget.addWidget(config.type, config.config);
512
+
513
+ if (new_widget) {
514
+ this.replaceWith(new_widget);
451
515
  }
452
516
  });
@@ -12,9 +12,16 @@ let Widget = Function.inherits('Alchemy.Element.Widget.Base', 'Widget');
12
12
  *
13
13
  * @author Jelle De Loecker <jelle@elevenways.be>
14
14
  * @since 0.1.0
15
- * @version 0.1.0
15
+ * @version 0.2.5
16
16
  */
17
- Widget.setAttribute('type');
17
+ Widget.setAttribute('type', function getType(type) {
18
+
19
+ if (!type) {
20
+ type = this.instance.constructor.type_name;
21
+ }
22
+
23
+ return type;
24
+ });
18
25
 
19
26
  /**
20
27
  * Mark this as a widget
@@ -199,6 +199,8 @@ AlchemyWidgets.setMethod(function clear() {
199
199
  *
200
200
  * @param {String} type
201
201
  * @param {Object} config
202
+ *
203
+ * @return {HTMLElement}
202
204
  */
203
205
  AlchemyWidgets.setMethod(function addWidget(type, config) {
204
206
 
@@ -229,6 +231,8 @@ AlchemyWidgets.setMethod(function addWidget(type, config) {
229
231
  if (this.editing) {
230
232
  element.startEditor();
231
233
  }
234
+
235
+ return element;
232
236
  });
233
237
 
234
238
  /**
@@ -1,315 +1,315 @@
1
- const RELATED_HEADING = Symbol('related_heading'),
2
- RELATED_SIBLINGS = Symbol('related_siblings');
3
-
4
- /**
5
- * The table-of-contents element
6
- *
7
- * @author Jelle De Loecker <jelle@elevenways.be>
8
- * @since 0.1.2
9
- * @version 0.1.2
10
- */
11
- const TableOfContents = Function.inherits('Alchemy.Element.App', 'TableOfContents');
12
-
13
- /**
14
- * The template to use for the content of this element
15
- *
16
- * @author Jelle De Loecker <jelle@elevenways.be>
17
- * @since 0.1.2
18
- * @version 0.1.2
19
- */
20
- TableOfContents.setTemplateFile('elements/table_of_contents');
21
-
22
- /**
23
- * Set the actual tag name
24
- *
25
- * @author Jelle De Loecker <jelle@elevenways.be>
26
- * @since 0.2.0
27
- * @version 0.2.0
28
- */
29
- TableOfContents.setTagName('AL-TOC');
30
-
31
- /**
32
- * Set the content
33
- *
34
- * @author Jelle De Loecker <jelle@elevenways.be>
35
- * @since 0.1.2
36
- * @version 0.1.2
37
- */
38
- TableOfContents.setAssignedProperty('content');
39
-
40
- /**
41
- * The role of this element
42
- *
43
- * @author Jelle De Loecker <jelle@elevenways.be>
44
- * @since 0.1.2
45
- * @version 0.1.2
46
- */
47
- TableOfContents.setRole('navigation');
48
-
49
- /**
50
- * The parent query
51
- *
52
- * @author Jelle De Loecker <jelle@elevenways.be>
53
- * @since 0.1.2
54
- * @version 0.1.2
55
- */
56
- TableOfContents.setAttribute('parent-selector');
57
-
58
- /**
59
- * The children query
60
- *
61
- * @author Jelle De Loecker <jelle@elevenways.be>
62
- * @since 0.1.2
63
- * @version 0.1.2
64
- */
65
- TableOfContents.setAttribute('children-selector');
66
-
67
- /**
68
- * The elements query
69
- *
70
- * @author Jelle De Loecker <jelle@elevenways.be>
71
- * @since 0.1.2
72
- * @version 0.1.2
73
- */
74
- TableOfContents.setAttribute('elements-selector');
75
-
76
- /**
77
- * The elements query
78
- *
79
- * @author Jelle De Loecker <jelle@elevenways.be>
80
- * @since 0.1.2
81
- * @version 0.1.2
82
- */
83
- TableOfContents.setAttribute('title-selector');
84
-
85
- /**
86
- * The class to add when intersecting (visible)
87
- *
88
- * @author Jelle De Loecker <jelle@elevenways.be>
89
- * @since 0.1.2
90
- * @version 0.1.2
91
- */
92
- TableOfContents.setAttribute('intersection-class');
93
-
94
- /**
95
- * Should titles be truncated?
96
- *
97
- * @author Jelle De Loecker <jelle@elevenways.be>
98
- * @since 0.2.1
99
- * @version 0.2.1
100
- */
101
- TableOfContents.setAttribute('truncate-length', {type: 'number'});
102
-
103
- /**
104
- * Get the entries
105
- *
106
- * @author Jelle De Loecker <jelle@elevenways.be>
107
- * @since 0.1.2
108
- * @version 0.2.4
109
- */
110
- TableOfContents.setProperty(function entries() {
111
-
112
- let result = [],
113
- parent = this.parentElement,
114
- wrapper;
115
-
116
- if (this.parent_selector) {
117
- parent = this.queryParents(this.parent_selector);
118
- }
119
-
120
- if (parent) {
121
- wrapper = parent;
122
- }
123
-
124
- if (wrapper && this.children_selector) {
125
- wrapper = wrapper.querySelector(this.children_selector);
126
- }
127
-
128
- if (wrapper) {
129
-
130
- let heading_level = 0,
131
- heading,
132
- i;
133
-
134
- let headings = wrapper.querySelectorAll(this.elements_selector || 'h1, h2, h3, h4, h5, h6'),
135
- nodes = [];
136
-
137
- for (i = 0; i < headings.length; i++) {
138
- heading = headings[i];
139
-
140
- if (!heading.id) {
141
-
142
- if (heading.hawkejs_id) {
143
- heading.id = heading.hawkejs_id;
144
- }
145
-
146
- if (!heading.id) {
147
- continue;
148
- }
149
- }
150
-
151
- let title_element,
152
- title;
153
-
154
- if (this.title_selector) {
155
- title_element = heading.querySelector(this.title_selector);
156
- }
157
-
158
- if (!title_element) {
159
- title_element = heading;
160
- }
161
-
162
- if (title_element.nodeName[0] == 'H' && isFinite(title_element.nodeName[1])) {
163
- heading_level = +title_element.nodeName[1];
164
- } else if (!heading_level) {
165
- heading_level = 1;
166
- }
167
-
168
- title = (title_element.toc_title || title_element.textContent || '').trim();
169
-
170
- if (this.truncate_length) {
171
- title = title.truncate(this.truncate_length);
172
- }
173
-
174
- // Don't add empty titles
175
- if (!title) {
176
- continue;
177
- }
178
-
179
- let node = {
180
- id : heading.id,
181
- level : heading_level,
182
- title : title,
183
- element : heading,
184
- };
185
-
186
- nodes.push(node);
187
- }
188
-
189
- result = nodes;
190
- }
191
-
192
- return result;
193
- });
194
-
195
- /**
196
- * Get the entries tree
197
- *
198
- * @author Jelle De Loecker <jelle@elevenways.be>
199
- * @since 0.2.4
200
- * @version 0.2.4
201
- */
202
- TableOfContents.setProperty(function tree() {
203
- return Blast.listToTree(this.entries);
204
- });
205
-
206
- /**
207
- * Added to the dom for the first time
208
- *
209
- * @author Jelle De Loecker <jelle@elevenways.be>
210
- * @since 0.1.2
211
- * @version 0.2.4
212
- */
213
- TableOfContents.setMethod(async function introduced() {
214
-
215
- await this.rerender();
216
-
217
- const observer = new IntersectionObserver(entries => {
218
-
219
- let class_name = this.intersection_class || 'visible',
220
- first_name = class_name + '-first';
221
-
222
- let intersect_map = new Map();
223
-
224
- for (let entry of entries) {
225
- const heading = entry.target[RELATED_HEADING] || entry.target;
226
- const id = heading.getAttribute('id');
227
-
228
- if (!id) {
229
- continue;
230
- }
231
-
232
- let query = `a[href="#${id}"]`,
233
- element = this.querySelector(query);
234
-
235
- if (!element) {
236
- continue;
237
- }
238
-
239
- let value = intersect_map.get(element) || 0;
240
-
241
- if (entry.intersectionRatio > 0) {
242
- value++;
243
- } else {
244
- let siblings = heading[RELATED_SIBLINGS];
245
-
246
- if (siblings?.length) {
247
- for (let sibling of siblings) {
248
- if (sibling.isVisible(-150)) {
249
- value++;
250
- break;
251
- }
252
- }
253
- }
254
- }
255
-
256
- intersect_map.set(element, value);
257
- };
258
-
259
- for (let [element, value] of intersect_map) {
260
-
261
- if (value > 0) {
262
- element.classList.add(class_name);
263
- } else {
264
- element.classList.remove(class_name);
265
- }
266
- }
267
-
268
- let is_visible,
269
- all_marked = this.querySelectorAll('.' + class_name + ', .' + first_name),
270
- element,
271
- seen = 0,
272
- i;
273
-
274
- for (i = 0; i < all_marked.length; i++) {
275
- element = all_marked[i];
276
- is_visible = element.classList.contains(class_name);
277
-
278
- if (is_visible && seen == 0) {
279
- element.classList.add(first_name);
280
- } else {
281
- element.classList.remove(first_name);
282
- }
283
-
284
- if (is_visible) {
285
- seen++;
286
- }
287
- }
288
- });
289
-
290
- let entries = this.entries,
291
- elements = [];
292
-
293
- for (let entry of entries) {
294
- observer.observe(entry.element);
295
- elements.push(entry.element);
296
- }
297
-
298
- for (let element of elements) {
299
-
300
- element[RELATED_SIBLINGS] = [];
301
- let sibling = element.nextElementSibling;
302
-
303
- while (sibling) {
304
-
305
- if (elements.includes(sibling)) {
306
- break;
307
- }
308
-
309
- element[RELATED_SIBLINGS].push(sibling);
310
- sibling[RELATED_HEADING] = element;
311
- observer.observe(sibling);
312
- sibling = sibling.nextElementSibling;
313
- }
314
- }
1
+ const RELATED_HEADING = Symbol('related_heading'),
2
+ RELATED_SIBLINGS = Symbol('related_siblings');
3
+
4
+ /**
5
+ * The table-of-contents element
6
+ *
7
+ * @author Jelle De Loecker <jelle@elevenways.be>
8
+ * @since 0.1.2
9
+ * @version 0.1.2
10
+ */
11
+ const TableOfContents = Function.inherits('Alchemy.Element.App', 'TableOfContents');
12
+
13
+ /**
14
+ * The template to use for the content of this element
15
+ *
16
+ * @author Jelle De Loecker <jelle@elevenways.be>
17
+ * @since 0.1.2
18
+ * @version 0.1.2
19
+ */
20
+ TableOfContents.setTemplateFile('elements/table_of_contents');
21
+
22
+ /**
23
+ * Set the actual tag name
24
+ *
25
+ * @author Jelle De Loecker <jelle@elevenways.be>
26
+ * @since 0.2.0
27
+ * @version 0.2.0
28
+ */
29
+ TableOfContents.setTagName('AL-TOC');
30
+
31
+ /**
32
+ * Set the content
33
+ *
34
+ * @author Jelle De Loecker <jelle@elevenways.be>
35
+ * @since 0.1.2
36
+ * @version 0.1.2
37
+ */
38
+ TableOfContents.setAssignedProperty('content');
39
+
40
+ /**
41
+ * The role of this element
42
+ *
43
+ * @author Jelle De Loecker <jelle@elevenways.be>
44
+ * @since 0.1.2
45
+ * @version 0.1.2
46
+ */
47
+ TableOfContents.setRole('navigation');
48
+
49
+ /**
50
+ * The parent query
51
+ *
52
+ * @author Jelle De Loecker <jelle@elevenways.be>
53
+ * @since 0.1.2
54
+ * @version 0.1.2
55
+ */
56
+ TableOfContents.setAttribute('parent-selector');
57
+
58
+ /**
59
+ * The children query
60
+ *
61
+ * @author Jelle De Loecker <jelle@elevenways.be>
62
+ * @since 0.1.2
63
+ * @version 0.1.2
64
+ */
65
+ TableOfContents.setAttribute('children-selector');
66
+
67
+ /**
68
+ * The elements query
69
+ *
70
+ * @author Jelle De Loecker <jelle@elevenways.be>
71
+ * @since 0.1.2
72
+ * @version 0.1.2
73
+ */
74
+ TableOfContents.setAttribute('elements-selector');
75
+
76
+ /**
77
+ * The elements query
78
+ *
79
+ * @author Jelle De Loecker <jelle@elevenways.be>
80
+ * @since 0.1.2
81
+ * @version 0.1.2
82
+ */
83
+ TableOfContents.setAttribute('title-selector');
84
+
85
+ /**
86
+ * The class to add when intersecting (visible)
87
+ *
88
+ * @author Jelle De Loecker <jelle@elevenways.be>
89
+ * @since 0.1.2
90
+ * @version 0.1.2
91
+ */
92
+ TableOfContents.setAttribute('intersection-class');
93
+
94
+ /**
95
+ * Should titles be truncated?
96
+ *
97
+ * @author Jelle De Loecker <jelle@elevenways.be>
98
+ * @since 0.2.1
99
+ * @version 0.2.1
100
+ */
101
+ TableOfContents.setAttribute('truncate-length', {type: 'number'});
102
+
103
+ /**
104
+ * Get the entries
105
+ *
106
+ * @author Jelle De Loecker <jelle@elevenways.be>
107
+ * @since 0.1.2
108
+ * @version 0.2.4
109
+ */
110
+ TableOfContents.setProperty(function entries() {
111
+
112
+ let result = [],
113
+ parent = this.parentElement,
114
+ wrapper;
115
+
116
+ if (this.parent_selector) {
117
+ parent = this.queryParents(this.parent_selector);
118
+ }
119
+
120
+ if (parent) {
121
+ wrapper = parent;
122
+ }
123
+
124
+ if (wrapper && this.children_selector) {
125
+ wrapper = wrapper.querySelector(this.children_selector);
126
+ }
127
+
128
+ if (wrapper) {
129
+
130
+ let heading_level = 0,
131
+ heading,
132
+ i;
133
+
134
+ let headings = wrapper.querySelectorAll(this.elements_selector || 'h1, h2, h3, h4, h5, h6'),
135
+ nodes = [];
136
+
137
+ for (i = 0; i < headings.length; i++) {
138
+ heading = headings[i];
139
+
140
+ if (!heading.id) {
141
+
142
+ if (heading.hawkejs_id) {
143
+ heading.id = heading.hawkejs_id;
144
+ }
145
+
146
+ if (!heading.id) {
147
+ continue;
148
+ }
149
+ }
150
+
151
+ let title_element,
152
+ title;
153
+
154
+ if (this.title_selector) {
155
+ title_element = heading.querySelector(this.title_selector);
156
+ }
157
+
158
+ if (!title_element) {
159
+ title_element = heading;
160
+ }
161
+
162
+ if (title_element.nodeName[0] == 'H' && isFinite(title_element.nodeName[1])) {
163
+ heading_level = +title_element.nodeName[1];
164
+ } else if (!heading_level) {
165
+ heading_level = 1;
166
+ }
167
+
168
+ title = (title_element.toc_title || title_element.textContent || '').trim();
169
+
170
+ if (this.truncate_length) {
171
+ title = title.truncate(this.truncate_length);
172
+ }
173
+
174
+ // Don't add empty titles
175
+ if (!title) {
176
+ continue;
177
+ }
178
+
179
+ let node = {
180
+ id : heading.id,
181
+ level : heading_level,
182
+ title : title,
183
+ element : heading,
184
+ };
185
+
186
+ nodes.push(node);
187
+ }
188
+
189
+ result = nodes;
190
+ }
191
+
192
+ return result;
193
+ });
194
+
195
+ /**
196
+ * Get the entries tree
197
+ *
198
+ * @author Jelle De Loecker <jelle@elevenways.be>
199
+ * @since 0.2.4
200
+ * @version 0.2.4
201
+ */
202
+ TableOfContents.setProperty(function tree() {
203
+ return Blast.listToTree(this.entries);
204
+ });
205
+
206
+ /**
207
+ * Added to the dom for the first time
208
+ *
209
+ * @author Jelle De Loecker <jelle@elevenways.be>
210
+ * @since 0.1.2
211
+ * @version 0.2.4
212
+ */
213
+ TableOfContents.setMethod(async function introduced() {
214
+
215
+ await this.rerender();
216
+
217
+ const observer = new IntersectionObserver(entries => {
218
+
219
+ let class_name = this.intersection_class || 'visible',
220
+ first_name = class_name + '-first';
221
+
222
+ let intersect_map = new Map();
223
+
224
+ for (let entry of entries) {
225
+ const heading = entry.target[RELATED_HEADING] || entry.target;
226
+ const id = heading.getAttribute('id');
227
+
228
+ if (!id) {
229
+ continue;
230
+ }
231
+
232
+ let query = `a[href="#${id}"]`,
233
+ element = this.querySelector(query);
234
+
235
+ if (!element) {
236
+ continue;
237
+ }
238
+
239
+ let value = intersect_map.get(element) || 0;
240
+
241
+ if (entry.intersectionRatio > 0) {
242
+ value++;
243
+ } else {
244
+ let siblings = heading[RELATED_SIBLINGS];
245
+
246
+ if (siblings?.length) {
247
+ for (let sibling of siblings) {
248
+ if (sibling.isVisible(-150)) {
249
+ value++;
250
+ break;
251
+ }
252
+ }
253
+ }
254
+ }
255
+
256
+ intersect_map.set(element, value);
257
+ };
258
+
259
+ for (let [element, value] of intersect_map) {
260
+
261
+ if (value > 0) {
262
+ element.classList.add(class_name);
263
+ } else {
264
+ element.classList.remove(class_name);
265
+ }
266
+ }
267
+
268
+ let is_visible,
269
+ all_marked = this.querySelectorAll('.' + class_name + ', .' + first_name),
270
+ element,
271
+ seen = 0,
272
+ i;
273
+
274
+ for (i = 0; i < all_marked.length; i++) {
275
+ element = all_marked[i];
276
+ is_visible = element.classList.contains(class_name);
277
+
278
+ if (is_visible && seen == 0) {
279
+ element.classList.add(first_name);
280
+ } else {
281
+ element.classList.remove(first_name);
282
+ }
283
+
284
+ if (is_visible) {
285
+ seen++;
286
+ }
287
+ }
288
+ });
289
+
290
+ let entries = this.entries,
291
+ elements = [];
292
+
293
+ for (let entry of entries) {
294
+ observer.observe(entry.element);
295
+ elements.push(entry.element);
296
+ }
297
+
298
+ for (let element of elements) {
299
+
300
+ element[RELATED_SIBLINGS] = [];
301
+ let sibling = element.nextElementSibling;
302
+
303
+ while (sibling) {
304
+
305
+ if (elements.includes(sibling)) {
306
+ break;
307
+ }
308
+
309
+ element[RELATED_SIBLINGS].push(sibling);
310
+ sibling[RELATED_HEADING] = element;
311
+ observer.observe(sibling);
312
+ sibling = sibling.nextElementSibling;
313
+ }
314
+ }
315
315
  });
@@ -148,7 +148,7 @@ Widget.enforceProperty(function is_hidden(new_value) {
148
148
  *
149
149
  * @author Jelle De Loecker <jelle@elevenways.be>
150
150
  * @since 0.1.0
151
- * @version 0.2.1
151
+ * @version 0.2.5
152
152
  */
153
153
  Widget.constitute(function prepareSchema() {
154
154
 
@@ -197,18 +197,57 @@ Widget.constitute(function prepareSchema() {
197
197
  return true;
198
198
  });
199
199
 
200
- copy.setIcon('clipboard');
200
+ copy.setIcon('copy');
201
201
  copy.close_actionbar = true;
202
202
 
203
+ // Add the "replace from clipboard" action
204
+ let replace = this.createAction('replace', 'Replace from clipboard');
205
+
206
+ replace.setHandler(async function replaceAction(widget_el, handle) {
207
+
208
+ let config = await widget_el.getReplaceableConfigFromClipboard();
209
+
210
+ if (!config) {
211
+ return false;
212
+ }
213
+
214
+ widget_el.replaceWithConfig(config);
215
+ });
216
+
217
+ replace.setTester(function replaceActionTester(widget_el, handle) {
218
+ return widget_el.getReplaceableConfigFromClipboard();
219
+ });
220
+
221
+ replace.setIcon('repeat');
222
+ replace.close_actionbar = true;
223
+
203
224
  // Add the "paste from clipboard" action
204
- let paste = this.createAction('paste', 'Replace from clipboard');
225
+ let paste = this.createAction('paste', 'Paste from clipboard');
226
+
227
+ paste.setHandler(async function pasteAction(widget_el, handle) {
205
228
 
206
- paste.setHandler(function pasteAction(widget_el, handle) {
207
- return widget_el.pasteConfigFromClipboard();
229
+ let config = await widget_el.getConfigFromClipboard();
230
+
231
+ if (!config) {
232
+ return false;
233
+ }
234
+
235
+ widget_el.addWidget(config.type, config.config);
208
236
  });
209
237
 
210
- paste.setTester(function pasteAction(widget_el, handle) {
211
- return widget_el.getConfigFromClipboard();
238
+ paste.setTester(async function pasteActionTester(widget_el, handle) {
239
+
240
+ if (!widget_el.addWidget) {
241
+ return false;
242
+ }
243
+
244
+ let config = await widget_el.getConfigFromClipboard();
245
+
246
+ if (!config || config.type == 'container') {
247
+ return false;
248
+ }
249
+
250
+ return true;
212
251
  });
213
252
 
214
253
  paste.setIcon('paste');
@@ -489,7 +528,7 @@ Widget.setStatic(function unDry(obj, custom_method, whenDone) {
489
528
  *
490
529
  * @author Jelle De Loecker <jelle@elevenways.be>
491
530
  * @since 0.1.0
492
- * @version 0.1.0
531
+ * @version 0.2.6
493
532
  *
494
533
  * @param {String} type The typeof widget to create
495
534
  * @param {Object} config The optional config object
@@ -507,6 +546,10 @@ Widget.setMethod(function createChildWidget(type, config) {
507
546
  // Create the instance
508
547
  let instance = new WidgetClass(config);
509
548
 
549
+ if (this.conduit) {
550
+ instance.conduit = this.conduit;
551
+ }
552
+
510
553
  // Set the parent instance!
511
554
  instance.parent_instance = this;
512
555
 
@@ -543,12 +586,16 @@ Widget.setMethod(function toDry() {
543
586
  *
544
587
  * @author Jelle De Loecker <jelle@elevenways.be>
545
588
  * @since 0.1.0
546
- * @version 0.2.0
589
+ * @version 0.2.6
547
590
  *
548
591
  * @return {Array}
549
592
  */
550
593
  Widget.setMethod(async function getActionbarActions() {
551
594
 
595
+ if (!this.constructor.actions) {
596
+ return [];
597
+ }
598
+
552
599
  let sorted = this.constructor.actions.getSorted(),
553
600
  result = [],
554
601
  action;
@@ -969,4 +1016,41 @@ Widget.setMethod(function getHandle() {
969
1016
  }
970
1017
 
971
1018
  return element;
1019
+ });
1020
+
1021
+ /**
1022
+ * See if the given value is considered not-empty for this widget
1023
+ *
1024
+ * @author Jelle De Loecker <jelle@elevenways.be>
1025
+ * @since 0.2.6
1026
+ * @version 0.2.6
1027
+ *
1028
+ * @return {Boolean}
1029
+ */
1030
+ Widget.setMethod(function valueHasContent(value) {
1031
+
1032
+ if (!value || typeof value != 'object') {
1033
+ return false;
1034
+ }
1035
+
1036
+ let entry,
1037
+ key;
1038
+
1039
+ for (key in value) {
1040
+ entry = value[key];
1041
+
1042
+ if (entry) {
1043
+ if (Array.isArray(entry) && entry.length) {
1044
+ return true;
1045
+ }
1046
+
1047
+ if (entry === '') {
1048
+ continue;
1049
+ }
1050
+
1051
+ return true;
1052
+ }
1053
+ }
1054
+
1055
+ return false;
972
1056
  });
@@ -17,11 +17,15 @@ const Html = Function.inherits('Alchemy.Widget', 'Html');
17
17
  *
18
18
  * @author Jelle De Loecker <jelle@elevenways.be>
19
19
  * @since 0.1.0
20
- * @version 0.1.0
20
+ * @version 0.2.5
21
21
  */
22
22
  Html.constitute(function prepareSchema() {
23
23
  // The actual HTML contents
24
- this.schema.addField('html', 'Html');
24
+ this.schema.addField('html', 'Html', {
25
+ title : 'HTML',
26
+ description : 'The HTML sourcecode',
27
+ widget_config_editable : true,
28
+ });
25
29
  });
26
30
 
27
31
  /**
@@ -79,12 +83,24 @@ Html.setMethod(function _stopEditor() {
79
83
  *
80
84
  * @author Jelle De Loecker <jelle@elevenways.be>
81
85
  * @since 0.1.0
82
- * @version 0.2.1
86
+ * @version 0.2.5
83
87
  *
84
88
  * @param {HTMLElement} widget
85
89
  */
86
90
  Html.setMethod(function populateWidget() {
87
- this.widget.innerHTML = this.config.html;
91
+
92
+ let html = this.config.html;
93
+
94
+ if (html == null) {
95
+ html = '';
96
+ }
97
+
98
+ if (this.ckeditor) {
99
+
100
+ this.ckeditor.setData(html);
101
+ } else {
102
+ this.widget.innerHTML = html;
103
+ }
88
104
  });
89
105
 
90
106
  /**
@@ -52,21 +52,35 @@ Markdown.setMethod(function populateWidget() {
52
52
  *
53
53
  * @author Jelle De Loecker <jelle@elevenways.be>
54
54
  * @since 0.1.0
55
- * @version 0.2.0
55
+ * @version 0.2.6
56
56
  */
57
57
  Markdown.setMethod(async function _startEditor() {
58
58
 
59
59
  Hawkejs.removeChildren(this.widget);
60
60
 
61
- hawkejs.scene.enableStyle('https://unpkg.com/easymde/dist/easymde.min.css');
62
- await hawkejs.require('https://unpkg.com/easymde/dist/easymde.min.js');
61
+ hawkejs.scene.enableStyle('https://uicdn.toast.com/editor/latest/toastui-editor.min.css');
62
+ await hawkejs.require('https://uicdn.toast.com/editor/latest/toastui-editor-all.min.js');
63
63
 
64
- let element = this.createElement('textarea');
64
+ const Editor = toastui.Editor
65
+
66
+ let element = this.createElement('div');
67
+ element.classList.add('markdown-editor-container');
65
68
  this.widget.append(element);
66
- element.value = this.config.markdown || '';
67
69
 
68
- const easyMDE = new EasyMDE({element});
69
- this.easy_mde = easyMDE;
70
+ const editor = new Editor({
71
+ el : element,
72
+ height : '900px',
73
+ initialEditType : 'markdown',
74
+ previewStyle : 'vertical',
75
+ usageStatistics : false,
76
+ autofocus : false,
77
+ previewStyle : 'global',
78
+ hideModeSwitch : true,
79
+ initialEditType : 'markdown',
80
+ });
81
+
82
+ this.toast_editor = editor;
83
+ editor.setMarkdown(this.config.markdown || '');
70
84
  });
71
85
 
72
86
  /**
@@ -74,12 +88,19 @@ Markdown.setMethod(async function _startEditor() {
74
88
  *
75
89
  * @author Jelle De Loecker <jelle@elevenways.be>
76
90
  * @since 0.1.0
77
- * @version 0.2.2
91
+ * @version 0.2.6
78
92
  */
79
93
  Markdown.setMethod(function _stopEditor() {
80
94
 
81
95
  Hawkejs.removeChildren(this.widget);
82
- this.easy_mde = null;
96
+
97
+ if (this.toast_editor) {
98
+ try {
99
+ this.toast_editor.destroy();
100
+ } catch (err) {}
101
+ }
102
+
103
+ this.toast_editor = null;
83
104
 
84
105
  return this.loadWidget();
85
106
  });
@@ -89,7 +110,7 @@ Markdown.setMethod(function _stopEditor() {
89
110
  *
90
111
  * @author Jelle De Loecker <jelle@elevenways.be>
91
112
  * @since 0.1.0
92
- * @version 0.2.0
113
+ * @version 0.2.6
93
114
  *
94
115
  * @return {Object}
95
116
  */
@@ -97,8 +118,8 @@ Markdown.setMethod(function syncConfig() {
97
118
 
98
119
  let value = '';
99
120
 
100
- if (this.easy_mde) {
101
- value = this.easy_mde.value();
121
+ if (this.toast_editor) {
122
+ value = this.toast_editor.getMarkdown();
102
123
  }
103
124
 
104
125
  this.config.markdown = value;
@@ -16,11 +16,18 @@ const Sourcecode = Function.inherits('Alchemy.Widget', 'Sourcecode');
16
16
  *
17
17
  * @author Jelle De Loecker <jelle@elevenways.be>
18
18
  * @since 0.1.0
19
- * @version 0.1.0
19
+ * @version 0.2.5
20
20
  */
21
21
  Sourcecode.constitute(function prepareSchema() {
22
22
  // The actual sourcecode contents
23
23
  this.schema.addField('sourcecode', 'Text');
24
+
25
+ // The type of sourcecode
26
+ this.schema.addField('code_type', 'String', {
27
+ title : 'Code type',
28
+ description : 'The type of sourcecode',
29
+ widget_config_editable : true,
30
+ });
24
31
  });
25
32
 
26
33
  /**
@@ -37,7 +44,7 @@ Sourcecode.setProperty('sourcecode_field', 'sourcecode');
37
44
  *
38
45
  * @author Jelle De Loecker <jelle@elevenways.be>
39
46
  * @since 0.1.0
40
- * @version 0.2.1
47
+ * @version 0.2.5
41
48
  *
42
49
  * @param {HTMLElement} widget
43
50
  */
@@ -53,7 +60,12 @@ Sourcecode.setMethod(function populateWidget() {
53
60
  pre = this.createElement('pre');
54
61
 
55
62
  pre.append(code);
56
- code.innerText = source;
63
+ code.textContent = source;
64
+
65
+ if (this.config.code_type) {
66
+ code.classList.add('language-' + this.config.code_type);
67
+ pre.classList.add('language-' + this.config.code_type);
68
+ }
57
69
 
58
70
  this.widget.append(pre);
59
71
  });
@@ -5,7 +5,7 @@
5
5
  *
6
6
  * @author Jelle De Loecker <jelle@elevenways.be>
7
7
  * @since 0.1.0
8
- * @version 0.1.0
8
+ * @version 0.2.6
9
9
  */
10
10
  const WidgetField = Function.inherits('Alchemy.Field.Schema', function Widget(schema, name, options) {
11
11
 
@@ -17,12 +17,15 @@ const WidgetField = Function.inherits('Alchemy.Field.Schema', function Widget(sc
17
17
  options.type = 'text';
18
18
  }
19
19
 
20
+ // Already set the options (they'll be changed by the super call too though)
21
+ this.options = options;
22
+
20
23
  // A custom schema should NOT be passed to this class, this class uses
21
24
  // a fixed schema that should not be altered.
22
25
  // But because that's exactly what happens when cloning (like preparing
23
26
  // the data to be sent to Hawkejs) we have to allow it anyway
24
27
  if (!options.schema) {
25
- let WidgetClass = Classes.Alchemy.Widget.Widget.getMember(options.type),
28
+ let WidgetClass = this.widget_class,
26
29
  sub_schema = WidgetClass.schema.clone();
27
30
 
28
31
  options.schema = sub_schema;
@@ -31,6 +34,24 @@ const WidgetField = Function.inherits('Alchemy.Field.Schema', function Widget(sc
31
34
  Widget.super.call(this, schema, name, options);
32
35
  });
33
36
 
37
+ /**
38
+ * Get the constructor of the widget class
39
+ *
40
+ * @author Jelle De Loecker <jelle@elevenways.be>
41
+ * @since 0.2.6
42
+ * @version 0.2.6
43
+ *
44
+ * @type {Function}
45
+ */
46
+ WidgetField.setProperty(function widget_class() {
47
+
48
+ if (!this.options?.type) {
49
+ return;
50
+ }
51
+
52
+ return Classes.Alchemy.Widget.Widget.getMember(this.options.type)
53
+ });
54
+
34
55
  /**
35
56
  * Cast the given value to this field's type
36
57
  *
@@ -75,3 +96,33 @@ WidgetField.setMethod(function toDry() {
75
96
 
76
97
  return {value};
77
98
  });
99
+
100
+ /**
101
+ * See if the given value is considered not-empty for this field
102
+ *
103
+ * @author Jelle De Loecker <jelle@elevenways.be>
104
+ * @since 0.2.6
105
+ * @version 0.2.6
106
+ *
107
+ * @param {Mixed} value
108
+ *
109
+ * @return {Boolean}
110
+ */
111
+ WidgetField.setMethod(function valueHasContent(value) {
112
+
113
+ if (!value) {
114
+ return false;
115
+ }
116
+
117
+ let constructor = this.widget_class;
118
+
119
+ if (!constructor) {
120
+ return true;
121
+ }
122
+
123
+ value = this.cast(value);
124
+
125
+ let instance = new constructor();
126
+
127
+ return instance.valueHasContent(value);
128
+ });
@@ -36,4 +36,28 @@ const WidgetsField = Function.inherits('Alchemy.Field.Schema', function Widgets(
36
36
  WidgetsField.setMethod(function getOptionsForDrying() {
37
37
  let {schema, ...options} = this.options;
38
38
  return options;
39
+ });
40
+
41
+ /**
42
+ * See if the given value is considered not-empty for this field
43
+ *
44
+ * @author Jelle De Loecker <jelle@elevenways.be>
45
+ * @since 0.2.6
46
+ * @version 0.2.6
47
+ *
48
+ * @param {Mixed} value
49
+ *
50
+ * @return {Boolean}
51
+ */
52
+ WidgetsField.setMethod(function valueHasContent(value) {
53
+
54
+ if (!value) {
55
+ return false;
56
+ }
57
+
58
+ if (!value.widgets?.length) {
59
+ return false;
60
+ }
61
+
62
+ return true;
39
63
  });
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "alchemy-widget",
3
3
  "description": "The widget plugin for the AlchemyMVC",
4
- "version": "0.2.4",
4
+ "version": "0.2.6",
5
5
  "author": "Jelle De Loecker <jelle@elevenways.be>",
6
6
  "keywords": [
7
7
  "alchemy",
@@ -11,7 +11,7 @@
11
11
  ],
12
12
  "peerDependencies": {
13
13
  "alchemymvc" : ">=1.2.0",
14
- "alchemy-form": "~0.2.1"
14
+ "alchemy-form": "~0.2.4"
15
15
  },
16
16
  "repository": "11ways/alchemy-widget",
17
17
  "license": "MIT",