alchemy-widget 0.2.5 → 0.2.7

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,15 @@
1
+ ## 0.2.7 (2023-04-20)
2
+
3
+ * Use `Element#hideForEveryone()` when hiding widgets
4
+ * Add the `al-editor-toolbar` and accompanying elements
5
+ * Use the `al-code-input` element again for editing markdown widgets
6
+
7
+ ## 0.2.6 (2023-02-26)
8
+
9
+ * Replace *EasyMDE* markdown editor with *Toast editor*
10
+ * Implement the `valueHasContent` method
11
+ * Make sure widgets keep a link to the `conduit` instance
12
+
1
13
  ## 0.2.5 (2023-02-11)
2
14
 
3
15
  * Add `code_type` field to the `Sourcecode` widget
@@ -97,6 +97,36 @@ al-widget-toolbar {
97
97
  background-color: rgb(53, 53, 53);
98
98
  }
99
99
  }
100
+
101
+ .watchers:empty {
102
+ display: none;
103
+ }
104
+
105
+ .watchers {
106
+ margin: 0 4rem;
107
+ }
108
+
109
+ al-user-avatar {
110
+ .bubble-representation {
111
+ width: 4.5rem;
112
+ height: 4.5rem;
113
+ font-size: 3.5rem;
114
+ line-height: 4.5rem;
115
+ }
116
+ }
117
+
118
+ [data-area="buttons"] {
119
+ display: flex;
120
+ gap: 1rem;
121
+
122
+ a {
123
+ text-decoration: none;
124
+
125
+ micro-copy {
126
+ text-decoration: underline;
127
+ }
128
+ }
129
+ }
100
130
  }
101
131
 
102
132
  al-widgets,
@@ -428,4 +458,62 @@ al-toc {
428
458
 
429
459
  .aw-hidden {
430
460
  opacity: 0.8;
461
+ }
462
+
463
+ al-widget[type="markdown"] {
464
+ .markdown-editor-container {
465
+ background: white;
466
+ }
467
+
468
+ .ProseMirror {
469
+ font-size: 1.1rem;
470
+ --default-font-family: "Roboto", sans-serif;
471
+ font-family: var(--font-family, var(--default-font-family));
472
+ }
473
+
474
+ .toastui-editor-toolbar .toastui-editor-md-tab-container .toastui-editor-tabs {
475
+ display: none;
476
+ }
477
+ }
478
+
479
+ al-user-avatar-group {
480
+ display: flex;
481
+
482
+ // Let all the al-user-avatar elements overlap a little.
483
+ > * {
484
+ position: relative;
485
+ z-index: 10;
486
+ margin-left: -0.5rem;
487
+ }
488
+
489
+ // The first element should not be overlapped
490
+ > :first-child {
491
+ margin-left: 0;
492
+ }
493
+
494
+ > *:hover {
495
+ z-index: 101;
496
+ }
497
+ }
498
+
499
+ al-user-avatar {
500
+ display: block;
501
+ cursor: default;
502
+ user-select: none;
503
+
504
+ .bubble-representation {
505
+ display: block;
506
+ width: 2rem;
507
+ height: 2rem;
508
+ border-radius: 50%;
509
+ background-color: var(--avatar-bg-color, #388ae5);
510
+ color: white;
511
+ font-size: 1.2rem;
512
+ font-weight: bold;
513
+ text-align: center;
514
+ line-height: 2rem;
515
+
516
+ // Add a shadow
517
+ box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.1), 0 1px 2px rgba(0, 0, 0, 0.2);
518
+ }
431
519
  }
@@ -0,0 +1,226 @@
1
+ const PREPARED = Symbol('prepared');
2
+
3
+ /**
4
+ * The base toolbar element
5
+ *
6
+ * @author Jelle De Loecker <jelle@elevenways.be>
7
+ * @since 0.2.7
8
+ * @version 0.2.7
9
+ */
10
+ let Toolbar = Function.inherits('Alchemy.Element.Form.Stateful', 'Alchemy.Element.Widget', 'BaseToolbar');
11
+
12
+ /**
13
+ * Don't register this as a custom element
14
+ * The `false` argument makes sure child classes don't also set this property
15
+ *
16
+ * @author Jelle De Loecker <jelle@elevenways.be>
17
+ * @since 0.2.7
18
+ * @version 0.2.7
19
+ */
20
+ Toolbar.makeAbstractClass();
21
+
22
+ /**
23
+ * Set the custom element prefix
24
+ *
25
+ * @author Jelle De Loecker <jelle@elevenways.be>
26
+ * @since 0.2.7
27
+ * @version 0.2.7
28
+ */
29
+ Toolbar.setStatic('custom_element_prefix', 'al');
30
+
31
+ /**
32
+ * The stylesheet to load for this element
33
+ *
34
+ * @author Jelle De Loecker <jelle@elevenways.be>
35
+ * @since 0.2.7
36
+ * @version 0.2.7
37
+ */
38
+ Toolbar.setStylesheetFile('alchemy_widgets');
39
+
40
+ /**
41
+ * The toolbar manager
42
+ *
43
+ * @author Jelle De Loecker <jelle@elevenways.be>
44
+ * @since 0.2.7
45
+ * @version 0.2.7
46
+ */
47
+ Toolbar.setAssignedProperty('toolbar_manager');
48
+
49
+ /**
50
+ * The optional watchers element
51
+ *
52
+ * @author Jelle De Loecker <jelle@elevenways.be>
53
+ * @since 0.2.7
54
+ * @version 0.2.7
55
+ */
56
+ Toolbar.addElementGetter('watchers_element', '.watchers');
57
+
58
+ /**
59
+ * A new toolbar manager was assigned
60
+ *
61
+ * @author Jelle De Loecker <jelle@elevenways.be>
62
+ * @since 0.2.7
63
+ * @version 0.2.7
64
+ */
65
+ Toolbar.setMethod(function onToolbarManagerAssignment(manager, old_manager) {
66
+ if (manager != old_manager) {
67
+ this.prepareToolbarManager(manager, old_manager);
68
+ }
69
+ });
70
+
71
+ /**
72
+ * Prepare the toolbar manager
73
+ *
74
+ * @author Jelle De Loecker <jelle@elevenways.be>
75
+ * @since 0.2.7
76
+ * @version 0.2.7
77
+ */
78
+ Toolbar.setMethod(function prepareToolbarManager(manager, old_manager) {
79
+
80
+ if (this.toolbar_manager != manager) {
81
+ this.toolbar_manager = manager;
82
+ }
83
+
84
+ if (manager && manager[PREPARED]) {
85
+ return;
86
+ }
87
+
88
+ if (manager) {
89
+ manager[PREPARED] = true;
90
+ }
91
+
92
+ if (old_manager) {
93
+ old_manager.release();
94
+ }
95
+
96
+ if (!manager) {
97
+ return;
98
+ }
99
+
100
+ let clear_counts = {};
101
+
102
+ manager.watchProperty('document_watcher', watcher => {
103
+ this.attachDocumentWatcher(watcher);
104
+ });
105
+
106
+ let elements = this.querySelectorAll('[data-toolbar]');
107
+
108
+ for (let i = 0; i < elements.length; i++) {
109
+ let element = elements[i],
110
+ name = element.dataset.toolbar;
111
+
112
+ if (name) {
113
+ manager.watchProperty(name, value => {
114
+ element.textContent = value;
115
+ });
116
+ }
117
+ }
118
+
119
+ manager.watchQueue('render_template', (area, template, variables) => {
120
+
121
+ let current_clear_count = clear_counts[area];
122
+
123
+ let area_element = this.getAreaElement(area);
124
+
125
+ if (area_element) {
126
+ hawkejs.renderToElements(template, variables, (err, elements) => {
127
+
128
+ if (current_clear_count != clear_counts[area]) {
129
+ // The area has been cleared in the meantime
130
+ return;
131
+ }
132
+
133
+ if (err) {
134
+ console.error('Error rendering template', err);
135
+ return;
136
+ }
137
+
138
+ area_element.append(...elements);
139
+ });
140
+ }
141
+ });
142
+
143
+ manager.watchQueue('clear_area', area => {
144
+
145
+ if (!clear_counts[area]) {
146
+ clear_counts[area] = 0;
147
+ }
148
+
149
+ let area_element = this.getAreaElement(area);
150
+
151
+ clear_counts[area]++;
152
+
153
+ if (area_element) {
154
+ area_element.innerHTML = '';
155
+ }
156
+ });
157
+
158
+ });
159
+
160
+ /**
161
+ * Attach to the given watcher
162
+ *
163
+ * @author Jelle De Loecker <jelle@elevenways.be>
164
+ * @since 0.2.7
165
+ * @version 0.2.7
166
+ */
167
+ Toolbar.setMethod(function attachDocumentWatcher(watcher) {
168
+
169
+ let old_watcher = this.current_watcher;
170
+ let watchers_element = this.watchers_element;
171
+ this.current_watcher = watcher;
172
+
173
+ if (old_watcher && old_watcher != watcher) {
174
+ old_watcher.release();
175
+ }
176
+
177
+ if (watchers_element) {
178
+ if (watcher) {
179
+
180
+ watcher.watchProperty('viewers', async (viewers) => {
181
+
182
+ let users = [];
183
+
184
+ if (viewers) {
185
+ let viewer,
186
+ i;
187
+
188
+ for (i = 0; i < viewers.length; i++) {
189
+ viewer = viewers[i];
190
+
191
+ if (!viewer.info) {
192
+ // The info isn't always set due to race conditions
193
+ viewer.info = await watcher.getUserInfo(viewer.user_id);
194
+ }
195
+
196
+ users.push(viewer.info);
197
+ }
198
+ }
199
+
200
+ watchers_element.setUsers(users);
201
+ });
202
+ } else {
203
+ watchers_element.clear();
204
+ }
205
+ }
206
+
207
+ if (!watcher) {
208
+ return;
209
+ }
210
+ });
211
+
212
+ /**
213
+ * Get an area element
214
+ *
215
+ * @author Jelle De Loecker <jelle@elevenways.be>
216
+ * @since 0.2.7
217
+ * @version 0.2.7
218
+ */
219
+ Toolbar.setMethod(function getAreaElement(area) {
220
+
221
+ if (!area) {
222
+ return;
223
+ }
224
+
225
+ return this.querySelector('[data-area="' + area + '"]');
226
+ });
@@ -0,0 +1,30 @@
1
+ /**
2
+ * The al-editor-toolbar element
3
+ *
4
+ * @author Jelle De Loecker <jelle@elevenways.be>
5
+ * @since 0.2.7
6
+ * @version 0.2.7
7
+ */
8
+ let Toolbar = Function.inherits('Alchemy.Element.Widget.BaseToolbar', 'EditorToolbar');
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.2.7
15
+ * @version 0.2.7
16
+ */
17
+ Toolbar.setTemplateFile('widget/elements/al_editor_toolbar');
18
+
19
+ /**
20
+ * Added to the dom for the first time
21
+ *
22
+ * @author Jelle De Loecker <jelle@elevenways.be>
23
+ * @since 0.2.7
24
+ * @version 0.2.7
25
+ */
26
+ Toolbar.setMethod(async function introduced() {
27
+
28
+ this.prepareToolbarManager(this.toolbar_manager);
29
+
30
+ });
@@ -0,0 +1,137 @@
1
+ /**
2
+ * The al-symbol-group element
3
+ *
4
+ * @author Jelle De Loecker <jelle@elevenways.be>
5
+ * @since 0.2.7
6
+ * @version 0.2.7
7
+ */
8
+ let UserAvatar = Function.inherits('Alchemy.Element.App', 'Alchemy.Element.Widget', 'UserAvatar');
9
+
10
+ /**
11
+ * The stylesheet to load for this element
12
+ *
13
+ * @author Jelle De Loecker <jelle@elevenways.be>
14
+ * @since 0.2.7
15
+ * @version 0.2.7
16
+ */
17
+ UserAvatar.setStylesheetFile('alchemy_widgets');
18
+
19
+ /**
20
+ * Set the custom element prefix
21
+ *
22
+ * @author Jelle De Loecker <jelle@elevenways.be>
23
+ * @since 0.2.7
24
+ * @version 0.2.7
25
+ */
26
+ UserAvatar.setStatic('custom_element_prefix', 'al');
27
+
28
+ /**
29
+ * The template to use for the content of this element
30
+ *
31
+ * @author Jelle De Loecker <jelle@elevenways.be>
32
+ * @since 0.2.7
33
+ * @version 0.2.7
34
+ */
35
+ UserAvatar.setTemplateFile('widget/elements/al_user_avatar');
36
+
37
+ /**
38
+ * The user data
39
+ *
40
+ * @author Jelle De Loecker <jelle@elevenways.be>
41
+ * @since 0.2.7
42
+ * @version 0.2.7
43
+ */
44
+ UserAvatar.setAssignedProperty('user');
45
+
46
+ /**
47
+ * Get the pk of the user
48
+ *
49
+ * @author Jelle De Loecker <jelle@elevenways.be>
50
+ * @since 0.2.7
51
+ * @version 0.2.7
52
+ */
53
+ UserAvatar.setProperty(function pk() {
54
+
55
+ let result = '';
56
+
57
+ if (this.user) {
58
+ result = this.user.$pk || this.user.pk || this.user.id || '';
59
+ }
60
+
61
+ return String(result);
62
+ });
63
+
64
+ /**
65
+ * Get the first name of the user
66
+ *
67
+ * @author Jelle De Loecker <jelle@elevenways.be>
68
+ * @since 0.2.7
69
+ * @version 0.2.7
70
+ */
71
+ UserAvatar.setProperty(function first_name() {
72
+
73
+ let result = '';
74
+
75
+ if (this.user) {
76
+ result = this.user.firstname || this.user.first_name || this.user.username || '';
77
+ }
78
+
79
+ return result;
80
+ });
81
+
82
+ /**
83
+ * Get the first letter for the user
84
+ *
85
+ * @author Jelle De Loecker <jelle@elevenways.be>
86
+ * @since 0.2.7
87
+ * @version 0.2.7
88
+ */
89
+ UserAvatar.setProperty(function first_letter() {
90
+
91
+ let result = this.first_name;
92
+
93
+ if (result) {
94
+ result = result[0].toUpperCase();
95
+ } else {
96
+ result = '?';
97
+ }
98
+
99
+ return result;
100
+ });
101
+
102
+ /**
103
+ * Set (new) user info
104
+ *
105
+ * @author Jelle De Loecker <jelle@elevenways.be>
106
+ * @since 0.2.7
107
+ * @version 0.2.7
108
+ */
109
+ UserAvatar.setMethod(function setUserInfo(user) {
110
+ this.user = user;
111
+ });
112
+
113
+ /**
114
+ * Received user info
115
+ *
116
+ * @author Jelle De Loecker <jelle@elevenways.be>
117
+ * @since 0.2.7
118
+ * @version 0.2.7
119
+ */
120
+ UserAvatar.setMethod(function onUserAssignment(user) {
121
+
122
+ let bg_color;
123
+
124
+ if (user) {
125
+ bg_color = user.color;
126
+ }
127
+
128
+ if (!bg_color) {
129
+ this.style.removeProperty('--avatar-bg-color');
130
+ } else {
131
+ this.style.setProperty('--avatar-bg-color', bg_color);
132
+ }
133
+
134
+ let first_name = this.first_name;
135
+
136
+ this.setAttribute('title', first_name);
137
+ });
@@ -0,0 +1,131 @@
1
+ /**
2
+ * The al-user-avatar-group element
3
+ *
4
+ * @author Jelle De Loecker <jelle@elevenways.be>
5
+ * @since 0.2.7
6
+ * @version 0.2.7
7
+ */
8
+ let UserAvatarGroup = Function.inherits('Alchemy.Element.App', 'Alchemy.Element.Widget', 'UserAvatarGroup');
9
+
10
+ /**
11
+ * The stylesheet to load for this element
12
+ *
13
+ * @author Jelle De Loecker <jelle@elevenways.be>
14
+ * @since 0.2.7
15
+ * @version 0.2.7
16
+ */
17
+ UserAvatarGroup.setStylesheetFile('alchemy_widgets');
18
+
19
+ /**
20
+ * Set the custom element prefix
21
+ *
22
+ * @author Jelle De Loecker <jelle@elevenways.be>
23
+ * @since 0.2.7
24
+ * @version 0.2.7
25
+ */
26
+ UserAvatarGroup.setStatic('custom_element_prefix', 'al');
27
+
28
+ /**
29
+ * Clear all the avatars
30
+ *
31
+ * @author Jelle De Loecker <jelle@elevenways.be>
32
+ * @since 0.2.7
33
+ * @version 0.2.7
34
+ */
35
+ UserAvatarGroup.setMethod(function clear() {
36
+
37
+ let existing_avatars = this.querySelectorAll('al-user-avatar'),
38
+ avatar;
39
+
40
+ for (let i = 0; i < existing_avatars.length; i++) {
41
+ avatar = existing_avatars[i];
42
+ avatar.remove();
43
+ }
44
+ });
45
+
46
+ /**
47
+ * Set all the users to show
48
+ *
49
+ * @author Jelle De Loecker <jelle@elevenways.be>
50
+ * @since 0.2.7
51
+ * @version 0.2.7
52
+ *
53
+ * @param {Object[]} users
54
+ */
55
+ UserAvatarGroup.setMethod(function setUsers(users) {
56
+
57
+ if (!users || !users.length) {
58
+ this.clear();
59
+ return;
60
+ }
61
+
62
+ let existing_avatars = this.querySelectorAll('al-user-avatar'),
63
+ keep_avatars = [],
64
+ updated,
65
+ avatar,
66
+ user;
67
+
68
+ users = [...users];
69
+
70
+ for (let i = 0; i < users.length; i++) {
71
+ user = users[i];
72
+
73
+ updated = false;
74
+
75
+ for (let j = 0; j < existing_avatars.length; j++) {
76
+ avatar = existing_avatars[j];
77
+
78
+ if (avatar.pk == user.pk) {
79
+ keep_avatars.push(avatar);
80
+ avatar.setUserInfo(user);
81
+ updated = true;
82
+ break;
83
+ }
84
+ }
85
+
86
+ if (updated) {
87
+ continue;
88
+ }
89
+
90
+ avatar = this.createElement('al-user-avatar');
91
+ avatar.setUserInfo(user);
92
+ this.appendChild(avatar);
93
+ keep_avatars.push(avatar);
94
+ }
95
+
96
+ for (let i = 0; i < existing_avatars.length; i++) {
97
+ avatar = existing_avatars[i];
98
+
99
+ if (keep_avatars.indexOf(avatar) == -1) {
100
+ avatar.remove();
101
+ }
102
+ }
103
+ });
104
+
105
+ /**
106
+ * Add a user to the group
107
+ *
108
+ * @author Jelle De Loecker <jelle@elevenways.be>
109
+ * @since 0.2.7
110
+ * @version 0.2.7
111
+ *
112
+ * @param {Object} user
113
+ */
114
+ UserAvatarGroup.setMethod(function addUser(user) {
115
+
116
+ let existing_avatars = this.querySelectorAll('al-user-avatar'),
117
+ avatar;
118
+
119
+ for (let i = 0; i < existing_avatars.length; i++) {
120
+ avatar = existing_avatars[i];
121
+
122
+ if (avatar.pk == user.pk) {
123
+ avatar.setUserInfo(user);
124
+ return;
125
+ }
126
+ }
127
+
128
+ avatar = this.createElement('al-user-avatar');
129
+ avatar.setUserInfo(user);
130
+ this.appendChild(avatar);
131
+ });
@@ -5,25 +5,7 @@
5
5
  * @since 0.2.0
6
6
  * @version 0.2.0
7
7
  */
8
- let Toolbar = Function.inherits('Alchemy.Element.Form.Stateful', 'Alchemy.Element.Widget', 'WidgetToolbar');
9
-
10
- /**
11
- * The stylesheet to load for this element
12
- *
13
- * @author Jelle De Loecker <jelle@elevenways.be>
14
- * @since 0.2.0
15
- * @version 0.2.0
16
- */
17
- Toolbar.setStylesheetFile('alchemy_widgets');
18
-
19
- /**
20
- * Set the custom element prefix
21
- *
22
- * @author Jelle De Loecker <jelle@elevenways.be>
23
- * @since 0.2.0
24
- * @version 0.2.0
25
- */
26
- Toolbar.setStatic('custom_element_prefix', 'al');
8
+ let Toolbar = Function.inherits('Alchemy.Element.Widget.BaseToolbar', 'WidgetToolbar');
27
9
 
28
10
  /**
29
11
  * The template to use for the content of this element
@@ -287,6 +269,28 @@ Toolbar.setMethod(async function saveAllAndUpdateButtonStates(before_stop) {
287
269
  */
288
270
  Toolbar.setMethod(function introduced() {
289
271
 
272
+ let set_manager_id = null;
273
+
274
+ const setManager = (manager) => {
275
+
276
+ if (set_manager_id) {
277
+ clearTimeout(set_manager_id);
278
+ }
279
+
280
+ set_manager_id = setTimeout(() => {
281
+ this.prepareToolbarManager(manager);
282
+ }, 250);
283
+ };
284
+
285
+ if (this.toolbar_manager) {
286
+ this.prepareToolbarManager(this.toolbar_manager);
287
+ } else {
288
+ let manager = hawkejs.scene.exposed.toolbar_manager;
289
+ if (manager) {
290
+ this.prepareToolbarManager(manager);
291
+ }
292
+ }
293
+
290
294
  this.button_start.addEventListener('activate', async e => {
291
295
  this.startEditing();
292
296
  });
@@ -307,8 +311,24 @@ Toolbar.setMethod(function introduced() {
307
311
  }
308
312
  });
309
313
 
310
- hawkejs.scene.on('opening_url', e => {
314
+ hawkejs.scene.on('rendered', (variables, renderer) => {
315
+ if (variables.toolbar_manager) {
316
+ setManager(variables.toolbar_manager);
317
+ }
318
+ })
319
+
320
+ hawkejs.scene.on('opening_url', (href, options) => {
321
+
322
+ // Ignore segments & other renders
323
+ if (options?.history === false) {
324
+ return;
325
+ }
326
+
311
327
  this.stopEditing();
328
+
329
+ if (this.toolbar_manager) {
330
+ this.toolbar_manager.queueClearDocumentWatcher(500);
331
+ }
312
332
  });
313
333
  });
314
334