@ailuracode/alpine-export 0.1.0 → 0.1.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/README.md CHANGED
@@ -35,7 +35,7 @@ Alpine.start();
35
35
  | | |
36
36
  |-|-|
37
37
  | **Magic** | `$export(source, options?)` |
38
- | **Helpers** | `$export.isSupported()` |
38
+ | **Helpers** | `$export.isSupported` (getter) |
39
39
 
40
40
  Callable like `$clipboard` and `$share`.
41
41
 
package/dist/index.d.ts CHANGED
@@ -6,7 +6,7 @@ type ExportOptions = {
6
6
  };
7
7
  type ExportSource = string | Blob | File;
8
8
  type ExportMagic = ((source: ExportSource, options?: ExportOptions | string) => Promise<boolean>) & {
9
- isSupported(): boolean;
9
+ readonly isSupported: boolean;
10
10
  };
11
11
  /** Returns whether programmatic file exports are available in this environment. */
12
12
  declare function isExportSupported(): boolean;
package/dist/index.js CHANGED
@@ -43,6 +43,16 @@ function exportBlob(blob, filename) {
43
43
  function isUrlLike(value) {
44
44
  return URL_LIKE.test(value);
45
45
  }
46
+ function isSameOriginUrl(value) {
47
+ if (!/^https?:/i.test(value)) {
48
+ return true;
49
+ }
50
+ try {
51
+ return new URL(value, window.location.href).origin === window.location.origin;
52
+ } catch {
53
+ return false;
54
+ }
55
+ }
46
56
  function exportData(source, options) {
47
57
  if (!isExportSupported()) {
48
58
  return Promise.resolve(false);
@@ -55,6 +65,9 @@ function exportData(source, options) {
55
65
  }
56
66
  const value = String(source);
57
67
  if (isUrlLike(value)) {
68
+ if (!isSameOriginUrl(value)) {
69
+ return Promise.resolve(false);
70
+ }
58
71
  triggerAnchorExport(value, filename);
59
72
  return Promise.resolve(true);
60
73
  }
@@ -68,8 +81,8 @@ function exportData(source, options) {
68
81
  }
69
82
  }
70
83
  function createExportMagic() {
71
- const exportFile = (source, options) => exportData(source, options);
72
- exportFile.isSupported = isExportSupported;
84
+ const exportFile = ((source, options) => exportData(source, options));
85
+ Object.defineProperty(exportFile, "isSupported", { get: isExportSupported });
73
86
  return exportFile;
74
87
  }
75
88
  function exportPlugin(Alpine) {
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts"],"sourcesContent":["import type AlpineType from \"alpinejs\";\n\nexport type ExportOptions = {\n filename?: string;\n mimeType?: string;\n};\n\nexport type ExportSource = string | Blob | File;\n\nexport type ExportMagic = ((\n source: ExportSource,\n options?: ExportOptions | string\n) => Promise<boolean>) & {\n isSupported(): boolean;\n};\n\nconst URL_LIKE = /^(https?:|data:|blob:|\\/|\\.\\/|\\.\\.\\/)/i;\n\nfunction normalizeOptions(options?: ExportOptions | string): ExportOptions {\n if (typeof options === \"string\") {\n return { filename: options };\n }\n\n return options ?? {};\n}\n\nfunction isExportEnvironment(): boolean {\n return (\n typeof document !== \"undefined\" &&\n typeof document.createElement === \"function\" &&\n typeof URL !== \"undefined\" &&\n typeof URL.createObjectURL === \"function\"\n );\n}\n\n/** Returns whether programmatic file exports are available in this environment. */\nexport function isExportSupported(): boolean {\n return isExportEnvironment();\n}\n\nfunction triggerAnchorExport(href: string, filename?: string): void {\n const anchor = document.createElement(\"a\");\n anchor.href = href;\n anchor.rel = \"noopener\";\n\n if (filename) {\n anchor.download = filename;\n }\n\n anchor.style.display = \"none\";\n document.body.appendChild(anchor);\n anchor.click();\n document.body.removeChild(anchor);\n}\n\nfunction revokeObjectUrlLater(url: string): void {\n setTimeout(() => {\n URL.revokeObjectURL(url);\n }, 0);\n}\n\nfunction exportBlob(blob: Blob, filename: string): boolean {\n const url = URL.createObjectURL(blob);\n\n try {\n triggerAnchorExport(url, filename);\n return true;\n } catch {\n return false;\n } finally {\n revokeObjectUrlLater(url);\n }\n}\n\nfunction isUrlLike(value: string): boolean {\n return URL_LIKE.test(value);\n}\n\n/** Exports a URL, blob, file, or text payload as a download. Resolves to `true` on success. Never throws. */\nexport function exportData(\n source: ExportSource,\n options?: ExportOptions | string\n): Promise<boolean> {\n if (!isExportSupported()) {\n return Promise.resolve(false);\n }\n\n const { filename, mimeType } = normalizeOptions(options);\n\n try {\n if (source instanceof Blob) {\n const name = filename ?? (source instanceof File ? source.name : \"export\");\n return Promise.resolve(exportBlob(source, name));\n }\n\n const value = String(source);\n\n if (isUrlLike(value)) {\n triggerAnchorExport(value, filename);\n return Promise.resolve(true);\n }\n\n if (!filename) {\n return Promise.resolve(false);\n }\n\n const blob = new Blob([value], { type: mimeType ?? \"text/plain;charset=utf-8\" });\n return Promise.resolve(exportBlob(blob, filename));\n } catch {\n return Promise.resolve(false);\n }\n}\n\n/** Builds callable `$export` magic with `isSupported` helper. */\nexport function createExportMagic(): ExportMagic {\n const exportFile = (source: ExportSource, options?: ExportOptions | string) =>\n exportData(source, options);\n exportFile.isSupported = isExportSupported;\n return exportFile;\n}\n\n/** Alpine.js export plugin. Registers callable magic `$export`. */\nexport default function exportPlugin(Alpine: AlpineType.Alpine): void {\n Alpine.magic(\"export\", () => createExportMagic());\n}\n\ndeclare global {\n namespace Alpine {\n interface Magics<T> {\n $export: ExportMagic;\n }\n }\n}\n"],"mappings":";AAgBA,IAAM,WAAW;AAEjB,SAAS,iBAAiB,SAAiD;AACzE,MAAI,OAAO,YAAY,UAAU;AAC/B,WAAO,EAAE,UAAU,QAAQ;AAAA,EAC7B;AAEA,SAAO,WAAW,CAAC;AACrB;AAEA,SAAS,sBAA+B;AACtC,SACE,OAAO,aAAa,eACpB,OAAO,SAAS,kBAAkB,cAClC,OAAO,QAAQ,eACf,OAAO,IAAI,oBAAoB;AAEnC;AAGO,SAAS,oBAA6B;AAC3C,SAAO,oBAAoB;AAC7B;AAEA,SAAS,oBAAoB,MAAc,UAAyB;AAClE,QAAM,SAAS,SAAS,cAAc,GAAG;AACzC,SAAO,OAAO;AACd,SAAO,MAAM;AAEb,MAAI,UAAU;AACZ,WAAO,WAAW;AAAA,EACpB;AAEA,SAAO,MAAM,UAAU;AACvB,WAAS,KAAK,YAAY,MAAM;AAChC,SAAO,MAAM;AACb,WAAS,KAAK,YAAY,MAAM;AAClC;AAEA,SAAS,qBAAqB,KAAmB;AAC/C,aAAW,MAAM;AACf,QAAI,gBAAgB,GAAG;AAAA,EACzB,GAAG,CAAC;AACN;AAEA,SAAS,WAAW,MAAY,UAA2B;AACzD,QAAM,MAAM,IAAI,gBAAgB,IAAI;AAEpC,MAAI;AACF,wBAAoB,KAAK,QAAQ;AACjC,WAAO;AAAA,EACT,QAAQ;AACN,WAAO;AAAA,EACT,UAAE;AACA,yBAAqB,GAAG;AAAA,EAC1B;AACF;AAEA,SAAS,UAAU,OAAwB;AACzC,SAAO,SAAS,KAAK,KAAK;AAC5B;AAGO,SAAS,WACd,QACA,SACkB;AAClB,MAAI,CAAC,kBAAkB,GAAG;AACxB,WAAO,QAAQ,QAAQ,KAAK;AAAA,EAC9B;AAEA,QAAM,EAAE,UAAU,SAAS,IAAI,iBAAiB,OAAO;AAEvD,MAAI;AACF,QAAI,kBAAkB,MAAM;AAC1B,YAAM,OAAO,aAAa,kBAAkB,OAAO,OAAO,OAAO;AACjE,aAAO,QAAQ,QAAQ,WAAW,QAAQ,IAAI,CAAC;AAAA,IACjD;AAEA,UAAM,QAAQ,OAAO,MAAM;AAE3B,QAAI,UAAU,KAAK,GAAG;AACpB,0BAAoB,OAAO,QAAQ;AACnC,aAAO,QAAQ,QAAQ,IAAI;AAAA,IAC7B;AAEA,QAAI,CAAC,UAAU;AACb,aAAO,QAAQ,QAAQ,KAAK;AAAA,IAC9B;AAEA,UAAM,OAAO,IAAI,KAAK,CAAC,KAAK,GAAG,EAAE,MAAM,YAAY,2BAA2B,CAAC;AAC/E,WAAO,QAAQ,QAAQ,WAAW,MAAM,QAAQ,CAAC;AAAA,EACnD,QAAQ;AACN,WAAO,QAAQ,QAAQ,KAAK;AAAA,EAC9B;AACF;AAGO,SAAS,oBAAiC;AAC/C,QAAM,aAAa,CAAC,QAAsB,YACxC,WAAW,QAAQ,OAAO;AAC5B,aAAW,cAAc;AACzB,SAAO;AACT;AAGe,SAAR,aAA8B,QAAiC;AACpE,SAAO,MAAM,UAAU,MAAM,kBAAkB,CAAC;AAClD;","names":[]}
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["import type AlpineType from \"alpinejs\";\n\nexport type ExportOptions = {\n filename?: string;\n mimeType?: string;\n};\n\nexport type ExportSource = string | Blob | File;\n\nexport type ExportMagic = ((\n source: ExportSource,\n options?: ExportOptions | string\n) => Promise<boolean>) & {\n readonly isSupported: boolean;\n};\n\nconst URL_LIKE = /^(https?:|data:|blob:|\\/|\\.\\/|\\.\\.\\/)/i;\n\nfunction normalizeOptions(options?: ExportOptions | string): ExportOptions {\n if (typeof options === \"string\") {\n return { filename: options };\n }\n\n return options ?? {};\n}\n\nfunction isExportEnvironment(): boolean {\n return (\n typeof document !== \"undefined\" &&\n typeof document.createElement === \"function\" &&\n typeof URL !== \"undefined\" &&\n typeof URL.createObjectURL === \"function\"\n );\n}\n\n/** Returns whether programmatic file exports are available in this environment. */\nexport function isExportSupported(): boolean {\n return isExportEnvironment();\n}\n\nfunction triggerAnchorExport(href: string, filename?: string): void {\n const anchor = document.createElement(\"a\");\n anchor.href = href;\n anchor.rel = \"noopener\";\n\n if (filename) {\n anchor.download = filename;\n }\n\n anchor.style.display = \"none\";\n document.body.appendChild(anchor);\n anchor.click();\n document.body.removeChild(anchor);\n}\n\nfunction revokeObjectUrlLater(url: string): void {\n setTimeout(() => {\n URL.revokeObjectURL(url);\n }, 0);\n}\n\nfunction exportBlob(blob: Blob, filename: string): boolean {\n const url = URL.createObjectURL(blob);\n\n try {\n triggerAnchorExport(url, filename);\n return true;\n } catch {\n return false;\n } finally {\n revokeObjectUrlLater(url);\n }\n}\n\nfunction isUrlLike(value: string): boolean {\n return URL_LIKE.test(value);\n}\n\nfunction isSameOriginUrl(value: string): boolean {\n if (!/^https?:/i.test(value)) {\n return true;\n }\n\n try {\n return new URL(value, window.location.href).origin === window.location.origin;\n } catch {\n return false;\n }\n}\n\n/** Exports a URL, blob, file, or text payload as a download. Resolves to `true` on success. Never throws. */\nexport function exportData(\n source: ExportSource,\n options?: ExportOptions | string\n): Promise<boolean> {\n if (!isExportSupported()) {\n return Promise.resolve(false);\n }\n\n const { filename, mimeType } = normalizeOptions(options);\n\n try {\n if (source instanceof Blob) {\n const name = filename ?? (source instanceof File ? source.name : \"export\");\n return Promise.resolve(exportBlob(source, name));\n }\n\n const value = String(source);\n\n if (isUrlLike(value)) {\n if (!isSameOriginUrl(value)) {\n return Promise.resolve(false);\n }\n\n triggerAnchorExport(value, filename);\n return Promise.resolve(true);\n }\n\n if (!filename) {\n return Promise.resolve(false);\n }\n\n const blob = new Blob([value], { type: mimeType ?? \"text/plain;charset=utf-8\" });\n return Promise.resolve(exportBlob(blob, filename));\n } catch {\n return Promise.resolve(false);\n }\n}\n\n/** Builds callable `$export` magic with `isSupported` helper. */\nexport function createExportMagic(): ExportMagic {\n const exportFile = ((source: ExportSource, options?: ExportOptions | string) =>\n exportData(source, options)) as ExportMagic;\n Object.defineProperty(exportFile, \"isSupported\", { get: isExportSupported });\n return exportFile;\n}\n\n/** Alpine.js export plugin. Registers callable magic `$export`. */\nexport default function exportPlugin(Alpine: AlpineType.Alpine): void {\n Alpine.magic(\"export\", () => createExportMagic());\n}\n\ndeclare global {\n namespace Alpine {\n interface Magics<T> {\n $export: ExportMagic;\n }\n }\n}\n"],"mappings":";AAgBA,IAAM,WAAW;AAEjB,SAAS,iBAAiB,SAAiD;AACzE,MAAI,OAAO,YAAY,UAAU;AAC/B,WAAO,EAAE,UAAU,QAAQ;AAAA,EAC7B;AAEA,SAAO,WAAW,CAAC;AACrB;AAEA,SAAS,sBAA+B;AACtC,SACE,OAAO,aAAa,eACpB,OAAO,SAAS,kBAAkB,cAClC,OAAO,QAAQ,eACf,OAAO,IAAI,oBAAoB;AAEnC;AAGO,SAAS,oBAA6B;AAC3C,SAAO,oBAAoB;AAC7B;AAEA,SAAS,oBAAoB,MAAc,UAAyB;AAClE,QAAM,SAAS,SAAS,cAAc,GAAG;AACzC,SAAO,OAAO;AACd,SAAO,MAAM;AAEb,MAAI,UAAU;AACZ,WAAO,WAAW;AAAA,EACpB;AAEA,SAAO,MAAM,UAAU;AACvB,WAAS,KAAK,YAAY,MAAM;AAChC,SAAO,MAAM;AACb,WAAS,KAAK,YAAY,MAAM;AAClC;AAEA,SAAS,qBAAqB,KAAmB;AAC/C,aAAW,MAAM;AACf,QAAI,gBAAgB,GAAG;AAAA,EACzB,GAAG,CAAC;AACN;AAEA,SAAS,WAAW,MAAY,UAA2B;AACzD,QAAM,MAAM,IAAI,gBAAgB,IAAI;AAEpC,MAAI;AACF,wBAAoB,KAAK,QAAQ;AACjC,WAAO;AAAA,EACT,QAAQ;AACN,WAAO;AAAA,EACT,UAAE;AACA,yBAAqB,GAAG;AAAA,EAC1B;AACF;AAEA,SAAS,UAAU,OAAwB;AACzC,SAAO,SAAS,KAAK,KAAK;AAC5B;AAEA,SAAS,gBAAgB,OAAwB;AAC/C,MAAI,CAAC,YAAY,KAAK,KAAK,GAAG;AAC5B,WAAO;AAAA,EACT;AAEA,MAAI;AACF,WAAO,IAAI,IAAI,OAAO,OAAO,SAAS,IAAI,EAAE,WAAW,OAAO,SAAS;AAAA,EACzE,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAGO,SAAS,WACd,QACA,SACkB;AAClB,MAAI,CAAC,kBAAkB,GAAG;AACxB,WAAO,QAAQ,QAAQ,KAAK;AAAA,EAC9B;AAEA,QAAM,EAAE,UAAU,SAAS,IAAI,iBAAiB,OAAO;AAEvD,MAAI;AACF,QAAI,kBAAkB,MAAM;AAC1B,YAAM,OAAO,aAAa,kBAAkB,OAAO,OAAO,OAAO;AACjE,aAAO,QAAQ,QAAQ,WAAW,QAAQ,IAAI,CAAC;AAAA,IACjD;AAEA,UAAM,QAAQ,OAAO,MAAM;AAE3B,QAAI,UAAU,KAAK,GAAG;AACpB,UAAI,CAAC,gBAAgB,KAAK,GAAG;AAC3B,eAAO,QAAQ,QAAQ,KAAK;AAAA,MAC9B;AAEA,0BAAoB,OAAO,QAAQ;AACnC,aAAO,QAAQ,QAAQ,IAAI;AAAA,IAC7B;AAEA,QAAI,CAAC,UAAU;AACb,aAAO,QAAQ,QAAQ,KAAK;AAAA,IAC9B;AAEA,UAAM,OAAO,IAAI,KAAK,CAAC,KAAK,GAAG,EAAE,MAAM,YAAY,2BAA2B,CAAC;AAC/E,WAAO,QAAQ,QAAQ,WAAW,MAAM,QAAQ,CAAC;AAAA,EACnD,QAAQ;AACN,WAAO,QAAQ,QAAQ,KAAK;AAAA,EAC9B;AACF;AAGO,SAAS,oBAAiC;AAC/C,QAAM,cAAc,CAAC,QAAsB,YACzC,WAAW,QAAQ,OAAO;AAC5B,SAAO,eAAe,YAAY,eAAe,EAAE,KAAK,kBAAkB,CAAC;AAC3E,SAAO;AACT;AAGe,SAAR,aAA8B,QAAiC;AACpE,SAAO,MAAM,UAAU,MAAM,kBAAkB,CAAC;AAClD;","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ailuracode/alpine-export",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Alpine.js file export magic",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -50,6 +50,6 @@
50
50
  ],
51
51
  "scripts": {
52
52
  "build": "tsup src/index.ts --format esm --dts --clean --sourcemap --out-dir dist && cp src/global.d.ts dist/global.d.ts",
53
- "test": "vitest run --config ../../vitest.config.js test"
53
+ "test": "vitest run --config ../../vitest.config.ts test"
54
54
  }
55
55
  }