@es-joy/jsoe 0.7.0 → 0.8.0

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/CHANGES.md CHANGED
@@ -1,5 +1,15 @@
1
1
  # CHANGES TO `@es-joy/jsoe`
2
2
 
3
+ ## 0.8.0
4
+
5
+ - feat: make `File` fully editable (also by modified date or
6
+ string contents or if by scratch)
7
+
8
+ ## 0.7.1
9
+
10
+ - feat: fix bug with `file` in array/object context not triggering file
11
+ picker
12
+
3
13
  ## 0.7.0
4
14
 
5
15
  - feat: add `file` type including video, audio, photo, and
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@es-joy/jsoe",
3
- "version": "0.7.0",
3
+ "version": "0.8.0",
4
4
  "type": "module",
5
5
  "types": "./dist/index.d.ts",
6
6
  "main": "./src/index.js",
@@ -1691,7 +1691,7 @@ const arrayType = {
1691
1691
  // invalid date checkbox; is this sufficient to prevent
1692
1692
  // other stray clicks apparently meant for the array
1693
1693
  // and object reference checking?
1694
- if (target.type !== 'checkbox' && target.type !== 'radio') {
1694
+ if (!['checkbox', 'radio', 'file'].includes(target.type)) {
1695
1695
  e.preventDefault();
1696
1696
  }
1697
1697
  }
@@ -128,11 +128,60 @@ function getDateString (timestamp) {
128
128
  return dateTimeLocalValue;
129
129
  }
130
130
 
131
+ /**
132
+ * @param {HTMLButtonElement & {$value: File}} viewBinary
133
+ * @param {{
134
+ * stringContents?: string,
135
+ * name?: string,
136
+ * type?: string,
137
+ * lastModified?: number
138
+ * }} value
139
+ * @returns {void}
140
+ */
141
+ function newFileForBinary (viewBinary, value) {
142
+ const oldFile = /** @type {File|undefined} */ (
143
+ viewBinary.$value
144
+ );
145
+ // We actually want to allow creating `File`'s from scratch
146
+ // if (
147
+ // !oldFile ||
148
+ // Object.prototype.toString.call(
149
+ // oldFile
150
+ // ).slice(8, -1) !== 'File'
151
+ // ) {
152
+ // return false;
153
+ // }
154
+ const file = new File(
155
+ [
156
+ value.stringContents === undefined
157
+ ? oldFile && Object.prototype.toString.call(
158
+ oldFile
159
+ ).slice(8, -1) === 'File'
160
+ ? oldFile
161
+ : ''
162
+ : value.stringContents
163
+ ],
164
+ value.name === undefined
165
+ ? oldFile?.name ?? ''
166
+ : value.name,
167
+ {
168
+ type: value.type === undefined
169
+ ? oldFile?.type ?? ''
170
+ : value.type,
171
+ lastModified: value.lastModified === undefined
172
+ ? oldFile?.lastModified ?? 0
173
+ : value.lastModified
174
+ }
175
+ );
176
+ viewBinary.$value = file;
177
+ }
178
+
131
179
  /**
132
180
  * @param {File} value
181
+ * @param {boolean} [editable]
133
182
  * @returns {import('jamilih').JamilihArray}
134
183
  */
135
- function binaryButton (value) {
184
+ function binaryButton (value, editable) {
136
185
  // @ts-expect-error It's ok
137
186
  return ['button', /** @type {import('jamilih').JamilihAttributes} */ ({
138
187
  class: 'viewBinary',
@@ -145,27 +194,49 @@ function binaryButton (value) {
145
194
  * @param {Event} e
146
195
  */
147
196
  click (e) {
197
+ // eslint-disable-next-line consistent-this -- Clarity
198
+ const viewBinary = this;
148
199
  e.preventDefault();
149
200
  if (
150
201
  !this.$value ||
151
202
  Object.prototype.toString.call(this.$value).slice(8, -1) !== 'File'
152
203
  ) {
153
- dialogs.alert(
154
- 'There is no file chosen with binary data'
155
- );
156
- return;
204
+ // Non-editable shouldn't be empty
205
+ // if (!editable) {
206
+ // dialogs.alert(
207
+ // 'There is no file chosen with binary data'
208
+ // );
209
+ // return;
210
+ // }
211
+
212
+ newFileForBinary(viewBinary, {});
157
213
  }
158
214
  const reader = new FileReader();
159
215
  reader.addEventListener('load', async function () {
160
- await dialogs.alert({
161
- message: ['div', [
162
- 'Binary source',
163
- ['br'],
164
- ['textarea', {class: 'view-binary'}, [
165
- /* c8 ignore next */
166
- /** @type {string|null} */ (reader.result) ?? ''
167
- ]]
168
- ]]
216
+ const dialog = await dialogs.makeSubmitDialog({
217
+ submitText: 'Save',
218
+ submit () {
219
+ const textarea = /** @type {HTMLTextAreaElement} */ (
220
+ $e(dialog, '.view-binary')
221
+ );
222
+ newFileForBinary(viewBinary, {
223
+ stringContents: textarea.value
224
+ });
225
+ dialog.close();
226
+ },
227
+ // @ts-expect-error TS bug
228
+ children: [
229
+ ['div', /** @type {import('jamilih').JamilihChildren} */ ([
230
+ 'Binary source',
231
+ ['br'],
232
+ ['textarea', {
233
+ class: 'view-binary'
234
+ }, [
235
+ /* c8 ignore next */
236
+ /** @type {string|null} */ (reader.result) ?? ''
237
+ ]]
238
+ ])]
239
+ ]
169
240
  });
170
241
  });
171
242
  // Seems not feasible to accurately simulate
@@ -183,7 +254,11 @@ function binaryButton (value) {
183
254
  reader.readAsBinaryString(this.$value);
184
255
  }
185
256
  }
186
- }), ['View binary data']];
257
+ }), [
258
+ editable
259
+ ? 'Edit binary data'
260
+ : 'View binary data'
261
+ ]];
187
262
  }
188
263
 
189
264
  /**
@@ -443,7 +518,7 @@ const fileType = {
443
518
 
444
519
  /** @type {HTMLButtonElement & {$value: File|undefined}} */ ($e(
445
520
  metadataFieldset,
446
- '.viewBinary'
521
+ 'button.viewBinary'
447
522
  )).$value = typeof file.lastModified === 'number'
448
523
  ? /** @type {File} */ (file)
449
524
  : undefined;
@@ -465,30 +540,12 @@ const fileType = {
465
540
  $e(
466
541
  /** @type {HTMLElement} */
467
542
  (this.parentElement?.parentElement),
468
- '.viewBinary'
543
+ 'button.viewBinary'
469
544
  )
470
545
  );
471
- const oldFile = /** @type {File|undefined} */ (
472
- viewBinary.$value
473
- );
474
- if (
475
- !oldFile ||
476
- Object.prototype.toString.call(
477
- oldFile
478
- ).slice(8, -1) !== 'File'
479
- ) {
480
- return;
481
- }
482
- const newName = /** @type {HTMLInputElement} */ (this).value;
483
- const file = new File(
484
- [oldFile],
485
- newName,
486
- {
487
- type: oldFile.type,
488
- lastModified: oldFile.lastModified
489
- }
490
- );
491
- viewBinary.$value = file;
546
+ newFileForBinary(viewBinary, {
547
+ name: /** @type {HTMLInputElement} */ (this).value
548
+ });
492
549
  }
493
550
  }
494
551
  }]
@@ -515,31 +572,15 @@ const fileType = {
515
572
  $e(
516
573
  /** @type {HTMLElement} */
517
574
  (this.parentElement?.parentElement),
518
- '.viewBinary'
575
+ 'button.viewBinary'
519
576
  )
520
577
  );
521
- const oldFile = /** @type {File|undefined} */ (
522
- viewBinary.$value
523
- );
524
- if (
525
- !oldFile ||
526
- Object.prototype.toString.call(
527
- oldFile
528
- ).slice(8, -1) !== 'File'
529
- ) {
530
- return;
531
- }
578
+
532
579
  const newContentType =
533
580
  /** @type {HTMLInputElement} */ (this).value;
534
- const file = new File(
535
- [oldFile],
536
- oldFile.name,
537
- {
538
- type: newContentType,
539
- lastModified: oldFile.lastModified
540
- }
541
- );
542
- viewBinary.$value = file;
581
+ newFileForBinary(viewBinary, {
582
+ type: newContentType
583
+ });
543
584
  }
544
585
  }
545
586
  }]
@@ -548,14 +589,35 @@ const fileType = {
548
589
  ['label', [
549
590
  'Last modified date ',
550
591
  ['input', {
551
- type: 'datetime-local', disabled: true, class: 'lastModified',
592
+ type: 'datetime-local', class: 'lastModified',
593
+ step: 0.001,
552
594
  value: value.lastModified
553
595
  ? getDateString(value.lastModified)
554
- : undefined
596
+ : undefined,
597
+ $on: {
598
+ change () {
599
+ const viewBinary =
600
+ /**
601
+ * @type {HTMLButtonElement & {$value: File}}
602
+ */ (
603
+ $e(
604
+ /** @type {HTMLElement} */
605
+ (this.parentElement?.parentElement),
606
+ 'button.viewBinary'
607
+ )
608
+ );
609
+
610
+ const newLastModified =
611
+ /** @type {HTMLInputElement} */ (this).value;
612
+ newFileForBinary(viewBinary, {
613
+ lastModified: new Date(newLastModified).getTime()
614
+ });
615
+ }
616
+ }
555
617
  }]
556
618
  ]],
557
619
  ['br'],
558
- binaryButton(value),
620
+ binaryButton(value, true),
559
621
  ['button', {
560
622
  class: 'clearData',
561
623
  $on: {
@@ -99,11 +99,12 @@ const dialogs = {
99
99
  /**
100
100
  * @param {object} cfg
101
101
  * @param {(info: {e: Event, dialog: HTMLDialogElement}) => void} cfg.submit
102
+ * @param {string} [cfg.submitText]
102
103
  * @param {string} [cfg.submitClass]
103
104
  * @param {MakeCancelArgs} [cfg.args]
104
105
  * @returns {HTMLDialogElement}
105
106
  */
106
- makeSubmitDialog ({submit, submitClass = 'submit', ...args}) {
107
+ makeSubmitDialog ({submit, submitText, submitClass = 'submit', ...args}) {
107
108
  const dialog = this.makeCancelDialog(/** @type {MakeCancelArgs} */ (args));
108
109
  $e(dialog, `button.${/** @type {MakeCancelArgs} */ (
109
110
  args
@@ -116,7 +117,7 @@ const dialogs = {
116
117
  submit.call(this, {e: /** @type {Event} */ (e), dialog});
117
118
  }
118
119
  }
119
- }, [this.localeStrings.submit]),
120
+ }, [submitText ?? this.localeStrings.submit]),
120
121
  nbsp.repeat(2)
121
122
  );
122
123
  return dialog;