@lamberl-lee/file-preview 0.2.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.
Files changed (263) hide show
  1. package/COPYING +674 -0
  2. package/LICENSE +165 -0
  3. package/README.md +165 -0
  4. package/dist/AudioPreview.d.ts +9 -0
  5. package/dist/AudioPreview.js +29 -0
  6. package/dist/AudioPreview.js.map +1 -0
  7. package/dist/CodePreview.d.ts +10 -0
  8. package/dist/CodePreview.js +121 -0
  9. package/dist/CodePreview.js.map +1 -0
  10. package/dist/CsvPreview.d.ts +9 -0
  11. package/dist/CsvPreview.js +117 -0
  12. package/dist/CsvPreview.js.map +1 -0
  13. package/dist/DocxPreview.d.ts +11 -0
  14. package/dist/DocxPreview.js +89 -0
  15. package/dist/DocxPreview.js.map +1 -0
  16. package/dist/EpubPreview.d.ts +9 -0
  17. package/dist/EpubPreview.js +693 -0
  18. package/dist/EpubPreview.js.map +1 -0
  19. package/dist/HtmlPreview.d.ts +9 -0
  20. package/dist/HtmlPreview.js +60 -0
  21. package/dist/HtmlPreview.js.map +1 -0
  22. package/dist/ImagePreview.d.ts +9 -0
  23. package/dist/ImagePreview.js +44 -0
  24. package/dist/ImagePreview.js.map +1 -0
  25. package/dist/LargeFileGate.d.ts +12 -0
  26. package/dist/LargeFileGate.js +88 -0
  27. package/dist/LargeFileGate.js.map +1 -0
  28. package/dist/MarkdownPreview.d.ts +8 -0
  29. package/dist/MarkdownPreview.js +140 -0
  30. package/dist/MarkdownPreview.js.map +1 -0
  31. package/dist/PdfPreview.d.ts +11 -0
  32. package/dist/PdfPreview.js +206 -0
  33. package/dist/PdfPreview.js.map +1 -0
  34. package/dist/PlainTextLargePreview.d.ts +9 -0
  35. package/dist/PlainTextLargePreview.js +62 -0
  36. package/dist/PlainTextLargePreview.js.map +1 -0
  37. package/dist/PluginPreviewRenderer.d.ts +13 -0
  38. package/dist/PluginPreviewRenderer.js +89 -0
  39. package/dist/PluginPreviewRenderer.js.map +1 -0
  40. package/dist/PptxPreview.d.ts +16 -0
  41. package/dist/PptxPreview.js +376 -0
  42. package/dist/PptxPreview.js.map +1 -0
  43. package/dist/PreviewErrorBoundary.d.ts +29 -0
  44. package/dist/PreviewErrorBoundary.js +53 -0
  45. package/dist/PreviewErrorBoundary.js.map +1 -0
  46. package/dist/PreviewFallback.d.ts +18 -0
  47. package/dist/PreviewFallback.js +143 -0
  48. package/dist/PreviewFallback.js.map +1 -0
  49. package/dist/PreviewLoading.d.ts +8 -0
  50. package/dist/PreviewLoading.js +14 -0
  51. package/dist/PreviewLoading.js.map +1 -0
  52. package/dist/RtfPreview.d.ts +10 -0
  53. package/dist/RtfPreview.js +240 -0
  54. package/dist/RtfPreview.js.map +1 -0
  55. package/dist/ShikiSourceView.d.ts +11 -0
  56. package/dist/ShikiSourceView.js +112 -0
  57. package/dist/ShikiSourceView.js.map +1 -0
  58. package/dist/SvgPreview.d.ts +9 -0
  59. package/dist/SvgPreview.js +89 -0
  60. package/dist/SvgPreview.js.map +1 -0
  61. package/dist/TextPreview.d.ts +9 -0
  62. package/dist/TextPreview.js +9 -0
  63. package/dist/TextPreview.js.map +1 -0
  64. package/dist/VideoPreview.d.ts +9 -0
  65. package/dist/VideoPreview.js +18 -0
  66. package/dist/VideoPreview.js.map +1 -0
  67. package/dist/XlsxPreview.d.ts +12 -0
  68. package/dist/XlsxPreview.js +856 -0
  69. package/dist/XlsxPreview.js.map +1 -0
  70. package/dist/ZipPreview.d.ts +9 -0
  71. package/dist/ZipPreview.js +153 -0
  72. package/dist/ZipPreview.js.map +1 -0
  73. package/dist/core/binary.d.ts +10 -0
  74. package/dist/core/binary.js +27 -0
  75. package/dist/core/binary.js.map +1 -0
  76. package/dist/core/config.d.ts +17 -0
  77. package/dist/core/config.js +19 -0
  78. package/dist/core/config.js.map +1 -0
  79. package/dist/core/download.d.ts +5 -0
  80. package/dist/core/download.js +20 -0
  81. package/dist/core/download.js.map +1 -0
  82. package/dist/core/i18n.d.ts +101 -0
  83. package/dist/core/i18n.js +200 -0
  84. package/dist/core/i18n.js.map +1 -0
  85. package/dist/core/plugin.d.ts +38 -0
  86. package/dist/core/plugin.js +46 -0
  87. package/dist/core/plugin.js.map +1 -0
  88. package/dist/core/registry.d.ts +13 -0
  89. package/dist/core/registry.js +33 -0
  90. package/dist/core/registry.js.map +1 -0
  91. package/dist/core/source.d.ts +14 -0
  92. package/dist/core/source.js +131 -0
  93. package/dist/core/source.js.map +1 -0
  94. package/dist/core/types.d.ts +88 -0
  95. package/dist/core/types.js +27 -0
  96. package/dist/core/types.js.map +1 -0
  97. package/dist/hooks/useObjectUrlFromSource.d.ts +9 -0
  98. package/dist/hooks/useObjectUrlFromSource.js +50 -0
  99. package/dist/hooks/useObjectUrlFromSource.js.map +1 -0
  100. package/dist/hooks/useSourceBase64.d.ts +10 -0
  101. package/dist/hooks/useSourceBase64.js +32 -0
  102. package/dist/hooks/useSourceBase64.js.map +1 -0
  103. package/dist/hooks/useSourceText.d.ts +10 -0
  104. package/dist/hooks/useSourceText.js +32 -0
  105. package/dist/hooks/useSourceText.js.map +1 -0
  106. package/dist/icons.d.ts +44 -0
  107. package/dist/icons.js +248 -0
  108. package/dist/icons.js.map +1 -0
  109. package/dist/index.d.ts +36 -0
  110. package/dist/index.js +147 -0
  111. package/dist/index.js.map +1 -0
  112. package/dist/limits.d.ts +26 -0
  113. package/dist/limits.js +45 -0
  114. package/dist/limits.js.map +1 -0
  115. package/dist/performance-limits.d.ts +27 -0
  116. package/dist/performance-limits.js +54 -0
  117. package/dist/performance-limits.js.map +1 -0
  118. package/dist/plugins/audio-plugin.d.ts +7 -0
  119. package/dist/plugins/audio-plugin.js +11 -0
  120. package/dist/plugins/audio-plugin.js.map +1 -0
  121. package/dist/plugins/builtin-plugins.d.ts +9 -0
  122. package/dist/plugins/builtin-plugins.js +43 -0
  123. package/dist/plugins/builtin-plugins.js.map +1 -0
  124. package/dist/plugins/csv-plugin.d.ts +7 -0
  125. package/dist/plugins/csv-plugin.js +11 -0
  126. package/dist/plugins/csv-plugin.js.map +1 -0
  127. package/dist/plugins/docx-plugin.d.ts +7 -0
  128. package/dist/plugins/docx-plugin.js +15 -0
  129. package/dist/plugins/docx-plugin.js.map +1 -0
  130. package/dist/plugins/epub-plugin.d.ts +7 -0
  131. package/dist/plugins/epub-plugin.js +15 -0
  132. package/dist/plugins/epub-plugin.js.map +1 -0
  133. package/dist/plugins/html-plugin.d.ts +7 -0
  134. package/dist/plugins/html-plugin.js +11 -0
  135. package/dist/plugins/html-plugin.js.map +1 -0
  136. package/dist/plugins/image-plugin.d.ts +7 -0
  137. package/dist/plugins/image-plugin.js +11 -0
  138. package/dist/plugins/image-plugin.js.map +1 -0
  139. package/dist/plugins/markdown-plugin.d.ts +7 -0
  140. package/dist/plugins/markdown-plugin.js +11 -0
  141. package/dist/plugins/markdown-plugin.js.map +1 -0
  142. package/dist/plugins/pdf-plugin.d.ts +7 -0
  143. package/dist/plugins/pdf-plugin.js +15 -0
  144. package/dist/plugins/pdf-plugin.js.map +1 -0
  145. package/dist/plugins/pptx-plugin.d.ts +7 -0
  146. package/dist/plugins/pptx-plugin.js +15 -0
  147. package/dist/plugins/pptx-plugin.js.map +1 -0
  148. package/dist/plugins/rtf-plugin.d.ts +7 -0
  149. package/dist/plugins/rtf-plugin.js +15 -0
  150. package/dist/plugins/rtf-plugin.js.map +1 -0
  151. package/dist/plugins/source-code-plugin.d.ts +7 -0
  152. package/dist/plugins/source-code-plugin.js +11 -0
  153. package/dist/plugins/source-code-plugin.js.map +1 -0
  154. package/dist/plugins/svg-plugin.d.ts +7 -0
  155. package/dist/plugins/svg-plugin.js +11 -0
  156. package/dist/plugins/svg-plugin.js.map +1 -0
  157. package/dist/plugins/text-plugin.d.ts +7 -0
  158. package/dist/plugins/text-plugin.js +11 -0
  159. package/dist/plugins/text-plugin.js.map +1 -0
  160. package/dist/plugins/video-plugin.d.ts +7 -0
  161. package/dist/plugins/video-plugin.js +11 -0
  162. package/dist/plugins/video-plugin.js.map +1 -0
  163. package/dist/plugins/xlsx-plugin.d.ts +7 -0
  164. package/dist/plugins/xlsx-plugin.js +15 -0
  165. package/dist/plugins/xlsx-plugin.js.map +1 -0
  166. package/dist/plugins/zip-plugin.d.ts +7 -0
  167. package/dist/plugins/zip-plugin.js +15 -0
  168. package/dist/plugins/zip-plugin.js.map +1 -0
  169. package/dist/preview-adapters/AudioPreviewAdapter.d.ts +8 -0
  170. package/dist/preview-adapters/AudioPreviewAdapter.js +31 -0
  171. package/dist/preview-adapters/AudioPreviewAdapter.js.map +1 -0
  172. package/dist/preview-adapters/CsvPreviewAdapter.d.ts +8 -0
  173. package/dist/preview-adapters/CsvPreviewAdapter.js +28 -0
  174. package/dist/preview-adapters/CsvPreviewAdapter.js.map +1 -0
  175. package/dist/preview-adapters/DocxPreviewAdapter.d.ts +8 -0
  176. package/dist/preview-adapters/DocxPreviewAdapter.js +16 -0
  177. package/dist/preview-adapters/DocxPreviewAdapter.js.map +1 -0
  178. package/dist/preview-adapters/EpubPreviewAdapter.d.ts +8 -0
  179. package/dist/preview-adapters/EpubPreviewAdapter.js +28 -0
  180. package/dist/preview-adapters/EpubPreviewAdapter.js.map +1 -0
  181. package/dist/preview-adapters/HtmlPreviewAdapter.d.ts +8 -0
  182. package/dist/preview-adapters/HtmlPreviewAdapter.js +28 -0
  183. package/dist/preview-adapters/HtmlPreviewAdapter.js.map +1 -0
  184. package/dist/preview-adapters/ImagePreviewAdapter.d.ts +8 -0
  185. package/dist/preview-adapters/ImagePreviewAdapter.js +31 -0
  186. package/dist/preview-adapters/ImagePreviewAdapter.js.map +1 -0
  187. package/dist/preview-adapters/MarkdownPreviewAdapter.d.ts +8 -0
  188. package/dist/preview-adapters/MarkdownPreviewAdapter.js +28 -0
  189. package/dist/preview-adapters/MarkdownPreviewAdapter.js.map +1 -0
  190. package/dist/preview-adapters/PdfPreviewAdapter.d.ts +8 -0
  191. package/dist/preview-adapters/PdfPreviewAdapter.js +16 -0
  192. package/dist/preview-adapters/PdfPreviewAdapter.js.map +1 -0
  193. package/dist/preview-adapters/PptxPreviewAdapter.d.ts +8 -0
  194. package/dist/preview-adapters/PptxPreviewAdapter.js +16 -0
  195. package/dist/preview-adapters/PptxPreviewAdapter.js.map +1 -0
  196. package/dist/preview-adapters/RtfPreviewAdapter.d.ts +8 -0
  197. package/dist/preview-adapters/RtfPreviewAdapter.js +54 -0
  198. package/dist/preview-adapters/RtfPreviewAdapter.js.map +1 -0
  199. package/dist/preview-adapters/SourceCodePreviewAdapter.d.ts +8 -0
  200. package/dist/preview-adapters/SourceCodePreviewAdapter.js +35 -0
  201. package/dist/preview-adapters/SourceCodePreviewAdapter.js.map +1 -0
  202. package/dist/preview-adapters/SvgPreviewAdapter.d.ts +8 -0
  203. package/dist/preview-adapters/SvgPreviewAdapter.js +28 -0
  204. package/dist/preview-adapters/SvgPreviewAdapter.js.map +1 -0
  205. package/dist/preview-adapters/TextPreviewAdapter.d.ts +8 -0
  206. package/dist/preview-adapters/TextPreviewAdapter.js +28 -0
  207. package/dist/preview-adapters/TextPreviewAdapter.js.map +1 -0
  208. package/dist/preview-adapters/UnsupportedPluginPreview.d.ts +11 -0
  209. package/dist/preview-adapters/UnsupportedPluginPreview.js +34 -0
  210. package/dist/preview-adapters/UnsupportedPluginPreview.js.map +1 -0
  211. package/dist/preview-adapters/VideoPreviewAdapter.d.ts +8 -0
  212. package/dist/preview-adapters/VideoPreviewAdapter.js +31 -0
  213. package/dist/preview-adapters/VideoPreviewAdapter.js.map +1 -0
  214. package/dist/preview-adapters/XlsxPreviewAdapter.d.ts +8 -0
  215. package/dist/preview-adapters/XlsxPreviewAdapter.js +17 -0
  216. package/dist/preview-adapters/XlsxPreviewAdapter.js.map +1 -0
  217. package/dist/preview-adapters/ZipPreviewAdapter.d.ts +8 -0
  218. package/dist/preview-adapters/ZipPreviewAdapter.js +28 -0
  219. package/dist/preview-adapters/ZipPreviewAdapter.js.map +1 -0
  220. package/dist/remote-url.d.ts +20 -0
  221. package/dist/remote-url.js +478 -0
  222. package/dist/remote-url.js.map +1 -0
  223. package/dist/rtf/load-rtfjs.d.ts +42 -0
  224. package/dist/rtf/load-rtfjs.js +71 -0
  225. package/dist/rtf/load-rtfjs.js.map +1 -0
  226. package/dist/rtf/normalize-codepage.d.ts +33 -0
  227. package/dist/rtf/normalize-codepage.js +88 -0
  228. package/dist/rtf/normalize-codepage.js.map +1 -0
  229. package/dist/shiki.d.ts +35 -0
  230. package/dist/shiki.js +128 -0
  231. package/dist/shiki.js.map +1 -0
  232. package/dist/styles/AudioPreview.css +35 -0
  233. package/dist/styles/CsvPreview.css +106 -0
  234. package/dist/styles/DocxPreview.css +93 -0
  235. package/dist/styles/EpubPreview.css +509 -0
  236. package/dist/styles/HtmlPreview.css +15 -0
  237. package/dist/styles/ImagePreview.css +45 -0
  238. package/dist/styles/LargeFileGate.css +55 -0
  239. package/dist/styles/MarkdownPreview.css +291 -0
  240. package/dist/styles/PdfPreview.css +68 -0
  241. package/dist/styles/PlainTextLargePreview.css +85 -0
  242. package/dist/styles/PluginDebugBar.css +30 -0
  243. package/dist/styles/PptxPreview.css +207 -0
  244. package/dist/styles/PreviewFallback.css +88 -0
  245. package/dist/styles/PreviewLoading.css +13 -0
  246. package/dist/styles/RtfPreview.css +99 -0
  247. package/dist/styles/ShikiSourceView.css +159 -0
  248. package/dist/styles/SvgPreview.css +99 -0
  249. package/dist/styles/VideoPreview.css +19 -0
  250. package/dist/styles/ViewModeBar.css +38 -0
  251. package/dist/styles/XlsxPreview.css +361 -0
  252. package/dist/styles/ZipPreview.css +86 -0
  253. package/dist/styles/base.css +238 -0
  254. package/dist/styles/index.css +39 -0
  255. package/dist/support-status.d.ts +19 -0
  256. package/dist/support-status.js +174 -0
  257. package/dist/support-status.js.map +1 -0
  258. package/dist/utils.d.ts +17 -0
  259. package/dist/utils.js +205 -0
  260. package/dist/utils.js.map +1 -0
  261. package/package.json +125 -0
  262. package/scripts/copy-pdf-worker.mjs +31 -0
  263. package/scripts/copy-rtfjs-bundles.mjs +49 -0
@@ -0,0 +1,478 @@
1
+ import { detectFileType, generateId } from "./utils";
2
+ class RemoteUrlError extends Error {
3
+ constructor(code, message, url) {
4
+ super(message);
5
+ this.code = code;
6
+ this.url = url;
7
+ this.name = "RemoteUrlError";
8
+ }
9
+ }
10
+ const REMOTE_MIME_BY_EXTENSION = {
11
+ docx: "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
12
+ pptx: "application/vnd.openxmlformats-officedocument.presentationml.presentation",
13
+ xlsx: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
14
+ doc: "application/msword",
15
+ ppt: "application/vnd.ms-powerpoint",
16
+ xls: "application/vnd.ms-excel",
17
+ pdf: "application/pdf",
18
+ zip: "application/zip",
19
+ epub: "application/epub+zip",
20
+ json: "application/json",
21
+ csv: "text/csv",
22
+ md: "text/markdown",
23
+ mdx: "text/markdown",
24
+ html: "text/html",
25
+ htm: "text/html",
26
+ svg: "image/svg+xml",
27
+ txt: "text/plain",
28
+ log: "text/plain",
29
+ png: "image/png",
30
+ jpg: "image/jpeg",
31
+ jpeg: "image/jpeg",
32
+ gif: "image/gif",
33
+ webp: "image/webp",
34
+ bmp: "image/bmp",
35
+ ico: "image/x-icon",
36
+ avif: "image/avif",
37
+ mp4: "video/mp4",
38
+ webm: "video/webm",
39
+ mov: "video/quicktime",
40
+ mp3: "audio/mpeg",
41
+ wav: "audio/wav",
42
+ ogg: "audio/ogg",
43
+ flac: "audio/flac",
44
+ aac: "audio/aac",
45
+ m4a: "audio/mp4"
46
+ };
47
+ const GENERIC_MIME_TYPES = /* @__PURE__ */ new Set([
48
+ "application/octet-stream",
49
+ "binary/octet-stream",
50
+ "application/x-msdownload",
51
+ "application/download"
52
+ ]);
53
+ const WEAK_MAGIC_MIME_TYPES = /* @__PURE__ */ new Set([
54
+ "application/zip",
55
+ "application/x-ole-storage",
56
+ "video/mp4"
57
+ ]);
58
+ function isStrongMagicMimeType(mimeType) {
59
+ return Boolean(mimeType && !WEAK_MAGIC_MIME_TYPES.has(mimeType));
60
+ }
61
+ function sanitizeRemoteFileName(fileName) {
62
+ const cleanName = fileName.replace(/[\\/:*?"<>|]/g, "_").replace(/\s+/g, " ").trim();
63
+ return cleanName || "remote-file";
64
+ }
65
+ function tryDecodeURIComponent(value) {
66
+ try {
67
+ return decodeURIComponent(value);
68
+ } catch {
69
+ return value;
70
+ }
71
+ }
72
+ function stripQuotes(value) {
73
+ const trimmed = value.trim();
74
+ if (trimmed.startsWith('"') && trimmed.endsWith('"') || trimmed.startsWith("'") && trimmed.endsWith("'")) {
75
+ return trimmed.slice(1, -1);
76
+ }
77
+ return trimmed;
78
+ }
79
+ function getExtension(fileName) {
80
+ const cleanName = fileName.split("?")[0].split("#")[0];
81
+ const parts = cleanName.toLowerCase().split(".");
82
+ return parts.length > 1 ? parts[parts.length - 1] : "";
83
+ }
84
+ function normalizeHeaderMimeType(headerMimeType) {
85
+ const mimeType = headerMimeType.split(";")[0]?.trim().toLowerCase() || "";
86
+ return GENERIC_MIME_TYPES.has(mimeType) ? "" : mimeType;
87
+ }
88
+ function getHeaderMimeType(response) {
89
+ return response.headers.get("content-type")?.split(";")[0]?.trim() || "";
90
+ }
91
+ function getFileNameFromContentDisposition(contentDisposition) {
92
+ if (!contentDisposition) return null;
93
+ const filenameStarMatch = contentDisposition.match(
94
+ /filename\*\s*=\s*([^;]+)/i
95
+ );
96
+ if (filenameStarMatch?.[1]) {
97
+ const rawValue = stripQuotes(filenameStarMatch[1]);
98
+ const rfc5987Match = rawValue.match(/^[^']*'[^']*'(.+)$/);
99
+ const encodedValue = rfc5987Match?.[1] || rawValue;
100
+ return sanitizeRemoteFileName(tryDecodeURIComponent(encodedValue));
101
+ }
102
+ const filenameMatch = contentDisposition.match(/filename\s*=\s*([^;]+)/i);
103
+ if (filenameMatch?.[1]) {
104
+ return sanitizeRemoteFileName(
105
+ tryDecodeURIComponent(stripQuotes(filenameMatch[1]))
106
+ );
107
+ }
108
+ return null;
109
+ }
110
+ function getRemoteFileName(rawUrl, contentDisposition) {
111
+ const fromDisposition = getFileNameFromContentDisposition(contentDisposition);
112
+ if (fromDisposition) {
113
+ return {
114
+ fileName: fromDisposition,
115
+ source: "content-disposition"
116
+ };
117
+ }
118
+ try {
119
+ const url = new URL(rawUrl);
120
+ const params = url.searchParams;
121
+ const queryKeys = [
122
+ "showname",
123
+ "filename",
124
+ "fileName",
125
+ "name",
126
+ "file",
127
+ "download"
128
+ ];
129
+ let firstQueryCandidate = null;
130
+ for (const key of queryKeys) {
131
+ const value = params.get(key)?.trim();
132
+ if (!value || value.toLowerCase() === "true") {
133
+ continue;
134
+ }
135
+ const candidate = sanitizeRemoteFileName(value);
136
+ if (!firstQueryCandidate) {
137
+ firstQueryCandidate = candidate;
138
+ }
139
+ if (getExtension(candidate)) {
140
+ return {
141
+ fileName: candidate,
142
+ source: "query"
143
+ };
144
+ }
145
+ }
146
+ const pathname = decodeURIComponent(url.pathname);
147
+ const pathnameName = pathname.split("/").filter(Boolean).pop();
148
+ if (pathnameName?.trim()) {
149
+ const candidate = sanitizeRemoteFileName(pathnameName.trim());
150
+ if (getExtension(candidate)) {
151
+ return {
152
+ fileName: candidate,
153
+ source: "pathname"
154
+ };
155
+ }
156
+ }
157
+ if (firstQueryCandidate) {
158
+ return {
159
+ fileName: firstQueryCandidate,
160
+ source: "query"
161
+ };
162
+ }
163
+ if (pathnameName?.trim()) {
164
+ return {
165
+ fileName: sanitizeRemoteFileName(pathnameName.trim()),
166
+ source: "pathname"
167
+ };
168
+ }
169
+ return {
170
+ fileName: "remote-file",
171
+ source: "fallback"
172
+ };
173
+ } catch {
174
+ return {
175
+ fileName: "remote-file",
176
+ source: "fallback"
177
+ };
178
+ }
179
+ }
180
+ function startsWithBytes(bytes, signature) {
181
+ if (bytes.length < signature.length) return false;
182
+ return signature.every((value, index) => bytes[index] === value);
183
+ }
184
+ function readAscii(bytes, start, length) {
185
+ return String.fromCharCode(...bytes.subarray(start, start + length));
186
+ }
187
+ function sniffMagic(buffer) {
188
+ const bytes = new Uint8Array(buffer.slice(0, 32));
189
+ if (readAscii(bytes, 0, 5) === "%PDF-") {
190
+ return {
191
+ ext: "pdf",
192
+ mimeType: "application/pdf"
193
+ };
194
+ }
195
+ if (startsWithBytes(bytes, [80, 75, 3, 4]) || startsWithBytes(bytes, [80, 75, 5, 6]) || startsWithBytes(bytes, [80, 75, 7, 8])) {
196
+ return {
197
+ ext: "zip",
198
+ mimeType: "application/zip"
199
+ };
200
+ }
201
+ if (startsWithBytes(bytes, [
202
+ 137,
203
+ 80,
204
+ 78,
205
+ 71,
206
+ 13,
207
+ 10,
208
+ 26,
209
+ 10
210
+ ])) {
211
+ return {
212
+ ext: "png",
213
+ mimeType: "image/png"
214
+ };
215
+ }
216
+ if (startsWithBytes(bytes, [255, 216, 255])) {
217
+ return {
218
+ ext: "jpg",
219
+ mimeType: "image/jpeg"
220
+ };
221
+ }
222
+ const gifHeader = readAscii(bytes, 0, 6);
223
+ if (gifHeader === "GIF87a" || gifHeader === "GIF89a") {
224
+ return {
225
+ ext: "gif",
226
+ mimeType: "image/gif"
227
+ };
228
+ }
229
+ if (readAscii(bytes, 0, 4) === "RIFF" && readAscii(bytes, 8, 4) === "WEBP") {
230
+ return {
231
+ ext: "webp",
232
+ mimeType: "image/webp"
233
+ };
234
+ }
235
+ if (bytes.length >= 12 && readAscii(bytes, 4, 4) === "ftyp") {
236
+ return {
237
+ ext: "mp4",
238
+ mimeType: "video/mp4"
239
+ };
240
+ }
241
+ if (startsWithBytes(bytes, [
242
+ 208,
243
+ 207,
244
+ 17,
245
+ 224,
246
+ 161,
247
+ 177,
248
+ 26,
249
+ 225
250
+ ])) {
251
+ return {
252
+ ext: "ole",
253
+ mimeType: "application/x-ole-storage"
254
+ };
255
+ }
256
+ return {
257
+ ext: null,
258
+ mimeType: null
259
+ };
260
+ }
261
+ async function sniffZipContainer(buffer) {
262
+ try {
263
+ const { default: JSZip } = await import("jszip");
264
+ const zip = await JSZip.loadAsync(buffer);
265
+ const fileNames = Object.keys(zip.files).map(
266
+ (name) => name.replace(/\\/g, "/").toLowerCase()
267
+ );
268
+ const hasFile = (target) => fileNames.includes(target);
269
+ if (hasFile("word/document.xml")) {
270
+ return {
271
+ ext: "docx",
272
+ mimeType: "application/vnd.openxmlformats-officedocument.wordprocessingml.document"
273
+ };
274
+ }
275
+ if (hasFile("ppt/presentation.xml")) {
276
+ return {
277
+ ext: "pptx",
278
+ mimeType: "application/vnd.openxmlformats-officedocument.presentationml.presentation"
279
+ };
280
+ }
281
+ if (hasFile("xl/workbook.xml")) {
282
+ return {
283
+ ext: "xlsx",
284
+ mimeType: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
285
+ };
286
+ }
287
+ const mimetypeFile = zip.file("mimetype");
288
+ if (mimetypeFile) {
289
+ const mimetype = (await mimetypeFile.async("string")).trim();
290
+ if (mimetype === "application/epub+zip") {
291
+ return {
292
+ ext: "epub",
293
+ mimeType: "application/epub+zip"
294
+ };
295
+ }
296
+ }
297
+ return null;
298
+ } catch {
299
+ return null;
300
+ }
301
+ }
302
+ function resolveRemoteMimeType(input) {
303
+ const ext = getExtension(input.fileName);
304
+ const mimeFromExtension = REMOTE_MIME_BY_EXTENSION[ext];
305
+ const mimeFromHeader = normalizeHeaderMimeType(input.headerMimeType);
306
+ if (input.containerMimeType) {
307
+ return {
308
+ mimeType: input.containerMimeType,
309
+ source: "container"
310
+ };
311
+ }
312
+ if (isStrongMagicMimeType(input.magicMimeType)) {
313
+ return {
314
+ mimeType: input.magicMimeType,
315
+ source: "magic"
316
+ };
317
+ }
318
+ if (mimeFromExtension) {
319
+ return {
320
+ mimeType: mimeFromExtension,
321
+ source: "extension"
322
+ };
323
+ }
324
+ if (mimeFromHeader) {
325
+ return {
326
+ mimeType: mimeFromHeader,
327
+ source: "header"
328
+ };
329
+ }
330
+ if (input.magicMimeType) {
331
+ return {
332
+ mimeType: input.magicMimeType,
333
+ source: "magic"
334
+ };
335
+ }
336
+ return {
337
+ mimeType: "application/octet-stream",
338
+ source: "fallback"
339
+ };
340
+ }
341
+ function getContentLength(response) {
342
+ const value = response.headers.get("content-length");
343
+ if (!value) return null;
344
+ const parsed = Number(value);
345
+ return Number.isFinite(parsed) && parsed >= 0 ? parsed : null;
346
+ }
347
+ async function readResponseAsArrayBufferWithProgress(response, options) {
348
+ const total = getContentLength(response);
349
+ if (!response.body) {
350
+ const buffer = await response.arrayBuffer();
351
+ options.onProgress?.({
352
+ received: buffer.byteLength,
353
+ total,
354
+ percent: total ? buffer.byteLength / total : null
355
+ });
356
+ return buffer;
357
+ }
358
+ const reader = response.body.getReader();
359
+ const chunks = [];
360
+ let received = 0;
361
+ try {
362
+ while (true) {
363
+ if (options.signal?.aborted) {
364
+ try {
365
+ await reader.cancel();
366
+ } catch {
367
+ }
368
+ throw new DOMException("The operation was aborted.", "AbortError");
369
+ }
370
+ const { done, value } = await reader.read();
371
+ if (done) break;
372
+ if (value) {
373
+ chunks.push(value);
374
+ received += value.byteLength;
375
+ options.onProgress?.({
376
+ received,
377
+ total,
378
+ percent: total ? received / total : null
379
+ });
380
+ }
381
+ }
382
+ } finally {
383
+ try {
384
+ reader.releaseLock();
385
+ } catch {
386
+ }
387
+ }
388
+ const merged = new Uint8Array(received);
389
+ let offset = 0;
390
+ for (const chunk of chunks) {
391
+ merged.set(chunk, offset);
392
+ offset += chunk.byteLength;
393
+ }
394
+ return merged.buffer;
395
+ }
396
+ async function processRemoteUrl(rawUrl, options = {}) {
397
+ const trimmedUrl = rawUrl.trim();
398
+ if (!trimmedUrl) {
399
+ throw new RemoteUrlError("INVALID_URL", "Remote URL is empty");
400
+ }
401
+ let parsedUrl;
402
+ try {
403
+ parsedUrl = new URL(trimmedUrl);
404
+ } catch {
405
+ throw new RemoteUrlError("INVALID_URL", "Please enter a valid URL");
406
+ }
407
+ if (!["http:", "https:"].includes(parsedUrl.protocol)) {
408
+ throw new RemoteUrlError(
409
+ "UNSUPPORTED_PROTOCOL",
410
+ "Only http/https URLs are supported",
411
+ parsedUrl.toString()
412
+ );
413
+ }
414
+ let response;
415
+ let buffer;
416
+ try {
417
+ response = await fetch(parsedUrl.toString(), {
418
+ signal: options.signal
419
+ });
420
+ if (!response.ok) {
421
+ throw new RemoteUrlError(
422
+ "HTTP_ERROR",
423
+ `\u8FDC\u7A0B\u6587\u4EF6\u8BF7\u6C42\u5931\u8D25\uFF1AHTTP ${response.status}`,
424
+ parsedUrl.toString()
425
+ );
426
+ }
427
+ const headerMimeType = getHeaderMimeType(response);
428
+ const contentDisposition = response.headers.get("content-disposition");
429
+ const fileNameResult = getRemoteFileName(
430
+ parsedUrl.toString(),
431
+ contentDisposition
432
+ );
433
+ buffer = await readResponseAsArrayBufferWithProgress(response, options);
434
+ const magicResult = sniffMagic(buffer);
435
+ const containerResult = magicResult.ext === "zip" ? await sniffZipContainer(buffer) : null;
436
+ const mimeResult = resolveRemoteMimeType({
437
+ fileName: fileNameResult.fileName,
438
+ headerMimeType,
439
+ magicMimeType: magicResult.mimeType,
440
+ containerMimeType: containerResult?.mimeType ?? null
441
+ });
442
+ const fileType = detectFileType(fileNameResult.fileName, mimeResult.mimeType);
443
+ return {
444
+ id: generateId(),
445
+ name: fileNameResult.fileName,
446
+ size: buffer.byteLength,
447
+ type: mimeResult.mimeType,
448
+ fileType,
449
+ source: {
450
+ kind: "arrayBuffer",
451
+ buffer,
452
+ name: fileNameResult.fileName,
453
+ mimeType: mimeResult.mimeType
454
+ }
455
+ };
456
+ } catch (error) {
457
+ if (error instanceof RemoteUrlError) {
458
+ throw error;
459
+ }
460
+ if (error instanceof DOMException && error.name === "AbortError") {
461
+ throw new RemoteUrlError(
462
+ "ABORTED",
463
+ "Remote file loading cancelled",
464
+ parsedUrl.toString()
465
+ );
466
+ }
467
+ throw new RemoteUrlError(
468
+ "NETWORK_OR_CORS",
469
+ "\u65E0\u6CD5\u52A0\u8F7D\u8FDC\u7A0B\u6587\u4EF6\u3002\u53EF\u80FD\u662F URL \u4E0D\u53EF\u8BBF\u95EE\uFF0C\u6216\u76EE\u6807\u670D\u52A1\u5668\u672A\u5141\u8BB8\u6D4F\u89C8\u5668\u8DE8\u57DF\u8BBF\u95EE\u3002",
470
+ parsedUrl.toString()
471
+ );
472
+ }
473
+ }
474
+ export {
475
+ RemoteUrlError,
476
+ processRemoteUrl
477
+ };
478
+ //# sourceMappingURL=remote-url.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/remote-url.ts"],"sourcesContent":["import { detectFileType, generateId } from \"./utils\";\nimport type { FileInfo } from \"./utils\";\n\nexport type RemoteUrlErrorCode =\n | \"INVALID_URL\"\n | \"UNSUPPORTED_PROTOCOL\"\n | \"NETWORK_OR_CORS\"\n | \"HTTP_ERROR\"\n | \"ABORTED\"\n | \"FILE_TOO_LARGE\";\n\nexport class RemoteUrlError extends Error {\n constructor(\n public code: RemoteUrlErrorCode,\n message: string,\n public url?: string\n ) {\n super(message);\n this.name = \"RemoteUrlError\";\n }\n}\n\nexport interface RemoteLoadProgress {\n received: number;\n total: number | null;\n percent: number | null;\n}\n\nexport interface ProcessRemoteUrlOptions {\n signal?: AbortSignal;\n onProgress?: (progress: RemoteLoadProgress) => void;\n}\n\ntype FileNameSource =\n | \"content-disposition\"\n | \"query\"\n | \"pathname\"\n | \"fallback\";\n\ntype MimeDetectionSource =\n | \"container\"\n | \"magic\"\n | \"extension\"\n | \"header\"\n | \"fallback\";\n\ninterface FileNameResult {\n fileName: string;\n source: FileNameSource;\n}\n\ninterface MimeResult {\n mimeType: string;\n source: MimeDetectionSource;\n}\n\ninterface MagicSniffResult {\n ext: string | null;\n mimeType: string | null;\n}\n\ninterface ContainerSniffResult {\n ext: string;\n mimeType: string;\n}\n\nconst REMOTE_MIME_BY_EXTENSION: Record<string, string> = {\n docx: \"application/vnd.openxmlformats-officedocument.wordprocessingml.document\",\n pptx: \"application/vnd.openxmlformats-officedocument.presentationml.presentation\",\n xlsx: \"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet\",\n\n doc: \"application/msword\",\n ppt: \"application/vnd.ms-powerpoint\",\n xls: \"application/vnd.ms-excel\",\n\n pdf: \"application/pdf\",\n zip: \"application/zip\",\n epub: \"application/epub+zip\",\n\n json: \"application/json\",\n csv: \"text/csv\",\n md: \"text/markdown\",\n mdx: \"text/markdown\",\n html: \"text/html\",\n htm: \"text/html\",\n svg: \"image/svg+xml\",\n\n txt: \"text/plain\",\n log: \"text/plain\",\n\n png: \"image/png\",\n jpg: \"image/jpeg\",\n jpeg: \"image/jpeg\",\n gif: \"image/gif\",\n webp: \"image/webp\",\n bmp: \"image/bmp\",\n ico: \"image/x-icon\",\n avif: \"image/avif\",\n\n mp4: \"video/mp4\",\n webm: \"video/webm\",\n mov: \"video/quicktime\",\n\n mp3: \"audio/mpeg\",\n wav: \"audio/wav\",\n ogg: \"audio/ogg\",\n flac: \"audio/flac\",\n aac: \"audio/aac\",\n m4a: \"audio/mp4\",\n};\n\nconst GENERIC_MIME_TYPES = new Set([\n \"application/octet-stream\",\n \"binary/octet-stream\",\n \"application/x-msdownload\",\n \"application/download\",\n]);\n\nconst WEAK_MAGIC_MIME_TYPES = new Set([\n \"application/zip\",\n \"application/x-ole-storage\",\n \"video/mp4\",\n]);\n\nfunction isStrongMagicMimeType(mimeType: string | null): boolean {\n return Boolean(mimeType && !WEAK_MAGIC_MIME_TYPES.has(mimeType));\n}\n\nfunction sanitizeRemoteFileName(fileName: string): string {\n const cleanName = fileName\n .replace(/[\\\\/:*?\"<>|]/g, \"_\")\n .replace(/\\s+/g, \" \")\n .trim();\n\n return cleanName || \"remote-file\";\n}\n\nfunction tryDecodeURIComponent(value: string): string {\n try {\n return decodeURIComponent(value);\n } catch {\n return value;\n }\n}\n\nfunction stripQuotes(value: string): string {\n const trimmed = value.trim();\n\n if (\n (trimmed.startsWith('\"') && trimmed.endsWith('\"')) ||\n (trimmed.startsWith(\"'\") && trimmed.endsWith(\"'\"))\n ) {\n return trimmed.slice(1, -1);\n }\n\n return trimmed;\n}\n\nfunction getExtension(fileName: string): string {\n const cleanName = fileName.split(\"?\")[0].split(\"#\")[0];\n const parts = cleanName.toLowerCase().split(\".\");\n\n return parts.length > 1 ? parts[parts.length - 1] : \"\";\n}\n\nfunction normalizeHeaderMimeType(headerMimeType: string): string {\n const mimeType = headerMimeType.split(\";\")[0]?.trim().toLowerCase() || \"\";\n\n return GENERIC_MIME_TYPES.has(mimeType) ? \"\" : mimeType;\n}\n\nfunction getHeaderMimeType(response: Response): string {\n return response.headers.get(\"content-type\")?.split(\";\")[0]?.trim() || \"\";\n}\n\nfunction getFileNameFromContentDisposition(\n contentDisposition?: string | null\n): string | null {\n if (!contentDisposition) return null;\n\n const filenameStarMatch = contentDisposition.match(\n /filename\\*\\s*=\\s*([^;]+)/i\n );\n\n if (filenameStarMatch?.[1]) {\n const rawValue = stripQuotes(filenameStarMatch[1]);\n\n const rfc5987Match = rawValue.match(/^[^']*'[^']*'(.+)$/);\n const encodedValue = rfc5987Match?.[1] || rawValue;\n\n return sanitizeRemoteFileName(tryDecodeURIComponent(encodedValue));\n }\n\n const filenameMatch = contentDisposition.match(/filename\\s*=\\s*([^;]+)/i);\n\n if (filenameMatch?.[1]) {\n return sanitizeRemoteFileName(\n tryDecodeURIComponent(stripQuotes(filenameMatch[1]))\n );\n }\n\n return null;\n}\n\nfunction getRemoteFileName(\n rawUrl: string,\n contentDisposition?: string | null\n): FileNameResult {\n const fromDisposition = getFileNameFromContentDisposition(contentDisposition);\n\n if (fromDisposition) {\n return {\n fileName: fromDisposition,\n source: \"content-disposition\",\n };\n }\n\n try {\n const url = new URL(rawUrl);\n const params = url.searchParams;\n\n const queryKeys = [\n \"showname\",\n \"filename\",\n \"fileName\",\n \"name\",\n \"file\",\n \"download\",\n ];\n\n let firstQueryCandidate: string | null = null;\n\n for (const key of queryKeys) {\n const value = params.get(key)?.trim();\n\n if (!value || value.toLowerCase() === \"true\") {\n continue;\n }\n\n const candidate = sanitizeRemoteFileName(value);\n\n if (!firstQueryCandidate) {\n firstQueryCandidate = candidate;\n }\n\n if (getExtension(candidate)) {\n return {\n fileName: candidate,\n source: \"query\",\n };\n }\n }\n\n const pathname = decodeURIComponent(url.pathname);\n const pathnameName = pathname.split(\"/\").filter(Boolean).pop();\n\n if (pathnameName?.trim()) {\n const candidate = sanitizeRemoteFileName(pathnameName.trim());\n\n if (getExtension(candidate)) {\n return {\n fileName: candidate,\n source: \"pathname\",\n };\n }\n }\n\n if (firstQueryCandidate) {\n return {\n fileName: firstQueryCandidate,\n source: \"query\",\n };\n }\n\n if (pathnameName?.trim()) {\n return {\n fileName: sanitizeRemoteFileName(pathnameName.trim()),\n source: \"pathname\",\n };\n }\n\n return {\n fileName: \"remote-file\",\n source: \"fallback\",\n };\n } catch {\n return {\n fileName: \"remote-file\",\n source: \"fallback\",\n };\n }\n}\n\nfunction startsWithBytes(bytes: Uint8Array, signature: number[]): boolean {\n if (bytes.length < signature.length) return false;\n\n return signature.every((value, index) => bytes[index] === value);\n}\n\nfunction readAscii(bytes: Uint8Array, start: number, length: number): string {\n return String.fromCharCode(...bytes.subarray(start, start + length));\n}\n\nfunction sniffMagic(buffer: ArrayBuffer): MagicSniffResult {\n const bytes = new Uint8Array(buffer.slice(0, 32));\n\n if (readAscii(bytes, 0, 5) === \"%PDF-\") {\n return {\n ext: \"pdf\",\n mimeType: \"application/pdf\",\n };\n }\n\n if (\n startsWithBytes(bytes, [0x50, 0x4b, 0x03, 0x04]) ||\n startsWithBytes(bytes, [0x50, 0x4b, 0x05, 0x06]) ||\n startsWithBytes(bytes, [0x50, 0x4b, 0x07, 0x08])\n ) {\n return {\n ext: \"zip\",\n mimeType: \"application/zip\",\n };\n }\n\n if (\n startsWithBytes(bytes, [\n 0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a,\n ])\n ) {\n return {\n ext: \"png\",\n mimeType: \"image/png\",\n };\n }\n\n if (startsWithBytes(bytes, [0xff, 0xd8, 0xff])) {\n return {\n ext: \"jpg\",\n mimeType: \"image/jpeg\",\n };\n }\n\n const gifHeader = readAscii(bytes, 0, 6);\n\n if (gifHeader === \"GIF87a\" || gifHeader === \"GIF89a\") {\n return {\n ext: \"gif\",\n mimeType: \"image/gif\",\n };\n }\n\n if (readAscii(bytes, 0, 4) === \"RIFF\" && readAscii(bytes, 8, 4) === \"WEBP\") {\n return {\n ext: \"webp\",\n mimeType: \"image/webp\",\n };\n }\n\n if (bytes.length >= 12 && readAscii(bytes, 4, 4) === \"ftyp\") {\n return {\n ext: \"mp4\",\n mimeType: \"video/mp4\",\n };\n }\n\n if (\n startsWithBytes(bytes, [\n 0xd0, 0xcf, 0x11, 0xe0, 0xa1, 0xb1, 0x1a, 0xe1,\n ])\n ) {\n return {\n ext: \"ole\",\n mimeType: \"application/x-ole-storage\",\n };\n }\n\n return {\n ext: null,\n mimeType: null,\n };\n}\n\nasync function sniffZipContainer(\n buffer: ArrayBuffer\n): Promise<ContainerSniffResult | null> {\n try {\n const { default: JSZip } = await import(\"jszip\");\n const zip = await JSZip.loadAsync(buffer);\n\n const fileNames = Object.keys(zip.files).map((name) =>\n name.replace(/\\\\/g, \"/\").toLowerCase()\n );\n\n const hasFile = (target: string) => fileNames.includes(target);\n\n if (hasFile(\"word/document.xml\")) {\n return {\n ext: \"docx\",\n mimeType:\n \"application/vnd.openxmlformats-officedocument.wordprocessingml.document\",\n };\n }\n\n if (hasFile(\"ppt/presentation.xml\")) {\n return {\n ext: \"pptx\",\n mimeType:\n \"application/vnd.openxmlformats-officedocument.presentationml.presentation\",\n };\n }\n\n if (hasFile(\"xl/workbook.xml\")) {\n return {\n ext: \"xlsx\",\n mimeType:\n \"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet\",\n };\n }\n\n const mimetypeFile = zip.file(\"mimetype\");\n\n if (mimetypeFile) {\n const mimetype = (await mimetypeFile.async(\"string\")).trim();\n\n if (mimetype === \"application/epub+zip\") {\n return {\n ext: \"epub\",\n mimeType: \"application/epub+zip\",\n };\n }\n }\n\n return null;\n } catch {\n return null;\n }\n}\n\nfunction resolveRemoteMimeType(input: {\n fileName: string;\n headerMimeType: string;\n magicMimeType: string | null;\n containerMimeType: string | null;\n}): MimeResult {\n const ext = getExtension(input.fileName);\n const mimeFromExtension = REMOTE_MIME_BY_EXTENSION[ext];\n const mimeFromHeader = normalizeHeaderMimeType(input.headerMimeType);\n\n // 1. ZIP internal container (docx/pptx/xlsx/epub)\n if (input.containerMimeType) {\n return {\n mimeType: input.containerMimeType,\n source: \"container\",\n };\n }\n\n // 2. Strong magic (PDF/PNG/JPG/GIF/WEBP)\n if (isStrongMagicMimeType(input.magicMimeType)) {\n return {\n mimeType: input.magicMimeType!,\n source: \"magic\",\n };\n }\n\n // 3. Explicit extension wins over weak magic (zip/ole/ftyp)\n if (mimeFromExtension) {\n return {\n mimeType: mimeFromExtension,\n source: \"extension\",\n };\n }\n\n // 4. Content-Type header\n if (mimeFromHeader) {\n return {\n mimeType: mimeFromHeader,\n source: \"header\",\n };\n }\n\n // 5. Weak magic as last resort before fallback\n if (input.magicMimeType) {\n return {\n mimeType: input.magicMimeType,\n source: \"magic\",\n };\n }\n\n return {\n mimeType: \"application/octet-stream\",\n source: \"fallback\",\n };\n}\n\nfunction getContentLength(response: Response): number | null {\n const value = response.headers.get(\"content-length\");\n if (!value) return null;\n\n const parsed = Number(value);\n return Number.isFinite(parsed) && parsed >= 0 ? parsed : null;\n}\n\nasync function readResponseAsArrayBufferWithProgress(\n response: Response,\n options: ProcessRemoteUrlOptions\n): Promise<ArrayBuffer> {\n const total = getContentLength(response);\n\n if (!response.body) {\n const buffer = await response.arrayBuffer();\n options.onProgress?.({\n received: buffer.byteLength,\n total,\n percent: total ? buffer.byteLength / total : null,\n });\n return buffer;\n }\n\n const reader = response.body.getReader();\n const chunks: Uint8Array[] = [];\n let received = 0;\n\n try {\n while (true) {\n if (options.signal?.aborted) {\n try {\n await reader.cancel();\n } catch {\n // ignore\n }\n throw new DOMException(\"The operation was aborted.\", \"AbortError\");\n }\n\n const { done, value } = await reader.read();\n\n if (done) break;\n\n if (value) {\n chunks.push(value);\n received += value.byteLength;\n\n options.onProgress?.({\n received,\n total,\n percent: total ? received / total : null,\n });\n }\n }\n } finally {\n try {\n reader.releaseLock();\n } catch {\n // ignore\n }\n }\n\n const merged = new Uint8Array(received);\n let offset = 0;\n\n for (const chunk of chunks) {\n merged.set(chunk, offset);\n offset += chunk.byteLength;\n }\n\n return merged.buffer;\n}\n\nexport async function processRemoteUrl(\n rawUrl: string,\n options: ProcessRemoteUrlOptions = {}\n): Promise<FileInfo> {\n const trimmedUrl = rawUrl.trim();\n\n if (!trimmedUrl) {\n throw new RemoteUrlError(\"INVALID_URL\", \"Remote URL is empty\");\n }\n\n let parsedUrl: URL;\n\n try {\n parsedUrl = new URL(trimmedUrl);\n } catch {\n throw new RemoteUrlError(\"INVALID_URL\", \"Please enter a valid URL\");\n }\n\n if (![\"http:\", \"https:\"].includes(parsedUrl.protocol)) {\n throw new RemoteUrlError(\n \"UNSUPPORTED_PROTOCOL\",\n \"Only http/https URLs are supported\",\n parsedUrl.toString()\n );\n }\n\n let response: Response;\n let buffer: ArrayBuffer;\n\n try {\n response = await fetch(parsedUrl.toString(), {\n signal: options.signal,\n });\n\n if (!response.ok) {\n throw new RemoteUrlError(\n \"HTTP_ERROR\",\n `远程文件请求失败:HTTP ${response.status}`,\n parsedUrl.toString()\n );\n }\n\n const headerMimeType = getHeaderMimeType(response);\n const contentDisposition = response.headers.get(\"content-disposition\");\n\n const fileNameResult = getRemoteFileName(\n parsedUrl.toString(),\n contentDisposition\n );\n\n buffer = await readResponseAsArrayBufferWithProgress(response, options);\n\n const magicResult = sniffMagic(buffer);\n\n const containerResult =\n magicResult.ext === \"zip\" ? await sniffZipContainer(buffer) : null;\n\n const mimeResult = resolveRemoteMimeType({\n fileName: fileNameResult.fileName,\n headerMimeType,\n magicMimeType: magicResult.mimeType,\n containerMimeType: containerResult?.mimeType ?? null,\n });\n\n const fileType = detectFileType(fileNameResult.fileName, mimeResult.mimeType);\n\n return {\n id: generateId(),\n name: fileNameResult.fileName,\n size: buffer.byteLength,\n type: mimeResult.mimeType,\n fileType,\n source: {\n kind: \"arrayBuffer\",\n buffer,\n name: fileNameResult.fileName,\n mimeType: mimeResult.mimeType,\n },\n };\n } catch (error) {\n if (error instanceof RemoteUrlError) {\n throw error;\n }\n\n if (error instanceof DOMException && error.name === \"AbortError\") {\n throw new RemoteUrlError(\n \"ABORTED\",\n \"Remote file loading cancelled\",\n parsedUrl.toString()\n );\n }\n\n throw new RemoteUrlError(\n \"NETWORK_OR_CORS\",\n \"无法加载远程文件。可能是 URL 不可访问,或目标服务器未允许浏览器跨域访问。\",\n parsedUrl.toString()\n );\n }\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Dynamically load rtf.js bundle scripts (WMFJS, EMFJS, RTFJS).
3
+ *
4
+ * The npm module `import 'rtf.js'` does not work correctly in
5
+ * Next.js/Turbopack. Instead, the bundle.js files are copied to
6
+ * `public/vendor/rtfjs/` and loaded via static <script> tags.
7
+ *
8
+ * Each bundle exposes its respective global on `window`:
9
+ * WMFJS → window.WMFJS
10
+ * EMFJS → window.EMFJS
11
+ * RTFJS → window.RTFJS
12
+ *
13
+ * Bundles are loaded only once; subsequent calls return a cached promise.
14
+ */
15
+ /** Minimal types for rtf.js globals exposed on window. */
16
+ interface RtfJsDocument {
17
+ render(): Promise<HTMLElement[]>;
18
+ setConfig(config: Record<string, unknown>): void;
19
+ }
20
+ interface RtfJsLibrary {
21
+ Document: new (buffer: ArrayBuffer | Uint8Array, options?: Record<string, unknown>) => RtfJsDocument;
22
+ loggingEnabled?: (enabled: boolean) => void;
23
+ }
24
+ type RtfJsGlobals = {
25
+ RTFJS: RtfJsLibrary;
26
+ WMFJS: {
27
+ loggingEnabled?: (enabled: boolean) => void;
28
+ };
29
+ EMFJS: {
30
+ loggingEnabled?: (enabled: boolean) => void;
31
+ };
32
+ };
33
+ declare global {
34
+ interface Window {
35
+ RTFJS?: RtfJsGlobals["RTFJS"];
36
+ WMFJS?: RtfJsGlobals["WMFJS"];
37
+ EMFJS?: RtfJsGlobals["EMFJS"];
38
+ }
39
+ }
40
+ declare function loadRtfJsGlobals(): Promise<RtfJsGlobals>;
41
+
42
+ export { loadRtfJsGlobals };
@@ -0,0 +1,71 @@
1
+ const BUNDLE_FILES = [
2
+ "vendor/rtfjs/WMFJS.bundle.min.js",
3
+ "vendor/rtfjs/EMFJS.bundle.min.js",
4
+ "vendor/rtfjs/RTFJS.bundle.min.js"
5
+ ];
6
+ let loadingPromise = null;
7
+ function loadRtfJsGlobals() {
8
+ if (loadingPromise) {
9
+ return loadingPromise;
10
+ }
11
+ loadingPromise = load();
12
+ return loadingPromise;
13
+ }
14
+ import { resolveAssetPath } from "../core/config";
15
+ function resolvePath(path) {
16
+ return resolveAssetPath(path);
17
+ }
18
+ async function load() {
19
+ for (const file of BUNDLE_FILES) {
20
+ await loadScript(resolvePath(file));
21
+ }
22
+ if (!window.RTFJS) {
23
+ throw new Error(
24
+ "RTFJS global is not available after loading RTFJS.bundle.min.js"
25
+ );
26
+ }
27
+ window.RTFJS.loggingEnabled?.(false);
28
+ window.WMFJS?.loggingEnabled?.(false);
29
+ window.EMFJS?.loggingEnabled?.(false);
30
+ return {
31
+ RTFJS: window.RTFJS,
32
+ WMFJS: window.WMFJS,
33
+ EMFJS: window.EMFJS
34
+ };
35
+ }
36
+ function loadScript(src) {
37
+ return new Promise((resolve, reject) => {
38
+ const existed = document.querySelector(
39
+ `script[data-rtfjs-src="${src}"]`
40
+ );
41
+ if (existed) {
42
+ if (existed.dataset.loaded === "true") {
43
+ resolve();
44
+ return;
45
+ }
46
+ existed.addEventListener("load", () => resolve(), { once: true });
47
+ existed.addEventListener(
48
+ "error",
49
+ () => reject(new Error(`Failed to load script: ${src}`)),
50
+ { once: true }
51
+ );
52
+ return;
53
+ }
54
+ const script = document.createElement("script");
55
+ script.src = src;
56
+ script.async = false;
57
+ script.dataset.rtfjsSrc = src;
58
+ script.onload = () => {
59
+ script.dataset.loaded = "true";
60
+ resolve();
61
+ };
62
+ script.onerror = () => {
63
+ reject(new Error(`Failed to load script: ${src}`));
64
+ };
65
+ document.head.appendChild(script);
66
+ });
67
+ }
68
+ export {
69
+ loadRtfJsGlobals
70
+ };
71
+ //# sourceMappingURL=load-rtfjs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/rtf/load-rtfjs.ts"],"sourcesContent":["/**\n * Dynamically load rtf.js bundle scripts (WMFJS, EMFJS, RTFJS).\n *\n * The npm module `import 'rtf.js'` does not work correctly in\n * Next.js/Turbopack. Instead, the bundle.js files are copied to\n * `public/vendor/rtfjs/` and loaded via static <script> tags.\n *\n * Each bundle exposes its respective global on `window`:\n * WMFJS → window.WMFJS\n * EMFJS → window.EMFJS\n * RTFJS → window.RTFJS\n *\n * Bundles are loaded only once; subsequent calls return a cached promise.\n */\n\n/** Minimal types for rtf.js globals exposed on window. */\ninterface RtfJsDocument {\n render(): Promise<HTMLElement[]>;\n setConfig(config: Record<string, unknown>): void;\n}\n\ninterface RtfJsLibrary {\n Document: new (\n buffer: ArrayBuffer | Uint8Array,\n options?: Record<string, unknown>,\n ) => RtfJsDocument;\n loggingEnabled?: (enabled: boolean) => void;\n}\n\ntype RtfJsGlobals = {\n RTFJS: RtfJsLibrary;\n WMFJS: { loggingEnabled?: (enabled: boolean) => void };\n EMFJS: { loggingEnabled?: (enabled: boolean) => void };\n};\n\ndeclare global {\n interface Window {\n RTFJS?: RtfJsGlobals[\"RTFJS\"];\n WMFJS?: RtfJsGlobals[\"WMFJS\"];\n EMFJS?: RtfJsGlobals[\"EMFJS\"];\n }\n}\n\nconst BUNDLE_FILES = [\n \"vendor/rtfjs/WMFJS.bundle.min.js\",\n \"vendor/rtfjs/EMFJS.bundle.min.js\",\n \"vendor/rtfjs/RTFJS.bundle.min.js\",\n] as const;\n\nlet loadingPromise: Promise<RtfJsGlobals> | null = null;\n\nexport function loadRtfJsGlobals(): Promise<RtfJsGlobals> {\n if (loadingPromise) {\n return loadingPromise;\n }\n\n loadingPromise = load();\n\n return loadingPromise;\n}\n\n/**\n * Resolve a public path against the configured asset base path.\n */\nimport { resolveAssetPath } from \"../core/config\";\nfunction resolvePath(path: string): string {\n return resolveAssetPath(path);\n}\n\nasync function load(): Promise<RtfJsGlobals> {\n for (const file of BUNDLE_FILES) {\n await loadScript(resolvePath(file));\n }\n\n if (!window.RTFJS) {\n throw new Error(\n \"RTFJS global is not available after loading RTFJS.bundle.min.js\",\n );\n }\n\n // Disable verbose console logging from the library\n window.RTFJS.loggingEnabled?.(false);\n window.WMFJS?.loggingEnabled?.(false);\n window.EMFJS?.loggingEnabled?.(false);\n\n return {\n RTFJS: window.RTFJS,\n WMFJS: window.WMFJS!,\n EMFJS: window.EMFJS!,\n };\n}\n\nfunction loadScript(src: string): Promise<void> {\n return new Promise((resolve, reject) => {\n // Check if already loaded or loading\n const existed = document.querySelector<HTMLScriptElement>(\n `script[data-rtfjs-src=\"${src}\"]`,\n );\n\n if (existed) {\n if (existed.dataset.loaded === \"true\") {\n resolve();\n return;\n }\n\n existed.addEventListener(\"load\", () => resolve(), { once: true });\n existed.addEventListener(\n \"error\",\n () => reject(new Error(`Failed to load script: ${src}`)),\n { once: true },\n );\n return;\n }\n\n const script = document.createElement(\"script\");\n script.src = src;\n script.async = false;\n script.dataset.rtfjsSrc = src;\n\n script.onload = () => {\n script.dataset.loaded = \"true\";\n resolve();\n };\n\n script.onerror = () => {\n reject(new Error(`Failed to load script: ${src}`));\n };\n\n document.head.appendChild(script);\n });\n}\n"],"mappings":"AA2CA,MAAM,eAAe;AAAA,EACnB;AAAA,EACA;AAAA,EACA;AACF;AAEA,IAAI,iBAA+C;AAE5C,SAAS,mBAA0C;AACxD,MAAI,gBAAgB;AAClB,WAAO;AAAA,EACT;AAEA,mBAAiB,KAAK;AAEtB,SAAO;AACT;AAKA,SAAS,wBAAwB;AACjC,SAAS,YAAY,MAAsB;AACzC,SAAO,iBAAiB,IAAI;AAC9B;AAEA,eAAe,OAA8B;AAC3C,aAAW,QAAQ,cAAc;AAC/B,UAAM,WAAW,YAAY,IAAI,CAAC;AAAA,EACpC;AAEA,MAAI,CAAC,OAAO,OAAO;AACjB,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EACF;AAGA,SAAO,MAAM,iBAAiB,KAAK;AACnC,SAAO,OAAO,iBAAiB,KAAK;AACpC,SAAO,OAAO,iBAAiB,KAAK;AAEpC,SAAO;AAAA,IACL,OAAO,OAAO;AAAA,IACd,OAAO,OAAO;AAAA,IACd,OAAO,OAAO;AAAA,EAChB;AACF;AAEA,SAAS,WAAW,KAA4B;AAC9C,SAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;AAEtC,UAAM,UAAU,SAAS;AAAA,MACvB,0BAA0B,GAAG;AAAA,IAC/B;AAEA,QAAI,SAAS;AACX,UAAI,QAAQ,QAAQ,WAAW,QAAQ;AACrC,gBAAQ;AACR;AAAA,MACF;AAEA,cAAQ,iBAAiB,QAAQ,MAAM,QAAQ,GAAG,EAAE,MAAM,KAAK,CAAC;AAChE,cAAQ;AAAA,QACN;AAAA,QACA,MAAM,OAAO,IAAI,MAAM,0BAA0B,GAAG,EAAE,CAAC;AAAA,QACvD,EAAE,MAAM,KAAK;AAAA,MACf;AACA;AAAA,IACF;AAEA,UAAM,SAAS,SAAS,cAAc,QAAQ;AAC9C,WAAO,MAAM;AACb,WAAO,QAAQ;AACf,WAAO,QAAQ,WAAW;AAE1B,WAAO,SAAS,MAAM;AACpB,aAAO,QAAQ,SAAS;AACxB,cAAQ;AAAA,IACV;AAEA,WAAO,UAAU,MAAM;AACrB,aAAO,IAAI,MAAM,0BAA0B,GAAG,EAAE,CAAC;AAAA,IACnD;AAEA,aAAS,KAAK,YAAY,MAAM;AAAA,EAClC,CAAC;AACH;","names":[]}