@etsoo/shared 1.1.84 → 1.1.86

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/README.md CHANGED
@@ -32,6 +32,15 @@ $ yarn add @etsoo/shared
32
32
  ## storage
33
33
  Storage interface and browser storage implementation
34
34
 
35
+ ## ContentDisposition
36
+ Content disposition of HTTP
37
+
38
+ |Name|Description|
39
+ |---:|---|
40
+ |static parse|Parse header value|
41
+ |Methods||
42
+ |format|Format to standard output|
43
+
35
44
  ## EColor
36
45
  Etsoo implmented Color
37
46
 
@@ -182,6 +191,7 @@ DOM/window related utilities
182
191
  |detectedCountry|Current detected country|
183
192
  |detectedCulture|Current detected culture|
184
193
  |dimensionEqual|Check two rectangles equality|
194
+ |downloadFile|Download file from API fetch response body|
185
195
  |fileToDataURL|File to data URL|
186
196
  |formDataToObject|Form data to object|
187
197
  |getCulture|Get the available culture definition|
@@ -0,0 +1,24 @@
1
+ import { ContentDisposition } from '../src/types/ContentDisposition';
2
+
3
+ test('Tests for parse', () => {
4
+ const cd1 = ContentDisposition.parse(
5
+ "attachment; filename=__PDF.pdf; filename*=UTF-8''%E6%B5%8B%E8%AF%95PDF.pdf"
6
+ );
7
+
8
+ const cd2 = ContentDisposition.parse(
9
+ `attachment; filename="__PDF.pdf"; filename*="UTF-8''%E6%B5%8B%E8%AF%95PDF.pdf"`
10
+ );
11
+
12
+ expect(cd1?.type).toBe(cd2?.type);
13
+ expect(cd1?.filename).toBe(cd2?.filename);
14
+ expect(cd1?.filename).toBe('测试PDF.pdf');
15
+ });
16
+
17
+ test('Tests for format', () => {
18
+ const cd1 = new ContentDisposition('form-data', 'a-b.jpg', 'file');
19
+ expect(cd1.format()).toBe(`form-data; name="file"; filename="a-b.jpg"`);
20
+ const cd2 = new ContentDisposition('attachment', '测试PDF.pdf');
21
+ expect(cd2.format()).toBe(
22
+ `attachment; filename="__PDF.pdf"; filename*="UTF-8''%E6%B5%8B%E8%AF%95PDF.pdf"`
23
+ );
24
+ });
@@ -1,3 +1,4 @@
1
+ /// <reference lib="dom" />
1
2
  import { DataTypes } from './DataTypes';
2
3
  import { FormDataFieldValue, IFormData } from './types/FormData';
3
4
  /**
@@ -50,6 +51,13 @@ export declare namespace DomUtils {
50
51
  * @param d2 Dimension 2
51
52
  */
52
53
  function dimensionEqual(d1?: DOMRect, d2?: DOMRect): boolean;
54
+ /**
55
+ * Download file from API fetch response body
56
+ * @param data Data
57
+ * @param suggestedName Suggested file name
58
+ * @param autoDetect Auto detect, false will use link click way
59
+ */
60
+ function downloadFile(data: ReadableStream | Blob, suggestedName?: string, autoDetect?: boolean): Promise<void>;
53
61
  /**
54
62
  * File to data URL
55
63
  * @param file File
@@ -10,6 +10,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
10
10
  };
11
11
  Object.defineProperty(exports, "__esModule", { value: true });
12
12
  exports.DomUtils = void 0;
13
+ /// <reference lib="dom" />
13
14
  const DataTypes_1 = require("./DataTypes");
14
15
  if (typeof navigator === 'undefined') {
15
16
  // Test mock only
@@ -231,6 +232,48 @@ var DomUtils;
231
232
  return false;
232
233
  }
233
234
  DomUtils.dimensionEqual = dimensionEqual;
235
+ /**
236
+ * Download file from API fetch response body
237
+ * @param data Data
238
+ * @param suggestedName Suggested file name
239
+ * @param autoDetect Auto detect, false will use link click way
240
+ */
241
+ function downloadFile(data, suggestedName, autoDetect = true) {
242
+ return __awaiter(this, void 0, void 0, function* () {
243
+ try {
244
+ if (autoDetect && 'showSaveFilePicker' in globalThis) {
245
+ const handle = yield globalThis.showSaveFilePicker({
246
+ suggestedName
247
+ });
248
+ const stream = yield handle.createWritable();
249
+ if (data instanceof Blob) {
250
+ data.stream().pipeTo(stream);
251
+ }
252
+ else {
253
+ yield data.pipeTo(stream);
254
+ }
255
+ }
256
+ else {
257
+ const url = window.URL.createObjectURL(data instanceof Blob
258
+ ? data
259
+ : yield new Response(data).blob());
260
+ const a = document.createElement('a');
261
+ a.style.display = 'none';
262
+ a.href = url;
263
+ if (suggestedName)
264
+ a.download = suggestedName;
265
+ document.body.appendChild(a);
266
+ a.click();
267
+ a.remove();
268
+ window.URL.revokeObjectURL(url);
269
+ }
270
+ }
271
+ catch (e) {
272
+ console.log(e);
273
+ }
274
+ });
275
+ }
276
+ DomUtils.downloadFile = downloadFile;
234
277
  /**
235
278
  * File to data URL
236
279
  * @param file File
@@ -1,3 +1,4 @@
1
+ export * from './types/ContentDisposition';
1
2
  export * from './types/DelayedExecutorType';
2
3
  export * from './types/EColor';
3
4
  export * from './types/EHistory';
package/lib/cjs/index.js CHANGED
@@ -14,6 +14,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
14
  for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
15
  };
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
+ __exportStar(require("./types/ContentDisposition"), exports);
17
18
  __exportStar(require("./types/DelayedExecutorType"), exports);
18
19
  __exportStar(require("./types/EColor"), exports);
19
20
  __exportStar(require("./types/EHistory"), exports);
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Content disposition of HTTP
3
+ * https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Disposition
4
+ */
5
+ export declare class ContentDisposition {
6
+ readonly type: string;
7
+ readonly filename: string;
8
+ readonly name?: string | undefined;
9
+ constructor(type: 'inline' | 'attachment', filename: string);
10
+ constructor(type: 'form-data', filename: string, name: string);
11
+ /**
12
+ * Format to standard output
13
+ * @returns Result
14
+ */
15
+ format(): string;
16
+ /**
17
+ * Parse header value
18
+ * @param header Content-Disposition header value
19
+ * @returns Object
20
+ */
21
+ static parse(header: string | undefined | null): ContentDisposition | undefined;
22
+ }
@@ -0,0 +1,75 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ContentDisposition = void 0;
4
+ const Utils_1 = require("../Utils");
5
+ /**
6
+ * Content disposition of HTTP
7
+ * https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Disposition
8
+ */
9
+ class ContentDisposition {
10
+ constructor(type, filename, name) {
11
+ this.type = type;
12
+ this.filename = filename;
13
+ this.name = name;
14
+ }
15
+ /**
16
+ * Format to standard output
17
+ * @returns Result
18
+ */
19
+ format() {
20
+ const items = [this.type];
21
+ if (this.name)
22
+ items.push(`name="${this.name}"`);
23
+ const filename1 = this.filename.replace(/[^a-zA-Z0-9\.-]/g, '_');
24
+ items.push(`filename="${filename1}"`);
25
+ if (filename1 != this.filename)
26
+ items.push(`filename*="UTF-8''${encodeURIComponent(this.filename)}"`);
27
+ return items.join('; ');
28
+ }
29
+ /**
30
+ * Parse header value
31
+ * @param header Content-Disposition header value
32
+ * @returns Object
33
+ */
34
+ static parse(header) {
35
+ if (!header)
36
+ return undefined;
37
+ const parts = header.trim().split(/\s*;\s*/g);
38
+ const len = parts.length;
39
+ if (len < 2)
40
+ return undefined;
41
+ const type = parts[0];
42
+ let name;
43
+ let filename = '';
44
+ for (let i = 1; i < len; i++) {
45
+ const part = parts[i];
46
+ let [field, value] = part.split(/\s*=\s*/g);
47
+ // case-insensitive
48
+ field = field.toLowerCase();
49
+ // Remove quotes
50
+ value = Utils_1.Utils.trim(value, '"');
51
+ if (field === 'name') {
52
+ name = value;
53
+ }
54
+ else if (field === 'filename') {
55
+ if (filename === '')
56
+ filename = value;
57
+ }
58
+ else if (field === 'filename*') {
59
+ const pos = value.indexOf("''");
60
+ filename =
61
+ pos == -1
62
+ ? value
63
+ : decodeURIComponent(value.substring(pos + 2));
64
+ }
65
+ }
66
+ if (type === 'form-data') {
67
+ return new ContentDisposition(type, filename, name !== null && name !== void 0 ? name : 'file');
68
+ }
69
+ if (type === 'inline' || type === 'attachment') {
70
+ return new ContentDisposition(type, filename);
71
+ }
72
+ return undefined;
73
+ }
74
+ }
75
+ exports.ContentDisposition = ContentDisposition;
@@ -1,3 +1,4 @@
1
+ /// <reference lib="dom" />
1
2
  import { DataTypes } from './DataTypes';
2
3
  import { FormDataFieldValue, IFormData } from './types/FormData';
3
4
  /**
@@ -50,6 +51,13 @@ export declare namespace DomUtils {
50
51
  * @param d2 Dimension 2
51
52
  */
52
53
  function dimensionEqual(d1?: DOMRect, d2?: DOMRect): boolean;
54
+ /**
55
+ * Download file from API fetch response body
56
+ * @param data Data
57
+ * @param suggestedName Suggested file name
58
+ * @param autoDetect Auto detect, false will use link click way
59
+ */
60
+ function downloadFile(data: ReadableStream | Blob, suggestedName?: string, autoDetect?: boolean): Promise<void>;
53
61
  /**
54
62
  * File to data URL
55
63
  * @param file File
@@ -7,6 +7,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
7
7
  step((generator = generator.apply(thisArg, _arguments || [])).next());
8
8
  });
9
9
  };
10
+ /// <reference lib="dom" />
10
11
  import { DataTypes } from './DataTypes';
11
12
  if (typeof navigator === 'undefined') {
12
13
  // Test mock only
@@ -228,6 +229,48 @@ export var DomUtils;
228
229
  return false;
229
230
  }
230
231
  DomUtils.dimensionEqual = dimensionEqual;
232
+ /**
233
+ * Download file from API fetch response body
234
+ * @param data Data
235
+ * @param suggestedName Suggested file name
236
+ * @param autoDetect Auto detect, false will use link click way
237
+ */
238
+ function downloadFile(data, suggestedName, autoDetect = true) {
239
+ return __awaiter(this, void 0, void 0, function* () {
240
+ try {
241
+ if (autoDetect && 'showSaveFilePicker' in globalThis) {
242
+ const handle = yield globalThis.showSaveFilePicker({
243
+ suggestedName
244
+ });
245
+ const stream = yield handle.createWritable();
246
+ if (data instanceof Blob) {
247
+ data.stream().pipeTo(stream);
248
+ }
249
+ else {
250
+ yield data.pipeTo(stream);
251
+ }
252
+ }
253
+ else {
254
+ const url = window.URL.createObjectURL(data instanceof Blob
255
+ ? data
256
+ : yield new Response(data).blob());
257
+ const a = document.createElement('a');
258
+ a.style.display = 'none';
259
+ a.href = url;
260
+ if (suggestedName)
261
+ a.download = suggestedName;
262
+ document.body.appendChild(a);
263
+ a.click();
264
+ a.remove();
265
+ window.URL.revokeObjectURL(url);
266
+ }
267
+ }
268
+ catch (e) {
269
+ console.log(e);
270
+ }
271
+ });
272
+ }
273
+ DomUtils.downloadFile = downloadFile;
231
274
  /**
232
275
  * File to data URL
233
276
  * @param file File
@@ -1,3 +1,4 @@
1
+ export * from './types/ContentDisposition';
1
2
  export * from './types/DelayedExecutorType';
2
3
  export * from './types/EColor';
3
4
  export * from './types/EHistory';
package/lib/mjs/index.js CHANGED
@@ -1,3 +1,4 @@
1
+ export * from './types/ContentDisposition';
1
2
  export * from './types/DelayedExecutorType';
2
3
  export * from './types/EColor';
3
4
  export * from './types/EHistory';
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Content disposition of HTTP
3
+ * https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Disposition
4
+ */
5
+ export declare class ContentDisposition {
6
+ readonly type: string;
7
+ readonly filename: string;
8
+ readonly name?: string | undefined;
9
+ constructor(type: 'inline' | 'attachment', filename: string);
10
+ constructor(type: 'form-data', filename: string, name: string);
11
+ /**
12
+ * Format to standard output
13
+ * @returns Result
14
+ */
15
+ format(): string;
16
+ /**
17
+ * Parse header value
18
+ * @param header Content-Disposition header value
19
+ * @returns Object
20
+ */
21
+ static parse(header: string | undefined | null): ContentDisposition | undefined;
22
+ }
@@ -0,0 +1,71 @@
1
+ import { Utils } from '../Utils';
2
+ /**
3
+ * Content disposition of HTTP
4
+ * https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Disposition
5
+ */
6
+ export class ContentDisposition {
7
+ constructor(type, filename, name) {
8
+ this.type = type;
9
+ this.filename = filename;
10
+ this.name = name;
11
+ }
12
+ /**
13
+ * Format to standard output
14
+ * @returns Result
15
+ */
16
+ format() {
17
+ const items = [this.type];
18
+ if (this.name)
19
+ items.push(`name="${this.name}"`);
20
+ const filename1 = this.filename.replace(/[^a-zA-Z0-9\.-]/g, '_');
21
+ items.push(`filename="${filename1}"`);
22
+ if (filename1 != this.filename)
23
+ items.push(`filename*="UTF-8''${encodeURIComponent(this.filename)}"`);
24
+ return items.join('; ');
25
+ }
26
+ /**
27
+ * Parse header value
28
+ * @param header Content-Disposition header value
29
+ * @returns Object
30
+ */
31
+ static parse(header) {
32
+ if (!header)
33
+ return undefined;
34
+ const parts = header.trim().split(/\s*;\s*/g);
35
+ const len = parts.length;
36
+ if (len < 2)
37
+ return undefined;
38
+ const type = parts[0];
39
+ let name;
40
+ let filename = '';
41
+ for (let i = 1; i < len; i++) {
42
+ const part = parts[i];
43
+ let [field, value] = part.split(/\s*=\s*/g);
44
+ // case-insensitive
45
+ field = field.toLowerCase();
46
+ // Remove quotes
47
+ value = Utils.trim(value, '"');
48
+ if (field === 'name') {
49
+ name = value;
50
+ }
51
+ else if (field === 'filename') {
52
+ if (filename === '')
53
+ filename = value;
54
+ }
55
+ else if (field === 'filename*') {
56
+ const pos = value.indexOf("''");
57
+ filename =
58
+ pos == -1
59
+ ? value
60
+ : decodeURIComponent(value.substring(pos + 2));
61
+ }
62
+ }
63
+ if (type === 'form-data') {
64
+ return new ContentDisposition(type, filename, name !== null && name !== void 0 ? name : 'file');
65
+ }
66
+ if (type === 'inline' || type === 'attachment') {
67
+ return new ContentDisposition(type, filename);
68
+ }
69
+ return undefined;
70
+ }
71
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@etsoo/shared",
3
- "version": "1.1.84",
3
+ "version": "1.1.86",
4
4
  "description": "TypeScript shared utilities and functions",
5
5
  "main": "lib/cjs/index.js",
6
6
  "module": "lib/mjs/index.js",
@@ -54,13 +54,8 @@
54
54
  },
55
55
  "homepage": "https://github.com/ETSOO/Shared#readme",
56
56
  "devDependencies": {
57
- "@types/jest": "^29.2.4",
57
+ "@types/jest": "^29.2.5",
58
58
  "@types/lodash.isequal": "^4.5.6",
59
- "@typescript-eslint/eslint-plugin": "^5.47.0",
60
- "@typescript-eslint/parser": "^5.47.0",
61
- "eslint": "^8.30.0",
62
- "eslint-config-airbnb-base": "^15.0.0",
63
- "eslint-plugin-import": "^2.26.0",
64
59
  "jest": "^29.3.1",
65
60
  "jest-environment-jsdom": "^29.3.1",
66
61
  "ts-jest": "^29.0.3",
package/src/DomUtils.ts CHANGED
@@ -1,3 +1,4 @@
1
+ /// <reference lib="dom" />
1
2
  import { DataTypes } from './DataTypes';
2
3
  import { FormDataFieldValue, IFormData } from './types/FormData';
3
4
 
@@ -270,6 +271,53 @@ export namespace DomUtils {
270
271
  return false;
271
272
  }
272
273
 
274
+ /**
275
+ * Download file from API fetch response body
276
+ * @param data Data
277
+ * @param suggestedName Suggested file name
278
+ * @param autoDetect Auto detect, false will use link click way
279
+ */
280
+ export async function downloadFile(
281
+ data: ReadableStream | Blob,
282
+ suggestedName?: string,
283
+ autoDetect: boolean = true
284
+ ) {
285
+ try {
286
+ if (autoDetect && 'showSaveFilePicker' in globalThis) {
287
+ const handle = await (globalThis as any).showSaveFilePicker({
288
+ suggestedName
289
+ });
290
+
291
+ const stream = await handle.createWritable();
292
+
293
+ if (data instanceof Blob) {
294
+ data.stream().pipeTo(stream);
295
+ } else {
296
+ await data.pipeTo(stream);
297
+ }
298
+ } else {
299
+ const url = window.URL.createObjectURL(
300
+ data instanceof Blob
301
+ ? data
302
+ : await new Response(data).blob()
303
+ );
304
+
305
+ const a = document.createElement('a');
306
+ a.style.display = 'none';
307
+ a.href = url;
308
+ if (suggestedName) a.download = suggestedName;
309
+
310
+ document.body.appendChild(a);
311
+ a.click();
312
+ a.remove();
313
+
314
+ window.URL.revokeObjectURL(url);
315
+ }
316
+ } catch (e) {
317
+ console.log(e);
318
+ }
319
+ }
320
+
273
321
  /**
274
322
  * File to data URL
275
323
  * @param file File
package/src/index.ts CHANGED
@@ -1,3 +1,4 @@
1
+ export * from './types/ContentDisposition';
1
2
  export * from './types/DelayedExecutorType';
2
3
  export * from './types/EColor';
3
4
  export * from './types/EHistory';
@@ -0,0 +1,81 @@
1
+ import { Utils } from '../Utils';
2
+
3
+ /**
4
+ * Content disposition of HTTP
5
+ * https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Disposition
6
+ */
7
+ export class ContentDisposition {
8
+ constructor(type: 'inline' | 'attachment', filename: string);
9
+ constructor(type: 'form-data', filename: string, name: string);
10
+ constructor(
11
+ public readonly type: string,
12
+ public readonly filename: string,
13
+ public readonly name?: string
14
+ ) {}
15
+
16
+ /**
17
+ * Format to standard output
18
+ * @returns Result
19
+ */
20
+ format() {
21
+ const items = [this.type];
22
+ if (this.name) items.push(`name="${this.name}"`);
23
+ const filename1 = this.filename.replace(/[^a-zA-Z0-9\.-]/g, '_');
24
+ items.push(`filename="${filename1}"`);
25
+ if (filename1 != this.filename)
26
+ items.push(
27
+ `filename*="UTF-8''${encodeURIComponent(this.filename)}"`
28
+ );
29
+ return items.join('; ');
30
+ }
31
+
32
+ /**
33
+ * Parse header value
34
+ * @param header Content-Disposition header value
35
+ * @returns Object
36
+ */
37
+ static parse(header: string | undefined | null) {
38
+ if (!header) return undefined;
39
+
40
+ const parts = header.trim().split(/\s*;\s*/g);
41
+ const len = parts.length;
42
+ if (len < 2) return undefined;
43
+
44
+ const type = parts[0];
45
+ let name: string | undefined;
46
+ let filename: string = '';
47
+
48
+ for (let i = 1; i < len; i++) {
49
+ const part = parts[i];
50
+ let [field, value] = part.split(/\s*=\s*/g);
51
+
52
+ // case-insensitive
53
+ field = field.toLowerCase();
54
+
55
+ // Remove quotes
56
+ value = Utils.trim(value, '"');
57
+
58
+ if (field === 'name') {
59
+ name = value;
60
+ } else if (field === 'filename') {
61
+ if (filename === '') filename = value;
62
+ } else if (field === 'filename*') {
63
+ const pos = value.indexOf("''");
64
+ filename =
65
+ pos == -1
66
+ ? value
67
+ : decodeURIComponent(value.substring(pos + 2));
68
+ }
69
+ }
70
+
71
+ if (type === 'form-data') {
72
+ return new ContentDisposition(type, filename, name ?? 'file');
73
+ }
74
+
75
+ if (type === 'inline' || type === 'attachment') {
76
+ return new ContentDisposition(type, filename);
77
+ }
78
+
79
+ return undefined;
80
+ }
81
+ }