alchemy-widget 0.2.2 → 0.2.4

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,14 @@
1
+ ## 0.2.4 (2023-01-30)
2
+
3
+ * Fix the tree structure generation of the `al-toc` element
4
+ * Make `TableOfContents#entries` return a flat list and `TableOfContents#tree` a tree structure
5
+ * Also take siblings of heading elements into account for the `TableOfContents` scrollspy
6
+
7
+ ## 0.2.3 (2023-01-23)
8
+
9
+ * Fix `List` widgets not having their content saved
10
+ * Fix more issues with saving to the database
11
+
1
12
  ## 0.2.2 (2022-12-23)
2
13
 
3
14
  * Fix widgets not being populated properly
@@ -225,7 +225,7 @@ AlchemyWidgets.setAction(async function uploadImage(conduit) {
225
225
  id : result._id,
226
226
  };
227
227
 
228
- let default_path = alchemy.routeUrl('Media::image', params);
228
+ let default_path = alchemy.routeUrl('MediaFile#image', params);
229
229
  let url = RURL.parse(default_path);
230
230
 
231
231
  let path_800 = url.clone();
@@ -389,6 +389,22 @@ Widget.setMethod(function getContextButton() {
389
389
  return button;
390
390
  });
391
391
 
392
+ /**
393
+ * Get a list of elements that could be child widgets
394
+ *
395
+ * @author Jelle De Loecker <jelle@elevenways.be>
396
+ * @since 0.2.3
397
+ * @version 0.2.3
398
+ */
399
+ Widget.setMethod(function getPossibleWidgetChildren() {
400
+
401
+ if (this.instance && typeof this.instance.getPossibleWidgetChildren == 'function') {
402
+ return this.instance.getPossibleWidgetChildren();
403
+ }
404
+
405
+ return this.children;
406
+ });
407
+
392
408
  /**
393
409
  * Show the config button
394
410
  *
@@ -139,19 +139,20 @@ AlchemyWidgets.setMethod(function applyValue(value) {
139
139
  *
140
140
  * @author Jelle De Loecker <jelle@elevenways.be>
141
141
  * @since 0.1.0
142
- * @version 0.1.0
142
+ * @version 0.2.3
143
143
  *
144
144
  * @return {Array}
145
145
  */
146
146
  AlchemyWidgets.setMethod(function getWidgetsConfig() {
147
147
 
148
- let widgets = [],
148
+ let children = this.getPossibleWidgetChildren(),
149
+ widgets = [],
149
150
  child,
150
151
  temp,
151
152
  i;
152
153
 
153
- for (i = 0; i < this.children.length; i++) {
154
- child = this.children[i];
154
+ for (i = 0; i < children.length; i++) {
155
+ child = children[i];
155
156
 
156
157
  if (child instanceof Classes.Alchemy.Element.Widget.Base) {
157
158
  temp = child.value;
@@ -1,3 +1,6 @@
1
+ const RELATED_HEADING = Symbol('related_heading'),
2
+ RELATED_SIBLINGS = Symbol('related_siblings');
3
+
1
4
  /**
2
5
  * The table-of-contents element
3
6
  *
@@ -102,7 +105,7 @@ TableOfContents.setAttribute('truncate-length', {type: 'number'});
102
105
  *
103
106
  * @author Jelle De Loecker <jelle@elevenways.be>
104
107
  * @since 0.1.2
105
- * @version 0.2.1
108
+ * @version 0.2.4
106
109
  */
107
110
  TableOfContents.setProperty(function entries() {
108
111
 
@@ -124,59 +127,42 @@ TableOfContents.setProperty(function entries() {
124
127
 
125
128
  if (wrapper) {
126
129
 
127
- let current_level = null,
128
- last_entry,
129
- elements = wrapper.querySelectorAll(this.elements_selector || 'h1,h2'),
130
- element,
131
- title,
132
- i;
130
+ let heading_level = 0,
131
+ heading,
132
+ i;
133
133
 
134
- for (i = 0; i < elements.length; i++) {
135
- element = elements[i];
134
+ let headings = wrapper.querySelectorAll(this.elements_selector || 'h1, h2, h3, h4, h5, h6'),
135
+ nodes = [];
136
136
 
137
- if (!element.id) {
137
+ for (i = 0; i < headings.length; i++) {
138
+ heading = headings[i];
138
139
 
139
- if (element.hawkejs_id) {
140
- element.id = element.hawkejs_id;
140
+ if (!heading.id) {
141
+
142
+ if (heading.hawkejs_id) {
143
+ heading.id = heading.hawkejs_id;
141
144
  }
142
145
 
143
- if (!element.id) {
146
+ if (!heading.id) {
144
147
  continue;
145
148
  }
146
149
  }
147
150
 
148
151
  let title_element,
149
- starts_level,
150
- ends_level;
152
+ title;
151
153
 
152
154
  if (this.title_selector) {
153
- title_element = element.querySelector(this.title_selector);
155
+ title_element = heading.querySelector(this.title_selector);
154
156
  }
155
157
 
156
158
  if (!title_element) {
157
- title_element = element;
159
+ title_element = heading;
158
160
  }
159
161
 
160
- if (element.nodeName[0] == 'H' && isFinite(element.nodeName[1])) {
161
- let heading_level = +element.nodeName[1];
162
-
163
- if (current_level == null) {
164
- current_level = heading_level;
165
- } else if (heading_level > current_level) {
166
- current_level++;
167
-
168
- if (last_entry) {
169
- last_entry.starts_level = true;
170
- }
171
- } else if (heading_level == current_level && last_entry) {
172
- last_entry.starts_level = false;
173
- } else if (heading_level < current_level) {
174
- current_level--;
175
-
176
- if (last_entry) {
177
- last_entry.ends_level = true;
178
- }
179
- }
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;
180
166
  }
181
167
 
182
168
  title = (title_element.toc_title || title_element.textContent || '').trim();
@@ -190,86 +176,39 @@ TableOfContents.setProperty(function entries() {
190
176
  continue;
191
177
  }
192
178
 
193
- last_entry = {
194
- id : element.id,
195
- level : current_level,
196
- title : title,
197
- element : element,
198
- starts_level : starts_level,
199
- ends_level : ends_level,
179
+ let node = {
180
+ id : heading.id,
181
+ level : heading_level,
182
+ title : title,
183
+ element : heading,
200
184
  };
201
185
 
202
- result.push(last_entry);
186
+ nodes.push(node);
203
187
  }
204
188
 
205
- let ended_level = false,
206
- entries = result,
207
- current_branch,
208
- current_nodes = [];
209
-
210
- result = current_nodes;
211
- last_entry = null;
212
-
213
- for (let entry of entries) {
214
-
215
- if (ended_level) {
216
-
217
- while (entry.level >= current_branch.level) {
218
- current_branch = current_branch.parent;
219
- current_nodes = current_branch?.children || result;
220
-
221
- if (!current_branch) {
222
- current_branch = result[result.length - 1];
223
- break;
224
- }
225
- }
226
-
227
- ended_level = false;
228
- }
229
-
230
- if (!current_branch) {
231
- current_branch = entry;
232
- } else {
233
- entry.parent = current_branch;
234
- }
235
-
236
- if (entry.starts_level) {
237
-
238
- current_branch = last_entry;
239
-
240
- if (!current_branch.children) {
241
- current_branch.children = [];
242
- }
243
-
244
- current_nodes = current_branch.children;
245
- }
246
-
247
- current_nodes.push(entry);
248
-
249
- if (entry.ends_level) {
250
- ended_level = true;
251
- current_branch = entry.parent;
252
- current_nodes = current_branch.children || result;
253
-
254
- if (!current_branch) {
255
- current_branch = result[result.length - 1];
256
- break;
257
- }
258
- }
259
-
260
- last_entry = entry;
261
- }
189
+ result = nodes;
262
190
  }
263
191
 
264
192
  return result;
265
193
  });
266
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
+
267
206
  /**
268
207
  * Added to the dom for the first time
269
208
  *
270
209
  * @author Jelle De Loecker <jelle@elevenways.be>
271
210
  * @since 0.1.2
272
- * @version 0.2.1
211
+ * @version 0.2.4
273
212
  */
274
213
  TableOfContents.setMethod(async function introduced() {
275
214
 
@@ -280,22 +219,51 @@ TableOfContents.setMethod(async function introduced() {
280
219
  let class_name = this.intersection_class || 'visible',
281
220
  first_name = class_name + '-first';
282
221
 
222
+ let intersect_map = new Map();
223
+
283
224
  for (let entry of entries) {
284
- const id = entry.target.getAttribute('id');
225
+ const heading = entry.target[RELATED_HEADING] || entry.target;
226
+ const id = heading.getAttribute('id');
227
+
228
+ if (!id) {
229
+ continue;
230
+ }
285
231
 
286
232
  let query = `a[href="#${id}"]`,
287
233
  element = this.querySelector(query);
288
-
234
+
289
235
  if (!element) {
290
- return;
236
+ continue;
291
237
  }
292
238
 
239
+ let value = intersect_map.get(element) || 0;
240
+
293
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) {
294
262
  element.classList.add(class_name);
295
263
  } else {
296
264
  element.classList.remove(class_name);
297
265
  }
298
- };
266
+ }
299
267
 
300
268
  let is_visible,
301
269
  all_marked = this.querySelectorAll('.' + class_name + ', .' + first_name),
@@ -317,10 +285,31 @@ TableOfContents.setMethod(async function introduced() {
317
285
  seen++;
318
286
  }
319
287
  }
320
-
321
288
  });
322
289
 
323
- for (let entry of this.entries) {
290
+ let entries = this.entries,
291
+ elements = [];
292
+
293
+ for (let entry of entries) {
324
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
+ }
325
314
  }
326
315
  });
@@ -11,6 +11,18 @@
11
11
  */
12
12
  const List = Function.inherits('Alchemy.Widget.Container', 'List');
13
13
 
14
+ /**
15
+ * Get a list of elements that could be child widgets
16
+ *
17
+ * @author Jelle De Loecker <jelle@elevenways.be>
18
+ * @since 0.2.3
19
+ * @version 0.2.3
20
+ */
21
+ List.setMethod(function getPossibleWidgetChildren() {
22
+ let children = this.widget.querySelectorAll(':scope > ul > li > *');
23
+ return children;
24
+ });
25
+
14
26
  /**
15
27
  * Dummy populate method
16
28
  *
@@ -57,7 +57,7 @@ Partial.constitute(function prepareSchema() {
57
57
  let contents = this.createSchema();
58
58
 
59
59
  contents.addField('name', 'String');
60
- contents.addField('content', 'Widgets');
60
+ contents.addField('contents', 'Widgets');
61
61
 
62
62
  this.schema.addField('contents', contents, {array: true});
63
63
  });
@@ -87,7 +87,26 @@ Partial.setMethod(async function populateWidget() {
87
87
 
88
88
  if (this.config?.contents?.length) {
89
89
  for (let entry of this.config.contents) {
90
- variables[entry.name] = entry.contents;
90
+
91
+ let actual_contents;
92
+
93
+ if (entry.contents) {
94
+ if (entry.contents.config) {
95
+ actual_contents = entry.contents.config;
96
+ } else if (entry.contents.widgets) {
97
+ actual_contents = entry.contents.widgets;
98
+
99
+ let type = actual_contents?.[0]?.type;
100
+
101
+ if (type == 'container' || type == 'row') {
102
+ actual_contents = actual_contents[0].config?.widgets;
103
+ } else {
104
+ actual_contents = actual_contents[0];
105
+ }
106
+ }
107
+ }
108
+
109
+ variables[entry.name] = actual_contents;
91
110
  }
92
111
  }
93
112
 
@@ -180,7 +199,7 @@ Partial.setMethod(function _stopEditor() {
180
199
  *
181
200
  * @author Jelle De Loecker <jelle@elevenways.be>
182
201
  * @since 0.1.6
183
- * @version 0.1.6
202
+ * @version 0.2.3
184
203
  *
185
204
  * @return {Object}
186
205
  */
@@ -206,7 +225,23 @@ Partial.setMethod(function syncConfig() {
206
225
  contents.push(widget_config);
207
226
  }
208
227
 
209
- widget_config.contents = sub_widget.value;
228
+ let value = sub_widget.value,
229
+ widget_contents;
230
+
231
+ if (value?.widgets) {
232
+ widget_contents = {
233
+ widgets : [{
234
+ type : 'row',
235
+ config : value,
236
+ }]
237
+ };
238
+ } else {
239
+ widget_contents = {
240
+ widgets: Array.cast(value)
241
+ };
242
+ }
243
+
244
+ widget_config.contents = widget_contents;
210
245
  }
211
246
 
212
247
  return this.config;
@@ -52,4 +52,26 @@ WidgetField.setMethod(function cast(value, to_datasource) {
52
52
  }
53
53
 
54
54
  return value;
55
- });
55
+ });
56
+
57
+ /**
58
+ * Convert to JSON
59
+ *
60
+ * @author Jelle De Loecker <jelle@elevenways.be>
61
+ * @since 0.2.3
62
+ * @version 0.2.3
63
+ *
64
+ * @return {Object}
65
+ */
66
+ WidgetField.setMethod(function toDry() {
67
+
68
+ let {schema, ...options} = this.options;
69
+
70
+ let value = {
71
+ schema : this.schema,
72
+ name : this.name,
73
+ options : options,
74
+ };
75
+
76
+ return {value};
77
+ });
@@ -22,4 +22,18 @@ const WidgetsField = Function.inherits('Alchemy.Field.Schema', function Widgets(
22
22
  }
23
23
 
24
24
  Widgets.super.call(this, schema, name, options);
25
+ });
26
+
27
+ /**
28
+ * Get the client-side options
29
+ *
30
+ * @author Jelle De Loecker <jelle@develry.be>
31
+ * @since 0.2.3
32
+ * @version 0.2.3
33
+ *
34
+ * @return {Object}
35
+ */
36
+ WidgetsField.setMethod(function getOptionsForDrying() {
37
+ let {schema, ...options} = this.options;
38
+ return options;
25
39
  });
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.2",
4
+ "version": "0.2.4",
5
5
  "author": "Jelle De Loecker <jelle@elevenways.be>",
6
6
  "keywords": [
7
7
  "alchemy",
@@ -1,4 +1,4 @@
1
- <% all_entries = self.entries %>
1
+ <% all_entries = self.tree %>
2
2
 
3
3
  {% macro printEntries %}
4
4
  <ol>