alchemy-widget 0.1.2 → 0.1.5

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,17 @@
1
+ ## 0.1.5 (2022-07-14)
2
+
3
+ * Unselect widgets when stopping the editor
4
+ * Add front-end save ability to widgets
5
+
6
+ ## 0.1.4 (2022-06-23)
7
+
8
+ * Use `he-context-menu` element to show widgets to add
9
+ * Add widget actions to move across container boundaries
10
+
11
+ ## 0.1.3 (2022-03-21)
12
+
13
+ * Catch and print errors when appending a widget to a container
14
+
1
15
  ## 0.1.2 (2022-02-20)
2
16
 
3
17
  * Add `table-of-contents` element and widget
@@ -11,7 +11,7 @@ alchemy-widgets-column {
11
11
  }
12
12
 
13
13
  > * {
14
- flex: 1;
14
+ flex: 10;
15
15
  }
16
16
  }
17
17
 
@@ -53,7 +53,7 @@ alchemy-widgets {
53
53
 
54
54
  alchemy-widgets-row {
55
55
  flex-flow: row;
56
- flex: 1 1 auto;
56
+ flex: 10 10 auto;
57
57
 
58
58
  &.aw-editing {
59
59
  padding-right: 5rem;
@@ -91,7 +91,7 @@ alchemy-widgets-column > alchemy-widgets-column,
91
91
  }
92
92
 
93
93
  alchemy-widgets-column {
94
- flex: 1 1 auto;
94
+ flex: 10 10 auto;
95
95
  }
96
96
 
97
97
  alchemy-widgets-column,
@@ -149,8 +149,8 @@ alchemy-widget-add-area {
149
149
  display: flex;
150
150
  align-content: center;
151
151
  min-height: 26px;
152
- min-width: 26px;
153
152
  align-items: center;
153
+ text-transform: uppercase;
154
154
 
155
155
  &:hover {
156
156
  color: #388ae5;
package/bootstrap.js ADDED
@@ -0,0 +1,6 @@
1
+ Router.add({
2
+ name : 'AlchemyWidgets#save',
3
+ methods : 'post',
4
+ paths : '/api/alchemywidgets/save',
5
+ policy : 'logged_in',
6
+ });
@@ -0,0 +1,111 @@
1
+ /**
2
+ * The Alchemy Widgets Controller class
3
+ *
4
+ * @author Jelle De Loecker <jelle@elevenways.be>
5
+ * @since 0.1.5
6
+ * @version 0.1.5
7
+ */
8
+ const AlchemyWidgets = Function.inherits('Alchemy.Controller', 'AlchemyWidgets');
9
+
10
+ /**
11
+ * Aggregate all the records to save
12
+ *
13
+ * @author Jelle De Loecker <jelle@elevenways.be>
14
+ * @since 0.1.5
15
+ * @version 0.1.5
16
+ *
17
+ * @param {Object[]} fields
18
+ *
19
+ * @return {Document[]}
20
+ */
21
+ AlchemyWidgets.setMethod(async function aggregate(widgets) {
22
+
23
+ let result = {};
24
+
25
+ for (let widget of widgets) {
26
+
27
+ if (!widget || !widget.model || !widget.field) {
28
+ throw new Error('Unable to save Widget: no model or field was given');
29
+ }
30
+
31
+ const model = alchemy.getModel(widget.model);
32
+
33
+ if (!model) {
34
+ throw new Error('Unable to save Widget: model "' + widget.model + '" not found');
35
+ }
36
+
37
+ let field = model.getField(widget.field);
38
+
39
+ if (!field) {
40
+ throw new Error('Unable to save Widget: field "' + widget.field + '" does not exist inside model "' + widget.model + '"');
41
+ }
42
+
43
+ let record;
44
+
45
+ if (widget.pk) {
46
+
47
+ if (result[widget.pk]) {
48
+ record = result[widget.pk];
49
+ } else {
50
+ record = await model.findByPk(widget.pk);
51
+ result[widget.pk] = record;
52
+ }
53
+ } else {
54
+
55
+ if (result[model.name]) {
56
+ record = result[model.name];
57
+ } else {
58
+ record = model.createDocument();
59
+ result[model.name] = record;
60
+ }
61
+ }
62
+
63
+ let field_language = widget.field_languages?.[widget.field];
64
+
65
+ if (field_language) {
66
+ if (!record[widget.field]) {
67
+ record[widget.field] = {};
68
+ }
69
+
70
+ record[widget.field][field_language] = widget.value;
71
+ } else {
72
+ record[widget.field] = widget.value;
73
+ }
74
+ }
75
+
76
+ return Object.values(result);
77
+ });
78
+
79
+ /**
80
+ * The save action
81
+ *
82
+ * @author Jelle De Loecker <jelle@elevenways.be>
83
+ * @since 0.1.5
84
+ * @version 0.1.5
85
+ *
86
+ * @param {Conduit} conduit
87
+ */
88
+ AlchemyWidgets.setAction(async function save(conduit) {
89
+
90
+ const body = conduit.body;
91
+
92
+ if (!body || !body.widgets?.length) {
93
+ return conduit.error('Unable to save Widgets: no widgets were given');
94
+ }
95
+
96
+ let records = await this.aggregate(body.widgets);
97
+
98
+ let saved_pks = [];
99
+
100
+ for (let record of records) {
101
+ await record.save();
102
+ saved_pks.push(record.$pk);
103
+ }
104
+
105
+ let result = {
106
+ saved_pks,
107
+ saved : true,
108
+ };
109
+
110
+ conduit.end(result);
111
+ });
@@ -42,9 +42,120 @@ Base.setAssignedProperty('instance');
42
42
  *
43
43
  * @author Jelle De Loecker <jelle@elevenways.be>
44
44
  * @since 0.1.0
45
- * @version 0.1.0
45
+ * @version 0.1.4
46
46
  */
47
- Base.setProperty('parent_container');
47
+ Base.setProperty(function parent_container() {
48
+
49
+ let container,
50
+ current = this.parentElement;
51
+
52
+ while (current) {
53
+ if (current.is_container) {
54
+ container = current;
55
+ break;
56
+ }
57
+
58
+ current = current.parentElement;
59
+ }
60
+
61
+ return container;
62
+ });
63
+
64
+ /**
65
+ * The next container across container boundaries
66
+ *
67
+ * @author Jelle De Loecker <jelle@elevenways.be>
68
+ * @since 0.1.4
69
+ * @version 0.1.4
70
+ */
71
+ Base.setProperty(function next_container() {
72
+ return this.getSiblingContainer('next');
73
+ });
74
+
75
+ /**
76
+ * The previous container across container boundaries
77
+ *
78
+ * @author Jelle De Loecker <jelle@elevenways.be>
79
+ * @since 0.1.4
80
+ * @version 0.1.4
81
+ */
82
+ Base.setProperty(function previous_container() {
83
+ return this.getSiblingContainer('previous');
84
+ });
85
+
86
+ /**
87
+ * Is this the root element?
88
+ *
89
+ * @author Jelle De Loecker <jelle@elevenways.be>
90
+ * @since 0.1.5
91
+ * @version 0.1.5
92
+ */
93
+ Base.setProperty(function is_root_widget() {
94
+ return !this.parent_container;
95
+ });
96
+
97
+ /**
98
+ * Can this widget be saved?
99
+ *
100
+ * @author Jelle De Loecker <jelle@elevenways.be>
101
+ * @since 0.1.5
102
+ * @version 0.1.5
103
+ */
104
+ Base.setProperty(function can_be_saved() {
105
+
106
+ if (!this.is_root_widget) {
107
+ return false;
108
+ }
109
+
110
+ if (!this.record || !this.field) {
111
+ return false;
112
+ }
113
+
114
+ return true;
115
+ });
116
+
117
+ /**
118
+ * Get a sibling container
119
+ *
120
+ * @author Jelle De Loecker <jelle@elevenways.be>
121
+ * @since 0.1.4
122
+ * @version 0.1.4
123
+ */
124
+ Base.setMethod(function getSiblingContainer(type) {
125
+ let property;
126
+
127
+ if (type == 'next') {
128
+ property = 'nextElementSibling';
129
+ } else if (type == 'previous') {
130
+ property = 'previousElementSibling';
131
+ } else {
132
+ return false;
133
+ }
134
+
135
+ if (!this[property] && !this.parent_container) {
136
+ return;
137
+ }
138
+
139
+ let next = this[property];
140
+
141
+ if (next) {
142
+ if (next.is_container) {
143
+ return next;
144
+ }
145
+
146
+ if (next.tagName != 'ALCHEMY-WIDGET-ADD-AREA') {
147
+ return false;
148
+ }
149
+ }
150
+
151
+ next = this.parent_container[property];
152
+
153
+ if (next && next.is_container) {
154
+ return next;
155
+ }
156
+
157
+ return false;
158
+ });
48
159
 
49
160
  /**
50
161
  * Look for a context variable
@@ -128,5 +239,57 @@ Base.setMethod(function introduced() {
128
239
  if (this.hasAttribute('editing')) {
129
240
  this.startEditor();
130
241
  }
242
+ });
243
+
244
+ /**
245
+ * Get the configuration used to save data
246
+ *
247
+ * @author Jelle De Loecker <jelle@elevenways.be>
248
+ * @since 0.1.5
249
+ * @version 0.1.5
250
+ */
251
+ Base.setMethod(function gatherSaveData() {
252
+
253
+ if (!this.is_root_widget) {
254
+ return;
255
+ }
256
+
257
+ if (!this.record || !this.field) {
258
+ return;
259
+ }
260
+
261
+ let result = {
262
+ model : this.record.$model_name,
263
+ pk : this.record.$pk,
264
+ field : this.field,
265
+ value : this.value,
266
+ field_languages : this.record.$hold.translated_fields,
267
+ };
268
+
269
+ return result;
270
+ });
271
+
272
+ /**
273
+ * Save the current configuration
274
+ *
275
+ * @author Jelle De Loecker <jelle@elevenways.be>
276
+ * @since 0.1.5
277
+ * @version 0.1.5
278
+ */
279
+ Base.setMethod(async function save() {
280
+
281
+ let data = this.gatherSaveData();
282
+
283
+ if (!data) {
284
+ return;
285
+ }
286
+
287
+ let config = {
288
+ href : alchemy.routeUrl('AlchemyWidgets#save'),
289
+ post : {
290
+ widgets: [data]
291
+ }
292
+ };
131
293
 
294
+ let result = await alchemy.fetch(config);
132
295
  });
@@ -34,6 +34,24 @@ Widget.setAttribute('type');
34
34
  */
35
35
  Widget.setProperty('is_alchemy_widget', true);
36
36
 
37
+ /**
38
+ * The database record to work with
39
+ *
40
+ * @author Jelle De Loecker <jelle@elevenways.be>
41
+ * @since 0.1.5
42
+ * @version 0.1.5
43
+ */
44
+ Widget.setAssignedProperty('record');
45
+
46
+ /**
47
+ * The fieldname in the record to work with
48
+ *
49
+ * @author Jelle De Loecker <jelle@elevenways.be>
50
+ * @since 0.1.5
51
+ * @version 0.1.5
52
+ */
53
+ Widget.setAssignedProperty('field');
54
+
37
55
  /**
38
56
  * Is this widget being edited?
39
57
  *
@@ -55,7 +73,7 @@ Widget.setProperty(function editing() {
55
73
  *
56
74
  * @author Jelle De Loecker <jelle@elevenways.be>
57
75
  * @since 0.1.0
58
- * @version 0.1.0
76
+ * @version 0.1.5
59
77
  */
60
78
  Widget.setProperty(function value() {
61
79
  return {
@@ -63,6 +81,45 @@ Widget.setProperty(function value() {
63
81
  config : this.instance.syncConfig(),
64
82
  }
65
83
  }, function setValue(value) {
84
+ this.applyValue(value);
85
+ });
86
+
87
+ /**
88
+ * Received a new record
89
+ *
90
+ * @author Jelle De Loecker <jelle@elevenways.be>
91
+ * @since 0.1.5
92
+ * @version 0.1.5
93
+ */
94
+ Widget.setMethod(function onRecordAssignment(new_record, old_val) {
95
+ if (new_record && this.field) {
96
+ let value = new_record[this.field];
97
+ this.applyValue(value);
98
+ }
99
+ });
100
+
101
+ /**
102
+ * Received a new field name
103
+ *
104
+ * @author Jelle De Loecker <jelle@elevenways.be>
105
+ * @since 0.1.5
106
+ * @version 0.1.5
107
+ */
108
+ Widget.setMethod(function onFieldAssignment(new_field, old_val) {
109
+ if (new_field && this.record) {
110
+ let value = this.record[new_field];
111
+ this.applyValue(value);
112
+ }
113
+ });
114
+
115
+ /**
116
+ * Apply the given value
117
+ *
118
+ * @author Jelle De Loecker <jelle@elevenways.be>
119
+ * @since 0.1.5
120
+ * @version 0.1.5
121
+ */
122
+ Widget.setMethod(function applyValue(value) {
66
123
 
67
124
  let config,
68
125
  type;
@@ -136,7 +193,7 @@ Widget.setMethod(function startEditor() {
136
193
  *
137
194
  * @author Jelle De Loecker <jelle@elevenways.be>
138
195
  * @since 0.1.0
139
- * @version 0.1.0
196
+ * @version 0.1.5
140
197
  */
141
198
  Widget.setMethod(function stopEditor() {
142
199
 
@@ -144,6 +201,7 @@ Widget.setMethod(function stopEditor() {
144
201
  throw new Error('Unable to stop the editor: this widget element has no accompanying instance');
145
202
  }
146
203
 
204
+ this.unselectWidget();
147
205
  this.instance.stopEditor();
148
206
  this.removeEditEventListeners();
149
207
  });
@@ -32,6 +32,15 @@ AlchemyWidgets.setStatic('custom_element_prefix', 'alchemy-widgets');
32
32
  */
33
33
  AlchemyWidgets.setProperty('add_edit_event_listeners', false);
34
34
 
35
+ /**
36
+ * Indicate this is a container
37
+ *
38
+ * @author Jelle De Loecker <jelle@elevenways.be>
39
+ * @since 0.1.4
40
+ * @version 0.1.4
41
+ */
42
+ AlchemyWidgets.setProperty('is_container', true);
43
+
35
44
  /**
36
45
  * Context variables
37
46
  *
@@ -74,6 +83,17 @@ AlchemyWidgets.setProperty(function value() {
74
83
  return result;
75
84
 
76
85
  }, function setValue(value) {
86
+ this.applyValue(value);
87
+ });
88
+
89
+ /**
90
+ * Apply the given value
91
+ *
92
+ * @author Jelle De Loecker <jelle@elevenways.be>
93
+ * @since 0.1.5
94
+ * @version 0.1.5
95
+ */
96
+ AlchemyWidgets.setMethod(function applyValue(value) {
77
97
 
78
98
  let widgets,
79
99
  config;
@@ -179,15 +199,26 @@ AlchemyWidgets.setMethod(function clear() {
179
199
  *
180
200
  * @author Jelle De Loecker <jelle@elevenways.be>
181
201
  * @since 0.1.0
182
- * @version 0.1.0
202
+ * @version 0.1.3
183
203
  *
184
204
  * @param {String} type
185
205
  * @param {Object} config
186
206
  */
187
207
  AlchemyWidgets.setMethod(function addWidget(type, config) {
188
208
 
209
+ let instance;
210
+
189
211
  // Create the instance of the widget
190
- let instance = this.instance.createChildWidget(type, config);
212
+ try {
213
+ instance = this.instance.createChildWidget(type, config);
214
+ } catch (err) {
215
+ config = {
216
+ original_config : config,
217
+ html : '<pre>' + err.message + '\n' + err.stack + '</pre>',
218
+ };
219
+
220
+ instance = this.instance.createChildWidget('html', config);
221
+ }
191
222
 
192
223
  // Attach the renderer
193
224
  instance.hawkejs_renderer = this.hawkejs_renderer;
@@ -195,9 +226,6 @@ AlchemyWidgets.setMethod(function addWidget(type, config) {
195
226
  // Get the actual widget HTML element
196
227
  let element = instance.element;
197
228
 
198
- // Make the element know what its parent container is
199
- element.parent_container = this;
200
-
201
229
  this._appendWidgetElement(element);
202
230
 
203
231
  element.value = config;
@@ -26,11 +26,9 @@ let AddArea = Function.inherits('Alchemy.Element.Widget.Base', function AlchemyW
26
26
  * @since 0.1.0
27
27
  * @version 0.1.0
28
28
  */
29
- AddArea.setMethod(function showTypes() {
29
+ AddArea.setMethod(function showTypes(event) {
30
30
 
31
- let that = this,
32
- types_element = this.querySelector('.widget-types'),
33
- widgets = alchemy.getClassGroup('widgets');
31
+ let that = this;
34
32
 
35
33
  let context_button = document.querySelector('alchemy-widget-context');
36
34
 
@@ -38,31 +36,25 @@ AddArea.setMethod(function showTypes() {
38
36
  context_button.unselectedWidget();
39
37
  }
40
38
 
41
- this.classList.add('show-types');
39
+ let context = this.createElement('he-context-menu');
42
40
 
43
- Hawkejs.removeChildren(types_element);
41
+ let widgets = Object.values(alchemy.getClassGroup('widgets')).sortByPath(1, 'title');
44
42
 
45
- for (let key in widgets) {
43
+ for (let widget of widgets) {
46
44
 
47
- if (key == 'container') {
45
+ if (widget.type_name == 'container') {
48
46
  continue;
49
47
  }
50
48
 
51
- let widget = widgets[key];
52
-
53
- let button = this.createElement('button');
54
- button.setAttribute('data-type', key);
55
- button.textContent = widget.title;
56
-
57
- button.addEventListener('click', function onClick(e) {
58
- e.preventDefault();
59
- that.classList.remove('show-types');
60
-
61
- that.parentElement.addWidget(key);
49
+ context.addEntry({
50
+ title : widget.title,
51
+ icon : null,
52
+ }, e => {
53
+ that.parentElement.addWidget(widget.type_name);
62
54
  });
63
-
64
- types_element.append(button);
65
55
  }
56
+
57
+ context.show(event);
66
58
  });
67
59
 
68
60
  /**
@@ -107,7 +99,7 @@ AddArea.setMethod(function introduced() {
107
99
 
108
100
  add_button.addEventListener('click', function onClick(e) {
109
101
  e.preventDefault();
110
- that.showTypes();
102
+ that.showTypes(e);
111
103
  });
112
104
 
113
105
  let context_button = this.querySelector('.menu-button');
@@ -1,193 +1,193 @@
1
- /**
2
- * The table-of-contents element
3
- *
4
- * @author Jelle De Loecker <jelle@elevenways.be>
5
- * @since 0.1.2
6
- * @version 0.1.2
7
- */
8
- const TableOfContents = Function.inherits('Alchemy.Element.App', 'TableOfContents');
9
-
10
- /**
11
- * The template to use for the content of this element
12
- *
13
- * @author Jelle De Loecker <jelle@elevenways.be>
14
- * @since 0.1.2
15
- * @version 0.1.2
16
- */
17
- TableOfContents.setTemplateFile('elements/table_of_contents');
18
-
19
- /**
20
- * Set the content
21
- *
22
- * @author Jelle De Loecker <jelle@elevenways.be>
23
- * @since 0.1.2
24
- * @version 0.1.2
25
- */
26
- TableOfContents.setAssignedProperty('content');
27
-
28
- /**
29
- * The role of this element
30
- *
31
- * @author Jelle De Loecker <jelle@elevenways.be>
32
- * @since 0.1.2
33
- * @version 0.1.2
34
- */
35
- TableOfContents.setRole('navigation');
36
-
37
- /**
38
- * The parent query
39
- *
40
- * @author Jelle De Loecker <jelle@elevenways.be>
41
- * @since 0.1.2
42
- * @version 0.1.2
43
- */
44
- TableOfContents.setAttribute('parent-selector');
45
-
46
- /**
47
- * The children query
48
- *
49
- * @author Jelle De Loecker <jelle@elevenways.be>
50
- * @since 0.1.2
51
- * @version 0.1.2
52
- */
53
- TableOfContents.setAttribute('children-selector');
54
-
55
- /**
56
- * The elements query
57
- *
58
- * @author Jelle De Loecker <jelle@elevenways.be>
59
- * @since 0.1.2
60
- * @version 0.1.2
61
- */
62
- TableOfContents.setAttribute('elements-selector');
63
-
64
- /**
65
- * The elements query
66
- *
67
- * @author Jelle De Loecker <jelle@elevenways.be>
68
- * @since 0.1.2
69
- * @version 0.1.2
70
- */
71
- TableOfContents.setAttribute('title-selector');
72
-
73
- /**
74
- * The class to add when intersecting (visible)
75
- *
76
- * @author Jelle De Loecker <jelle@elevenways.be>
77
- * @since 0.1.2
78
- * @version 0.1.2
79
- */
80
- TableOfContents.setAttribute('intersection-class');
81
-
82
- /**
83
- * Get the entries
84
- *
85
- * @author Jelle De Loecker <jelle@elevenways.be>
86
- * @since 0.1.2
87
- * @version 0.1.2
88
- */
89
- TableOfContents.setProperty(function entries() {
90
-
91
- let result = [],
92
- parent = this.parentElement,
93
- wrapper;
94
-
95
- if (this.parent_selector) {
96
- parent = this.queryParents(this.parent_selector);
97
- }
98
-
99
- if (parent) {
100
- wrapper = parent;
101
- }
102
-
103
- if (wrapper && this.children_selector) {
104
- wrapper = wrapper.querySelector(this.children_selector);
105
- }
106
-
107
- if (wrapper) {
108
- let elements = wrapper.querySelectorAll(this.elements_selector || 'h1,h2'),
109
- element,
110
- title,
111
- i;
112
-
113
- for (i = 0; i < elements.length; i++) {
114
- element = elements[i];
115
-
116
- if (!element.id) {
117
-
118
- if (element.hawkejs_id) {
119
- element.id = element.hawkejs_id;
120
- }
121
-
122
- if (!element.id) {
123
- continue;
124
- }
125
- }
126
-
127
- let title_element;
128
-
129
- if (this.title_selector) {
130
- title_element = element.querySelector(this.title_selector);
131
- }
132
-
133
- if (!title_element) {
134
- title_element = element;
135
- }
136
-
137
- title = (title_element.toc_title || title_element.textContent || '').trim();
138
-
139
- title = title.truncate(30);
140
-
141
- // Don't add empty titles
142
- if (!title) {
143
- continue;
144
- }
145
-
146
- result.push({
147
- id : element.id,
148
- title : title,
149
- element : element,
150
- });
151
- }
152
- }
153
-
154
- return result;
155
- });
156
-
157
- /**
158
- * Added to the dom for the first time
159
- *
160
- * @author Jelle De Loecker <jelle@elevenways.be>
161
- * @since 0.1.2
162
- * @version 0.1.2
163
- */
164
- TableOfContents.setMethod(async function introduced() {
165
-
166
- await this.rerender();
167
-
168
- const observer = new IntersectionObserver(entries => {
169
-
170
- let class_name = this.intersection_class || 'visible';
171
-
172
- for (let entry of entries) {
173
- const id = entry.target.getAttribute('id');
174
-
175
- let query = `a[href="#${id}"]`,
176
- element = this.querySelector(query);
177
-
178
- if (!element) {
179
- return;
180
- }
181
-
182
- if (entry.intersectionRatio > 0) {
183
- element.classList.add(class_name);
184
- } else {
185
- element.classList.remove(class_name);
186
- }
187
- };
188
- });
189
-
190
- for (let entry of this.entries) {
191
- observer.observe(entry.element);
192
- }
1
+ /**
2
+ * The table-of-contents element
3
+ *
4
+ * @author Jelle De Loecker <jelle@elevenways.be>
5
+ * @since 0.1.2
6
+ * @version 0.1.2
7
+ */
8
+ const TableOfContents = Function.inherits('Alchemy.Element.App', 'TableOfContents');
9
+
10
+ /**
11
+ * The template to use for the content of this element
12
+ *
13
+ * @author Jelle De Loecker <jelle@elevenways.be>
14
+ * @since 0.1.2
15
+ * @version 0.1.2
16
+ */
17
+ TableOfContents.setTemplateFile('elements/table_of_contents');
18
+
19
+ /**
20
+ * Set the content
21
+ *
22
+ * @author Jelle De Loecker <jelle@elevenways.be>
23
+ * @since 0.1.2
24
+ * @version 0.1.2
25
+ */
26
+ TableOfContents.setAssignedProperty('content');
27
+
28
+ /**
29
+ * The role of this element
30
+ *
31
+ * @author Jelle De Loecker <jelle@elevenways.be>
32
+ * @since 0.1.2
33
+ * @version 0.1.2
34
+ */
35
+ TableOfContents.setRole('navigation');
36
+
37
+ /**
38
+ * The parent query
39
+ *
40
+ * @author Jelle De Loecker <jelle@elevenways.be>
41
+ * @since 0.1.2
42
+ * @version 0.1.2
43
+ */
44
+ TableOfContents.setAttribute('parent-selector');
45
+
46
+ /**
47
+ * The children query
48
+ *
49
+ * @author Jelle De Loecker <jelle@elevenways.be>
50
+ * @since 0.1.2
51
+ * @version 0.1.2
52
+ */
53
+ TableOfContents.setAttribute('children-selector');
54
+
55
+ /**
56
+ * The elements query
57
+ *
58
+ * @author Jelle De Loecker <jelle@elevenways.be>
59
+ * @since 0.1.2
60
+ * @version 0.1.2
61
+ */
62
+ TableOfContents.setAttribute('elements-selector');
63
+
64
+ /**
65
+ * The elements query
66
+ *
67
+ * @author Jelle De Loecker <jelle@elevenways.be>
68
+ * @since 0.1.2
69
+ * @version 0.1.2
70
+ */
71
+ TableOfContents.setAttribute('title-selector');
72
+
73
+ /**
74
+ * The class to add when intersecting (visible)
75
+ *
76
+ * @author Jelle De Loecker <jelle@elevenways.be>
77
+ * @since 0.1.2
78
+ * @version 0.1.2
79
+ */
80
+ TableOfContents.setAttribute('intersection-class');
81
+
82
+ /**
83
+ * Get the entries
84
+ *
85
+ * @author Jelle De Loecker <jelle@elevenways.be>
86
+ * @since 0.1.2
87
+ * @version 0.1.2
88
+ */
89
+ TableOfContents.setProperty(function entries() {
90
+
91
+ let result = [],
92
+ parent = this.parentElement,
93
+ wrapper;
94
+
95
+ if (this.parent_selector) {
96
+ parent = this.queryParents(this.parent_selector);
97
+ }
98
+
99
+ if (parent) {
100
+ wrapper = parent;
101
+ }
102
+
103
+ if (wrapper && this.children_selector) {
104
+ wrapper = wrapper.querySelector(this.children_selector);
105
+ }
106
+
107
+ if (wrapper) {
108
+ let elements = wrapper.querySelectorAll(this.elements_selector || 'h1,h2'),
109
+ element,
110
+ title,
111
+ i;
112
+
113
+ for (i = 0; i < elements.length; i++) {
114
+ element = elements[i];
115
+
116
+ if (!element.id) {
117
+
118
+ if (element.hawkejs_id) {
119
+ element.id = element.hawkejs_id;
120
+ }
121
+
122
+ if (!element.id) {
123
+ continue;
124
+ }
125
+ }
126
+
127
+ let title_element;
128
+
129
+ if (this.title_selector) {
130
+ title_element = element.querySelector(this.title_selector);
131
+ }
132
+
133
+ if (!title_element) {
134
+ title_element = element;
135
+ }
136
+
137
+ title = (title_element.toc_title || title_element.textContent || '').trim();
138
+
139
+ title = title.truncate(30);
140
+
141
+ // Don't add empty titles
142
+ if (!title) {
143
+ continue;
144
+ }
145
+
146
+ result.push({
147
+ id : element.id,
148
+ title : title,
149
+ element : element,
150
+ });
151
+ }
152
+ }
153
+
154
+ return result;
155
+ });
156
+
157
+ /**
158
+ * Added to the dom for the first time
159
+ *
160
+ * @author Jelle De Loecker <jelle@elevenways.be>
161
+ * @since 0.1.2
162
+ * @version 0.1.2
163
+ */
164
+ TableOfContents.setMethod(async function introduced() {
165
+
166
+ await this.rerender();
167
+
168
+ const observer = new IntersectionObserver(entries => {
169
+
170
+ let class_name = this.intersection_class || 'visible';
171
+
172
+ for (let entry of entries) {
173
+ const id = entry.target.getAttribute('id');
174
+
175
+ let query = `a[href="#${id}"]`,
176
+ element = this.querySelector(query);
177
+
178
+ if (!element) {
179
+ return;
180
+ }
181
+
182
+ if (entry.intersectionRatio > 0) {
183
+ element.classList.add(class_name);
184
+ } else {
185
+ element.classList.remove(class_name);
186
+ }
187
+ };
188
+ });
189
+
190
+ for (let entry of this.entries) {
191
+ observer.observe(entry.element);
192
+ }
193
193
  });
@@ -38,6 +38,7 @@ Toolbar.setMethod(function showWidgetActions(widget) {
38
38
  button.classList.add('aw-toolbar-button');
39
39
 
40
40
  button.innerHTML = action.getButtonHTML();
41
+ button.setAttribute('title', action.title);
41
42
 
42
43
  let is_selected = action.isAlreadySelected(widget);
43
44
 
@@ -95,7 +95,7 @@ Widget.enforceProperty(function hawkejs_renderer(new_value) {
95
95
  *
96
96
  * @author Jelle De Loecker <jelle@elevenways.be>
97
97
  * @since 0.1.0
98
- * @version 0.1.0
98
+ * @version 0.1.5
99
99
  */
100
100
  Widget.constitute(function prepareSchema() {
101
101
 
@@ -107,15 +107,30 @@ Widget.constitute(function prepareSchema() {
107
107
 
108
108
  // Extra classnames for the wrapper
109
109
  this.schema.addField('wrapper_class_names', 'String', {
110
+ title : 'Wrapper CSS classes',
111
+ description : 'Configure extra CSS classes to the wrapper `alchemy-widget` element',
110
112
  array: true,
111
113
  widget_config_editable: true,
112
114
  });
113
115
 
114
116
  // Classnames for the inserted element (if any)
115
117
  this.schema.addField('main_class_names', 'String', {
118
+ title : 'Main CSS classes',
119
+ description : 'Configure extra CSS classes for the main inserted element',
116
120
  array: true,
117
121
  });
118
122
 
123
+ // Add the "save" action
124
+ let save = this.createAction('save', 'Save');
125
+
126
+ save.setHandler(function removeAction(widget_el, handle) {
127
+ return widget_el.save();
128
+ });
129
+
130
+ save.setTester(function saveAction(widget_el, handle) {
131
+ return widget_el.can_be_saved;
132
+ });
133
+
119
134
  // Add the remove action
120
135
  let remove = this.createAction('remove', 'Remove');
121
136
 
@@ -179,6 +194,47 @@ Widget.constitute(function prepareSchema() {
179
194
 
180
195
  move_right.setIcon('gg-arrow-right');
181
196
 
197
+ // The move-in-left action
198
+ let move_in_left = this.createAction('move-in-left', 'Move in left');
199
+
200
+ move_in_left.close_toolbar = true;
201
+
202
+ move_in_left.setHandler(function moveLeftAction(widget_el, handle) {
203
+ // Hawkejs custom element method!
204
+ let container = handle.previous_container;
205
+
206
+ if (container) {
207
+ container.append(handle);
208
+ }
209
+ });
210
+
211
+ move_in_left.setTester(function moveLeftTest(widget_el, handle) {
212
+ return !!handle.previous_container;
213
+ });
214
+
215
+ move_in_left.setIcon('gg-arrow-left');
216
+
217
+ // The move-in-right action
218
+ let move_in_right = this.createAction('move-in-right', 'Move in right');
219
+
220
+ move_in_right.close_toolbar = true;
221
+
222
+ move_in_right.setHandler(function moveRightAction(widget_el, handle) {
223
+ // Hawkejs custom element method!
224
+ let container = handle.next_container;
225
+
226
+ if (container) {
227
+ container.prepend(handle);
228
+ }
229
+ });
230
+
231
+ move_in_right.setTester(function moveRightTest(widget_el, handle) {
232
+ console.log('Right test of:', handle)
233
+ return !!handle.next_container;
234
+ });
235
+
236
+ move_in_right.setIcon('gg-arrow-right');
237
+
182
238
  let css_class = this.createAction('css-class', 'CSS Class');
183
239
 
184
240
  css_class.setHandler(function setCssClass(widget_el, handle) {
@@ -212,11 +268,11 @@ Widget.setStatic(function createSchema() {
212
268
  *
213
269
  * @author Jelle De Loecker <jelle@elevenways.be>
214
270
  * @since 0.1.0
215
- * @version 0.1.0
271
+ * @version 0.1.4
216
272
  */
217
- Widget.setStatic(function createAction(name) {
273
+ Widget.setStatic(function createAction(name, title) {
218
274
 
219
- let action = new Classes.Alchemy.Widget.Action(name);
275
+ let action = new Classes.Alchemy.Widget.Action(name, title || name.titleize());
220
276
 
221
277
  this.actions.set(name, action);
222
278
 
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.1.2",
4
+ "version": "0.1.5",
5
5
  "author": "Jelle De Loecker <jelle@elevenways.be>",
6
6
  "keywords": [
7
7
  "alchemy",