alchemy-widget 0.1.6 → 0.2.1

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.
Files changed (37) hide show
  1. package/CHANGELOG.md +29 -0
  2. package/assets/stylesheets/{alchemy-widgets.scss → alchemy_widgets.scss} +169 -54
  3. package/bootstrap.js +101 -2
  4. package/controller/alchemy_widgets_controller.js +64 -0
  5. package/element/00-widget_base_element.js +105 -4
  6. package/element/05-widget_element.js +33 -21
  7. package/element/10-container_elements.js +27 -32
  8. package/element/11-alchemy_widgets_list_element.js +2 -2
  9. package/element/20-add_area_element.js +22 -22
  10. package/element/table_of_contents_element.js +144 -11
  11. package/element/widget_actionbar_element.js +92 -0
  12. package/element/widget_context_element.js +39 -23
  13. package/element/widget_toolbar_element.js +295 -50
  14. package/helper/widget_action.js +10 -10
  15. package/helper/widgets/00-widget.js +244 -27
  16. package/helper/widgets/01-container.js +29 -7
  17. package/helper/widgets/05-list.js +1 -1
  18. package/helper/widgets/alchemy_field_widget.js +112 -0
  19. package/helper/widgets/alchemy_form_widget.js +183 -0
  20. package/helper/widgets/alchemy_table_widget.js +71 -0
  21. package/helper/widgets/alchemy_tabs_widget.js +193 -0
  22. package/helper/widgets/hawkejs_template.js +60 -0
  23. package/helper/widgets/header.js +8 -10
  24. package/helper/widgets/html.js +52 -3
  25. package/helper/widgets/markdown.js +18 -13
  26. package/helper/widgets/partial.js +1 -3
  27. package/helper/widgets/sourcecode.js +5 -7
  28. package/helper/widgets/table_of_contents.js +2 -4
  29. package/helper/widgets/text.js +15 -15
  30. package/helper/widgets_helper.js +26 -0
  31. package/package.json +3 -2
  32. package/view/elements/table_of_contents.hwk +33 -9
  33. package/view/form/inputs/edit/widget.hwk +2 -2
  34. package/view/form/inputs/edit/widgets.hwk +2 -2
  35. package/view/widget/elements/al_widget_toolbar.hwk +49 -0
  36. package/view/widget/widget_config.hwk +7 -4
  37. package/assets/stylesheets/alchemy-widget-symbols.scss +0 -191
package/CHANGELOG.md CHANGED
@@ -1,3 +1,32 @@
1
+ ## 0.2.1 (2022-12-23)
2
+
3
+ * Add a `-first` modifier class to the first visible entry of a table-of-contents element
4
+ * Make HTML widget editable
5
+ * Also store and retrieve copied widget config from localStorage
6
+ * Move `Container` widget init logic from the custom-element to the main widget instance
7
+ * Disable most `backdrop-filter` properties, they caused too many FPS issues
8
+ * Add the ability to hide widgets from the public
9
+ * Allow overriding the language (`lang` attribute) of a widget
10
+ * Fix config of container widgets not being saved
11
+ * Only add minium dimensions when editing
12
+ * Add `child-role` attribute & use it to set the default role of child widgets
13
+ * Add the `toc-has-content` or `toc-is-empty` class to `al-toc` elements
14
+ * Fix issue where `Text` widget would break when edited with LanguageTool extension
15
+ * Do not truncate titles in table-of-contents automatically
16
+ * Fix nesting levels in `al-toc`
17
+ * Only allow text inside header widgets
18
+ * Move default `Widget#populateWidget()` code to `Widget#finalizePopulatedWidget()`
19
+ * Add `HawkejsTemplate` widget
20
+
21
+ ## 0.2.0 (2022-11-02)
22
+
23
+ * Use `al-` prefix for all custom elements
24
+ * Update to `alchemy-form` v0.2.0
25
+ * Add copy & paste actions
26
+ * Add nesting levels to `al-toc`
27
+ * Add `al-widget-toolbar` element
28
+ * Use `easymde` markdown editor for markdown widgets
29
+
1
30
  ## 0.1.6 (2022-10-12)
2
31
 
3
32
  * Allow hiding widgets from the add-menu
@@ -1,20 +1,120 @@
1
- @import "alchemy-widget-symbols.scss";
1
+ :root {
2
+ al-widget-toolbar {
3
+ display: none;
4
+ }
5
+
6
+ [hidden] {
7
+ display: none !important;
8
+ }
9
+ }
10
+
11
+ html.logged-in {
12
+ al-widget-toolbar {
13
+ font-size: 15px;
14
+
15
+ display: flex;
16
+ z-index: 9999;
17
+ background: rgba(200, 200, 200, 0.5);
18
+ box-shadow: 0 5px 10px 0 rgba(0, 0, 0, 0.15);
19
+ bottom: 3em; left: 3em;
20
+ border-radius: 24px;
21
+ height: 5.25em;
22
+ padding: 1em;
23
+ border: 1px solid #aaa;
24
+ display: flex;
25
+ gap: 1em;
26
+ backdrop-filter: blur(10px);
27
+
28
+ position: fixed;
29
+ bottom: 16px;
30
+ left: 16px;
31
+ right: 16px;
32
+ }
33
+ }
34
+
35
+ al-widget-toolbar {
36
+
37
+ &[state="editing"] {
38
+ .start-edit {
39
+ display: none;
40
+ }
41
+ }
42
+
43
+ &[state="default"],
44
+ &[state="ready"] {
45
+ .stop-and-save,
46
+ .stop-edit,
47
+ .save-all {
48
+ display: none;
49
+ }
50
+ }
51
+
52
+ &[state="saving"],
53
+ &[state="saving-before-stop"] {
54
+ .start-edit,
55
+ .stop-edit {
56
+ display: none;
57
+ }
58
+ }
59
+
60
+ &[state="saving"] {
61
+ .stop-and-save {
62
+ display: none;
63
+ }
64
+ }
65
+
66
+ &[state="saving-before-stop"] {
67
+ .save-all {
68
+ display: none;
69
+ }
70
+ }
71
+
72
+ .stop-and-save,
73
+ .save-all {
74
+ --al-button-bg-color: green;
75
+ --al-button-bg-color-hover: rgb(55, 155, 55);
76
+ }
77
+
78
+ a,
79
+ al-button {
80
+ --al-button-font-size: 1.3em;
81
+ border-radius: 1em;
82
+ }
83
+
84
+ a {
85
+ background-color: black;
86
+ color: white;
87
+ padding: 0.5em 1em;
88
+ display: inline-flex;
89
+ align-items: center;
90
+ gap: 0.5em;
91
+
92
+ al-icon {
93
+ font-size: 2em;
94
+ }
95
+
96
+ &:hover {
97
+ background-color: rgb(53, 53, 53);
98
+ }
99
+ }
100
+ }
2
101
 
3
- alchemy-widgets,
4
- alchemy-widgets-row,
5
- alchemy-widgets-column,
6
- alchemy-widget {
102
+ al-widgets,
103
+ al-widgets-row,
104
+ al-widgets-column,
105
+ al-widget {
7
106
 
8
107
  }
9
108
 
10
- alchemy-widgets,
11
- alchemy-widgets-row,
12
- alchemy-widgets-column {
109
+ al-widgets,
110
+ al-widgets-row,
111
+ al-widgets-column {
13
112
  display: flex;
14
113
 
15
114
  &.aw-editing {
16
115
  position: relative;
17
116
  min-height: 3rem;
117
+ min-width: 10rem;
18
118
  }
19
119
 
20
120
  > * {
@@ -22,15 +122,24 @@ alchemy-widgets-column {
22
122
  }
23
123
  }
24
124
 
25
- alchemy-widget {
125
+ al-widgets,
126
+ al-widget {
26
127
  &.aw-editing {
27
- outline: 2px dashed rgba(0, 0, 0, 0.3);
28
- outline-offset: -2px;
128
+ &:before {
129
+ content: "";
130
+ position: absolute;
131
+ inset: -2px;
132
+ background: white;
133
+ border: 2px dashed rgba(0, 0, 0, 0.4);
134
+ pointer-events: none;
135
+ //backdrop-filter: invert(80%);
136
+ clip-path: polygon(0% 0%, 0% 100%, 2px 100%, 2px 2px, calc(100% - 2px) 2px, calc(100% - 2px) calc(100% - 2px), 0 calc(100% - 2px), 0 100%, 100% 100%, 100% 0)
137
+ }
29
138
  }
30
139
  }
31
140
 
32
- alchemy-widgets-row,
33
- alchemy-widgets-column {
141
+ al-widgets-row,
142
+ al-widgets-column {
34
143
 
35
144
  &.aw-editing {
36
145
  &:hover {
@@ -39,15 +148,15 @@ alchemy-widgets-column {
39
148
  }
40
149
  }
41
150
 
42
- alchemy-widgets,
43
- alchemy-widgets-column {
151
+ al-widgets,
152
+ al-widgets-column {
44
153
  flex-flow: column;
45
154
 
46
155
  &.aw-editing {
47
156
  padding-bottom: 5rem;
48
157
  }
49
158
 
50
- > alchemy-widget-add-area {
159
+ > al-widget-add-area {
51
160
  position: absolute;
52
161
  bottom: 0.2rem;
53
162
  left: 50%;
@@ -55,17 +164,7 @@ alchemy-widgets-column {
55
164
  }
56
165
  }
57
166
 
58
- alchemy-widgets {
59
- min-width: 10rem;
60
- min-height: 10rem;
61
-
62
- &.aw-editing {
63
- outline: 2px dashed rgba(0, 0, 0, 0.3);
64
- outline-offset: -2px;
65
- }
66
- }
67
-
68
- alchemy-widgets-row {
167
+ al-widgets-row {
69
168
  flex-flow: row;
70
169
  flex: 10 10 auto;
71
170
 
@@ -73,7 +172,7 @@ alchemy-widgets-row {
73
172
  padding-right: 5rem;
74
173
  }
75
174
 
76
- > alchemy-widget-add-area {
175
+ > al-widget-add-area {
77
176
  position: absolute;
78
177
  right: 0.5rem;
79
178
  top: 50%;
@@ -81,7 +180,7 @@ alchemy-widgets-row {
81
180
  }
82
181
  }
83
182
 
84
- alchemy-widget-add-area {
183
+ al-widget-add-area {
85
184
  background: rgba(255,255,255,0.5);
86
185
  padding: 0.5rem 2rem;
87
186
  border-radius: 2rem;
@@ -93,10 +192,10 @@ alchemy-widget-add-area {
93
192
  }
94
193
  }
95
194
 
96
- alchemy-widgets-row,
97
- alchemy-widgets > alchemy-widgets-column,
98
- alchemy-widgets-column > alchemy-widgets-column,
99
- .alchemy-widgets-container > alchemy-widgets-column {
195
+ al-widgets-row,
196
+ al-widgets > al-widgets-column,
197
+ al-widgets-column > al-widgets-column,
198
+ .alchemy-widgets-container > al-widgets-column {
100
199
  &.aw-editing {
101
200
 
102
201
  &::after {
@@ -116,13 +215,13 @@ alchemy-widgets-column > alchemy-widgets-column,
116
215
  }
117
216
  }
118
217
 
119
- alchemy-widgets-column {
218
+ al-widgets-column {
120
219
  flex: 10 10 auto;
121
220
  }
122
221
 
123
- alchemy-widgets-column,
124
- alchemy-widgets-row > alchemy-widgets-row,
125
- .alchemy-widgets-container > alchemy-widgets-row {
222
+ al-widgets-column,
223
+ al-widgets-row > al-widgets-row,
224
+ .alchemy-widgets-container > al-widgets-row {
126
225
  &.aw-editing {
127
226
  &::after {
128
227
  content: "";
@@ -141,7 +240,7 @@ alchemy-widgets-row > alchemy-widgets-row,
141
240
  }
142
241
  }
143
242
 
144
- alchemy-widget-add-area {
243
+ al-widget-add-area {
145
244
  display: flex;
146
245
  justify-content: center;
147
246
  align-items: center;
@@ -165,7 +264,7 @@ alchemy-widget-add-area {
165
264
  }
166
265
  }
167
266
 
168
- .aw-toolbar-button,
267
+ .aw-actionbar-button,
169
268
  .widget-button {
170
269
  color: #707684;
171
270
  cursor: pointer;
@@ -183,28 +282,34 @@ alchemy-widget-add-area {
183
282
  }
184
283
  }
185
284
 
186
- alchemy-widget {
285
+ al-widget {
187
286
  display: block;
188
287
 
189
288
  &.aw-editing {
190
289
  min-height: 2rem;
290
+ min-width: 2rem;
191
291
 
192
292
  &:hover {
193
293
  background: rgba(60, 60, 120, 0.2);
194
294
 
195
- // This actually causes some glitches on Firefox :/
196
- backdrop-filter: blur(4px);
295
+ // This actually causes some glitches on Firefox and chrome too :/
296
+ //backdrop-filter: blur(4px);
197
297
  }
198
298
  }
199
299
 
200
300
  &.aw-selected {
201
301
  background-color: #7979f347;
202
- outline: 1px dashed #7979f399;
302
+ position: relative;
303
+ outline: none;
304
+
305
+ &:before {
306
+ background-color: yellow;
307
+ }
203
308
  }
204
309
  }
205
310
 
206
- alchemy-widget-context,
207
- alchemy-widget-toolbar {
311
+ al-widget-context,
312
+ al-widget-actionbar {
208
313
  display: block;
209
314
  padding: 0.4rem;
210
315
  background-color: white;
@@ -217,11 +322,11 @@ alchemy-widget-toolbar {
217
322
  }
218
323
  }
219
324
 
220
- alchemy-widget-context {
325
+ al-widget-context {
221
326
  position: fixed;
222
327
  z-index: 99999;
223
328
 
224
- alchemy-widget-toolbar {
329
+ al-widget-actionbar {
225
330
  position: absolute;
226
331
  top: calc(100% + 5px);
227
332
  right: 0;
@@ -229,7 +334,7 @@ alchemy-widget-context {
229
334
  }
230
335
  }
231
336
 
232
- alchemy-widget-toolbar {
337
+ al-widget-actionbar {
233
338
  min-height: 2.5rem;
234
339
  min-width: 2.5rem;
235
340
  display: flex;
@@ -238,7 +343,7 @@ alchemy-widget-toolbar {
238
343
  margin-right: 5px;
239
344
  }
240
345
 
241
- .aw-toolbar-button {
346
+ .aw-actionbar-button {
242
347
  font-size: 2.5rem;
243
348
  border-radius: 4px;
244
349
  padding: 0.6rem;
@@ -254,7 +359,7 @@ alchemy-widget-toolbar {
254
359
  }
255
360
  }
256
361
 
257
- alchemy-widget[type="header"] {
362
+ al-widget[type="header"] {
258
363
 
259
364
  h1, h2, h3, h4, h5, h6 {
260
365
  padding: 1em 0;
@@ -272,7 +377,7 @@ alchemy-widget[type="header"] {
272
377
  }
273
378
  }
274
379
 
275
- alchemy-widget[type="text"] {
380
+ al-widget[type="text"] {
276
381
 
277
382
  &.aw-editing {
278
383
  &,
@@ -288,7 +393,7 @@ alchemy-widget[type="text"] {
288
393
  }
289
394
  }
290
395
 
291
- .aw-toolbar-button {
396
+ .aw-actionbar-button {
292
397
 
293
398
  .aw-header-h {
294
399
  font-weight: bold;
@@ -301,16 +406,26 @@ alchemy-widget[type="text"] {
301
406
  }
302
407
  }
303
408
 
304
- table-of-contents {
409
+ al-toc {
305
410
  display: block;
306
411
  }
307
412
 
308
413
  [data-he-template="widget/widget_config"] {
309
- alchemy-label {
414
+
415
+ .widget-config-title {
416
+ margin-bottom: 1rem;
417
+ color: black;
418
+ }
419
+
420
+ al-label {
310
421
  padding: 0.5rem;
311
422
 
312
423
  [data-he-name="field-title"] {
313
424
  display: block;
314
425
  }
315
426
  }
427
+ }
428
+
429
+ .aw-hidden {
430
+ opacity: 0.8;
316
431
  }
package/bootstrap.js CHANGED
@@ -1,5 +1,95 @@
1
- if (alchemy.plugins.form) {
2
- throw new Error('The alchemy-form plugin has to be loaded AFTER alchemy-widget');
1
+ alchemy.requirePlugin('form', false);
2
+
3
+ if (!alchemy.plugins.form) {
4
+ throw new Error('The alchemy-form plugin has to be loaded BEFORE alchemy-widget');
5
+ }
6
+
7
+ /* Ckeditor 5 available toolbar buttons (from styleboost build)
8
+ [
9
+ "blockQuote",
10
+ "bold",
11
+ "code",
12
+ "codeBlock",
13
+ "selectAll",
14
+ "undo",
15
+ "redo",
16
+ "heading",
17
+ "horizontalLine",
18
+ "imageTextAlternative",
19
+ "toggleImageCaption",
20
+ "imageStyle:inline",
21
+ "imageStyle:alignLeft",
22
+ "imageStyle:alignRight",
23
+ "imageStyle:alignCenter",
24
+ "imageStyle:alignBlockLeft",
25
+ "imageStyle:alignBlockRight",
26
+ "imageStyle:block",
27
+ "imageStyle:side",
28
+ "imageStyle:wrapText",
29
+ "imageStyle:breakText",
30
+ "uploadImage",
31
+ "imageUpload",
32
+ "indent",
33
+ "outdent",
34
+ "italic",
35
+ "link",
36
+ "linkImage",
37
+ "numberedList",
38
+ "bulletedList",
39
+ "mediaEmbed",
40
+ "removeFormat",
41
+ "sourceEditing",
42
+ "strikethrough",
43
+ "insertTable",
44
+ "tableColumn",
45
+ "tableRow",
46
+ "mergeTableCells",
47
+ "toggleTableCaption",
48
+ "tableCellProperties",
49
+ "tableProperties",
50
+ "todoList"
51
+ ]
52
+ */
53
+
54
+ let options = {
55
+ ckeditor_path: null,
56
+ ckeditor_toolbar: [
57
+ 'heading',
58
+ '|',
59
+ 'bold', 'italic', 'link', 'bulletedList', 'numberedList',
60
+ '|',
61
+ 'indent',
62
+ 'outdent',
63
+ 'horizontalLine',
64
+ '|',
65
+ 'blockQuote',
66
+ 'code',
67
+ 'codeBlock',
68
+ '|',
69
+ 'imageUpload',
70
+ 'insertTable',
71
+ '|',
72
+ 'undo', 'redo',
73
+ ],
74
+ };
75
+
76
+ // Inject the user-overridden options
77
+ alchemy.plugins.widget = Object.assign(options, alchemy.plugins.widget);
78
+
79
+ if (!options.ckeditor_path) {
80
+ if (alchemy.plugins.styleboost) {
81
+ options.ckeditor_path = '/public/ckeditor/5/ckeditor.js';
82
+ } else {
83
+ options.ckeditor_path = 'https://cdn.ckeditor.com/ckeditor5/35.3.2/inline/ckeditor.js';
84
+ }
85
+ }
86
+
87
+ if (options.ckeditor_path) {
88
+ alchemy.exposeStatic('ckeditor_path', options.ckeditor_path);
89
+ }
90
+
91
+ if (options.ckeditor_toolbar) {
92
+ alchemy.exposeStatic('ckeditor_toolbar', options.ckeditor_toolbar);
3
93
  }
4
94
 
5
95
  Router.add({
@@ -7,4 +97,13 @@ Router.add({
7
97
  methods : 'post',
8
98
  paths : '/api/alchemywidgets/save',
9
99
  policy : 'logged_in',
100
+ permission : 'alchemy.widgets.save',
10
101
  });
102
+
103
+ Router.add({
104
+ name : 'AlchemyWidgets#uploadImage',
105
+ methods : 'post',
106
+ paths : '/api/alchemywidgets/upload',
107
+ policy : 'logged_in',
108
+ permission : 'alchemy.widgets.image.upload',
109
+ });
@@ -184,4 +184,68 @@ AlchemyWidgets.setAction(async function save(conduit) {
184
184
  };
185
185
 
186
186
  conduit.end(result);
187
+ });
188
+
189
+ /**
190
+ * Handle an image upload from within CKeditor
191
+ *
192
+ * @author Jelle De Loecker <jelle@elevenways.be>
193
+ * @since 0.2.1
194
+ * @version 0.2.1
195
+ */
196
+ AlchemyWidgets.setAction(async function uploadImage(conduit) {
197
+
198
+ let file = conduit.files.upload;
199
+
200
+ const MediaFile = this.getModel('MediaFile');
201
+
202
+ let name = file.name.split('.');
203
+
204
+ // Remove the last piece if there are more than 1
205
+ if (name.length > 1) {
206
+ name.pop();
207
+ }
208
+
209
+ // Join them again
210
+ name = name.join('.');
211
+
212
+ const options = {
213
+ move : true,
214
+ filename : file.name,
215
+ name : name,
216
+ };
217
+
218
+ MediaFile.addFile(file.path, options, (err, result) => {
219
+
220
+ if (err) {
221
+ return conduit.error(err);
222
+ }
223
+
224
+ const params = {
225
+ id : result._id,
226
+ };
227
+
228
+ let default_path = alchemy.routeUrl('Media::image', params);
229
+ let url = RURL.parse(default_path);
230
+
231
+ let path_800 = url.clone();
232
+ path_800.param('width', '800');
233
+
234
+ let path_1024 = url.clone();
235
+ path_1024.param('height', '1024');
236
+
237
+ let path_1920 = url.clone();
238
+ path_1920.param('width', '1920');
239
+
240
+ let response = {
241
+ urls: {
242
+ default : default_path,
243
+ '800' : path_800+'',
244
+ '1024' : path_1024+'',
245
+ '1920' : path_1920+''
246
+ }
247
+ };
248
+
249
+ conduit.end(response);
250
+ });
187
251
  });