alchemy-media 0.7.0 → 0.7.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.
package/CHANGELOG.md CHANGED
@@ -1,3 +1,14 @@
1
+ ## 0.7.1 (2022-12-23)
2
+
3
+ * Cascade `al-svg` role attribute to child `svg` elements
4
+ * Add `img` role fallback when using `graphics-symbol`
5
+ * Also add default role to `al-icon` element
6
+ * Make `al-file` catch errors and report them to a possible `al-field` parent
7
+ * Add the `accept` attribute to `al-file`
8
+ * Add the `accept` option to `File` fields
9
+ * Add `width_hint` field to `image` widget. This field allows users to specify the approximate maximum width of an image in pixels or as a percentage of the total page width.
10
+ * Add `lazy_load` field to `image` widget
11
+
1
12
  ## 0.7.0 (2022-11-02)
2
13
 
3
14
  * Use the `al-` prefix for custom elements
package/README.md CHANGED
@@ -75,7 +75,7 @@ you can do so like this on Debian/Ubuntu
75
75
 
76
76
  Install the requirements of the `veronica` module:
77
77
 
78
- apt-get install graphicsmagick webp libgif-dev
78
+ apt-get install graphicsmagick webp libgif-dev libcairo2-dev libpango1.0-dev libjpeg-dev librsvg2-dev
79
79
 
80
80
  This plugin requires exiv2
81
81
 
@@ -206,7 +206,7 @@ MediaFiles.setAction(function downloadFile(conduit, id, extension) {
206
206
  *
207
207
  * @author Jelle De Loecker <jelle@develry.be>
208
208
  * @since 0.0.1
209
- * @version 0.4.2
209
+ * @version 0.7.1
210
210
  *
211
211
  * @param {Conduit} conduit
212
212
  */
@@ -215,16 +215,16 @@ MediaFiles.setAction(function upload(conduit) {
215
215
  var MediaFile = this.getModel('MediaFile'),
216
216
  files = conduit.files,
217
217
  tasks = [],
218
- file;
218
+ accept = conduit.body?.accept;
219
219
 
220
220
  if (files && files.files && typeof files.files == 'object') {
221
221
  files = files.files;
222
222
  }
223
223
 
224
- // Iterate over every file
225
- Object.each(files, function eachFile(file, key) {
224
+ for (let key in files) {
225
+ let file = files[key];
226
226
 
227
- tasks[tasks.length] = function storeFile(next) {
227
+ tasks.push(async next => {
228
228
  let options = {
229
229
  move: true,
230
230
  filename: file.name
@@ -242,9 +242,20 @@ MediaFiles.setAction(function upload(conduit) {
242
242
 
243
243
  options.name = name;
244
244
 
245
+ // @TODO: don't trust the browser to dictate the type
246
+ if (accept) {
247
+ let file_type = await file.getMimetype();
248
+
249
+ if (file_type === accept || accept.includes(file_type)) {
250
+ // OK!
251
+ } else {
252
+ return next(new Error('File upload failed: Expected filetype "' + accept + '", but got "' + file_type + '"'));
253
+ }
254
+ }
255
+
245
256
  MediaFile.addFile(file.path, options, next);
246
- };
247
- });
257
+ });
258
+ }
248
259
 
249
260
  // Store every file
250
261
  Function.parallel(tasks, function storedFiles(err, result) {
@@ -79,6 +79,15 @@ AlFile.addElementGetter('icon_uploading', '.uploading-icon');
79
79
  */
80
80
  AlFile.addElementGetter('icon_empty', '.empty-icon');
81
81
 
82
+ /**
83
+ * Set the accepted types
84
+ *
85
+ * @author Jelle De Loecker <jelle@elevenways.be>
86
+ * @since 0.7.1
87
+ * @version 0.7.1
88
+ */
89
+ AlFile.setAttribute('accept');
90
+
82
91
  /**
83
92
  * Set the value
84
93
  *
@@ -135,7 +144,7 @@ AlFile.setMethod(function updatePreview(value) {
135
144
  *
136
145
  * @author Jelle De Loecker <jelle@elevenways.be>
137
146
  * @since 0.6.0
138
- * @version 0.6.2
147
+ * @version 0.7.1
139
148
  */
140
149
  AlFile.setMethod(async function uploadFile(config) {
141
150
 
@@ -159,6 +168,10 @@ AlFile.setMethod(async function uploadFile(config) {
159
168
  form_data.append('format', format);
160
169
  }
161
170
 
171
+ if (this.accept) {
172
+ form_data.append('accept', this.accept);
173
+ }
174
+
162
175
  let response;
163
176
 
164
177
  try {
@@ -167,7 +180,15 @@ AlFile.setMethod(async function uploadFile(config) {
167
180
  post : form_data,
168
181
  });
169
182
  } catch (err) {
170
- console.error('Failed to upload file:', err);
183
+
184
+ let alchemy_field = this.queryUp('al-field');
185
+
186
+ if (alchemy_field) {
187
+ alchemy_field.showError(err);
188
+ } else {
189
+ console.error('Failed to upload file:', err);
190
+ alert('Failed to upload file: ' + err);
191
+ }
171
192
  }
172
193
 
173
194
  this.classList.remove('uploading');
@@ -282,12 +303,20 @@ AlFile.setMethod(function _getContent(callback) {
282
303
  *
283
304
  * @author Jelle De Loecker <jelle@elevenways.be>
284
305
  * @since 0.7.0
285
- * @version 0.7.0
306
+ * @version 0.7.1
286
307
  */
287
308
  AlFile.setMethod(async function showExistingFileSelection() {
288
309
 
289
310
  let variables = {};
290
311
 
312
+ if (this.accept) {
313
+ let filters = {
314
+ type : this.accept,
315
+ };
316
+
317
+ variables.filters = filters;
318
+ }
319
+
291
320
  await hawkejs.scene.render('element/al_file_selection', variables);
292
321
 
293
322
  let dialog_contents = document.querySelector('he-dialog [data-he-template="element/al_file_selection"]');
@@ -47,6 +47,15 @@ Icon.setAttribute('icon-name');
47
47
  */
48
48
  Icon.setAttribute('icon-flags');
49
49
 
50
+ /**
51
+ * Set the default role
52
+ *
53
+ * @author Jelle De Loecker <jelle@elevenways.be>
54
+ * @since 0.7.1
55
+ * @version 0.7.1
56
+ */
57
+ Icon.setRole('graphics-symbol');
58
+
50
59
  /**
51
60
  * Refresh the icon when these attributes change
52
61
  *
@@ -85,7 +94,7 @@ Icon.setMethod(function introduced() {
85
94
  *
86
95
  * @author Jelle De Loecker <jelle@elevenways.be>
87
96
  * @since 0.7.0
88
- * @version 0.7.0
97
+ * @version 0.7.1
89
98
  *
90
99
  * @param {Boolean} force
91
100
  */
@@ -95,6 +104,14 @@ Icon.setMethod(function refresh(force) {
95
104
  return;
96
105
  }
97
106
 
107
+ if (this.role == 'graphics-symbol') {
108
+ this.role = 'graphics-symbol img';
109
+
110
+ if (!this.hasAttribute('aria-label')) {
111
+ this.setAttribute('aria-label', '');
112
+ }
113
+ }
114
+
98
115
  if (!force && Blast.isNode) {
99
116
  return;
100
117
  }
package/element/al_svg.js CHANGED
@@ -21,7 +21,7 @@ Svg.setAttribute('src');
21
21
  *
22
22
  * @author Jelle De Loecker <jelle@elevenways.be>
23
23
  * @since 0.5.1
24
- * @version 0.5.1
24
+ * @version 0.7.1
25
25
  */
26
26
  Svg.setMethod(async function injectSvg() {
27
27
 
@@ -50,6 +50,29 @@ Svg.setMethod(async function injectSvg() {
50
50
 
51
51
  this.innerHTML = contents;
52
52
 
53
+ if (this.role) {
54
+
55
+ // Do not allow `graphics-symbol` role, because it is not widely supported
56
+ // and google doesn't know it either
57
+ if (this.role == 'graphics-symbol') {
58
+ this.role = 'img';
59
+
60
+ if (!this.hasAttribute('aria-label')) {
61
+ this.setAttribute('aria-label', '');
62
+ }
63
+ }
64
+
65
+ let svg = this.querySelector('svg');
66
+
67
+ if (svg) {
68
+ svg.setAttribute('role', this.role);
69
+
70
+ if (this.hasAttribute('aria-label')) {
71
+ svg.setAttribute('aria-label', this.getAttribute('aria-label'));
72
+ }
73
+ }
74
+ }
75
+
53
76
  if (this._resolve_me_too) {
54
77
  this._resolve_me_too.resolve();
55
78
  }
@@ -115,7 +115,7 @@ Media.setMethod(function loadIconFont() {
115
115
  *
116
116
  * @author Jelle De Loecker <jelle@develry.be>
117
117
  * @since 0.5.0
118
- * @version 0.6.2
118
+ * @version 0.7.1
119
119
  *
120
120
  * @param {Element} element The element to apply to
121
121
  * @param {String} image The image identifier
@@ -160,6 +160,12 @@ Media.setMethod(function applyDirective(element, image, options) {
160
160
  options.height = height;
161
161
  }
162
162
 
163
+ if (options.lazy_load) {
164
+ if (!element.hasAttribute('loading')) {
165
+ element.setAttribute('loading', 'lazy');
166
+ }
167
+ }
168
+
163
169
  let url = this.imageUrl(image, options),
164
170
  clone = url.clone();
165
171
 
@@ -16,12 +16,25 @@ const Image = Function.inherits('Alchemy.Widget', 'Image');
16
16
  *
17
17
  * @author Jelle De Loecker <jelle@elevenways.be>
18
18
  * @since 0.6.0
19
- * @version 0.6.0
19
+ * @version 0.7.1
20
20
  */
21
21
  Image.constitute(function prepareSchema() {
22
22
 
23
23
  this.schema.addField('image', 'File', {
24
+ description : 'Upload/select the image to display',
25
+ widget_config_editable: true,
26
+ });
27
+
28
+ this.schema.addField('width_hint', 'Number', {
29
+ description : 'Approximate maximum width of the image in pixels or as a percentage of the total page width.',
30
+ widget_config_editable: true,
31
+ default: '25%',
32
+ });
33
+
34
+ this.schema.addField('lazy_load', 'Boolean', {
35
+ description : 'Determines whether the image should be loaded lazily or not',
24
36
  widget_config_editable: true,
37
+ default: true,
25
38
  });
26
39
  });
27
40
 
@@ -30,7 +43,7 @@ Image.constitute(function prepareSchema() {
30
43
  *
31
44
  * @author Jelle De Loecker <jelle@elevenways.be>
32
45
  * @since 0.6.0
33
- * @version 0.6.4
46
+ * @version 0.7.1
34
47
  *
35
48
  * @param {HTMLElement} widget
36
49
  */
@@ -38,7 +51,18 @@ Image.setMethod(function populateWidget() {
38
51
 
39
52
  let img = this.createElement('img');
40
53
 
41
- this.hawkejs_renderer.helpers.Media.applyDirective(img, this.config.image);
54
+ let width_hint = this.config.width_hint,
55
+ lazy_load = this.config.lazy_load;
56
+
57
+ if (width_hint === null || width_hint === '' || typeof width_hint == 'undefined') {
58
+ width_hint = '25%';
59
+ }
60
+
61
+ if (lazy_load == null) {
62
+ lazy_load = true;
63
+ }
64
+
65
+ this.hawkejs_renderer.helpers.Media.applyDirective(img, this.config.image, {width: width_hint, lazy_load});
42
66
 
43
67
  this.widget.append(img);
44
68
 
@@ -16,6 +16,48 @@ var FileField = Function.inherits('Alchemy.Field.ObjectId', 'File');
16
16
  */
17
17
  FileField.setProperty('deferCast', true);
18
18
 
19
+ /**
20
+ * Get the accepted types
21
+ *
22
+ * @author Jelle De Loecker <jelle@elevenways.be>
23
+ * @since 0.7.1
24
+ * @version 0.7.1
25
+ *
26
+ * @return {Array|null}
27
+ */
28
+ FileField.setMethod(function getAcceptedTypes() {
29
+
30
+ let accept = this.options?.accept;
31
+
32
+ if (!accept) {
33
+ return null;
34
+ }
35
+
36
+ accept = Array.cast(accept);
37
+
38
+ return accept;
39
+ });
40
+
41
+ /**
42
+ * Get the accepted types string, for the input element
43
+ *
44
+ * @author Jelle De Loecker <jelle@elevenways.be>
45
+ * @since 0.7.1
46
+ * @version 0.7.1
47
+ *
48
+ * @return {String|null}
49
+ */
50
+ FileField.setMethod(function getAcceptedTypesString() {
51
+
52
+ let types = this.getAcceptedTypes();
53
+
54
+ if (!types?.length) {
55
+ return null;
56
+ }
57
+
58
+ return types.join(', ');
59
+ });
60
+
19
61
  if (Blast.isBrowser) {
20
62
  return;
21
63
  }
@@ -225,20 +225,33 @@ MediaFile.setMethod(function getFile(id, callback) {
225
225
  *
226
226
  * @author Jelle De Loecker <jelle@develry.be>
227
227
  * @since 0.0.1
228
- * @version 0.2.0
228
+ * @version 0.7.1
229
229
  *
230
230
  * @param {String} file The path to the file, can be a URL
231
231
  * @param {Object} options
232
232
  * @param {Function} callback
233
+ *
234
+ * @return {Pledge}
233
235
  */
234
236
  MediaFile.setMethod(function addFile(file, options, callback) {
235
237
 
236
- var that = this;
238
+ const that = this,
239
+ pledge = new Pledge();
240
+
241
+ pledge.done(callback);
237
242
 
238
243
  this.queue.add(function(done) {
239
244
  that.getModel('MediaRaw').addFile(file, options, function(err, response) {
240
- callback(err, response);
245
+
241
246
  done();
247
+
248
+ if (err) {
249
+ pledge.reject(err);
250
+ } else {
251
+ pledge.resolve(response);
252
+ }
242
253
  });
243
254
  });
255
+
256
+ return pledge;
244
257
  });
@@ -168,7 +168,7 @@ MediaRaw.Document.setMethod(function extraImportFromStream(input) {
168
168
  *
169
169
  * @author Jelle De Loecker <jelle@develry.be>
170
170
  * @since 0.0.1
171
- * @version 0.4.2
171
+ * @version 0.7.1
172
172
  *
173
173
  * @param {String} file The path to the file, can be a URL
174
174
  * @param {Object} options
@@ -185,6 +185,14 @@ MediaRaw.setMethod(function addFile(file, options, callback) {
185
185
  options = {};
186
186
  }
187
187
 
188
+ if (!options) {
189
+ options = {};
190
+ }
191
+
192
+ if (file && typeof file == 'object' && file instanceof Classes.Alchemy.Inode.File) {
193
+ file = file.path;
194
+ }
195
+
188
196
  // If the given file is actually a url, we'll need to download it first
189
197
  if (file.startsWith('http://') || file.startsWith('https://')) {
190
198
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "alchemy-media",
3
3
  "description": "The media plugin for Alchemy",
4
- "version": "0.7.0",
4
+ "version": "0.7.1",
5
5
  "repository": {
6
6
  "type" : "git",
7
7
  "url" : "https://github.com/11ways/alchemy-media.git"
@@ -13,7 +13,13 @@
13
13
  class="empty-icon"
14
14
  ><% if (self.value) $0.hidden = true %></al-icon>
15
15
 
16
- <input type="file" tabindex="-1" class="al-file-input" hidden>
16
+ <input
17
+ class="al-file-input"
18
+ type="file"
19
+ tabindex="-1"
20
+ accept={% self.accept %}
21
+ hidden
22
+ >
17
23
  </label>
18
24
 
19
25
  <div class="al-file-right">
@@ -25,6 +25,7 @@ fieldset.addField('type');
25
25
  mode="inline"
26
26
  page-size=20
27
27
  show-filters
28
+ #filters={% filters %}
28
29
  #fieldset={% fieldset %}
29
30
  #recordsource={% {route: 'MediaFile#recordsource'} %}
30
31
  ></al-table>
@@ -3,6 +3,7 @@ script('chimera/mediafield');
3
3
  style('chimera/mediafield');
4
4
  %>
5
5
  <al-file
6
- value=<% value %>
7
6
  class="alchemy-field-value"
7
+ value=<% value %>
8
+ accept={% alchemy_field.config.getAcceptedTypesString() %}
8
9
  ></al-file>