1 | const defaultOpts = {name: "files[]"};
|
2 |
|
3 | export default function Uppie() {
|
4 | return (node, opts, cb) => {
|
5 | if (typeof opts === "function") {
|
6 | cb = opts;
|
7 | opts = defaultOpts;
|
8 | } else {
|
9 | if (!opts) opts = {};
|
10 | if (!opts.name) opts.name = defaultOpts.name;
|
11 | }
|
12 |
|
13 | if (node instanceof NodeList) {
|
14 | for (const n of [].slice.call(node)) {
|
15 | watch(n, opts, cb);
|
16 | }
|
17 | } else {
|
18 | watch(node, opts, cb);
|
19 | }
|
20 | };
|
21 | }
|
22 |
|
23 | function watch(node, opts, cb) {
|
24 | if (node.tagName?.toLowerCase() === "input" && node.type === "file") {
|
25 | node.addEventListener("change", e => {
|
26 | const t = e.target;
|
27 | if (t?.files?.length) {
|
28 | arrayApi(t, opts, cb.bind(null, e));
|
29 | } else {
|
30 | cb(e);
|
31 | }
|
32 | });
|
33 | } else {
|
34 | const stop = e => e.preventDefault();
|
35 | node.addEventListener("dragover", stop);
|
36 | node.addEventListener("dragenter", stop);
|
37 | node.addEventListener("drop", (e) => {
|
38 | e.preventDefault();
|
39 | const dt = e.dataTransfer;
|
40 | if (dt.items?.[0]?.webkitGetAsEntry()) {
|
41 | entriesApi(dt.items, opts, cb.bind(null, e));
|
42 | } else if (dt.files) {
|
43 | arrayApi(dt, opts, cb.bind(null, e));
|
44 | } else cb(e);
|
45 | });
|
46 | }
|
47 | }
|
48 |
|
49 |
|
50 | function arrayApi(input, opts, cb) {
|
51 | const fd = new FormData();
|
52 | const files = [];
|
53 |
|
54 | for (const file of [].slice.call(input.files)) {
|
55 | fd.append(opts.name, file, file.webkitRelativePath || file.name);
|
56 | files.push(file.webkitRelativePath || file.name);
|
57 | }
|
58 | cb(fd, files);
|
59 | }
|
60 |
|
61 | function readEntries(entry, reader, oldEntries, cb) {
|
62 | const dirReader = reader || entry.createReader();
|
63 |
|
64 | dirReader.readEntries((entries) => {
|
65 | const newEntries = oldEntries ? oldEntries.concat(entries) : entries;
|
66 | if (entries.length) {
|
67 | setTimeout(readEntries.bind(null, entry, dirReader, newEntries, cb), 0);
|
68 | } else {
|
69 | cb(newEntries);
|
70 | }
|
71 | });
|
72 | }
|
73 |
|
74 |
|
75 | function entriesApi(items, opts, cb) {
|
76 | const fd = new FormData(), files = [], rootPromises = [];
|
77 |
|
78 | function readDirectory(entry, path, resolve) {
|
79 | if (!path) path = entry.name;
|
80 | readEntries(entry, 0, 0, (entries) => {
|
81 | const promises = [];
|
82 | for (const entry of entries) {
|
83 | promises.push(new Promise((resolve) => {
|
84 | if (entry.isFile) {
|
85 | entry.file((file) => {
|
86 | const p = `${path}/${file.name}`;
|
87 | fd.append(opts.name, file, p);
|
88 | files.push(p);
|
89 | resolve();
|
90 | }, resolve.bind());
|
91 | } else readDirectory(entry, `${path}/${entry.name}`, resolve);
|
92 | }));
|
93 | }
|
94 | Promise.all(promises).then(resolve.bind());
|
95 | });
|
96 | }
|
97 |
|
98 | for (let entry of [].slice.call(items)) {
|
99 | entry = entry.webkitGetAsEntry();
|
100 | if (entry) {
|
101 | rootPromises.push(new Promise((resolve) => {
|
102 | if (entry.isFile) {
|
103 | entry.file((file) => {
|
104 | fd.append(opts.name, file, file.name);
|
105 | files.push(file.name);
|
106 | resolve();
|
107 | }, resolve.bind());
|
108 | } else if (entry.isDirectory) {
|
109 | readDirectory(entry, null, resolve);
|
110 | }
|
111 | }));
|
112 | }
|
113 | }
|
114 | Promise.all(rootPromises).then(cb.bind(null, fd, files));
|
115 | }
|