1(function () {
2
3    /**
4     * Handle pasting of files
5     *
6     * @param {ClipboardEvent} e
7     */
8    function handlePaste(e) {
9        if (!document.getElementById('wiki__text')) return; // only when editing
10
11        const items = (e.clipboardData || e.originalEvent.clipboardData).items;
12
13        // When running prosemirror and pasting into its edit area, check for HTML paste first
14        if (
15            typeof window.proseMirrorIsActive !== 'undefined'
16            && window.proseMirrorIsActive === true
17            && document.activeElement.tagName === 'DIV'
18            && document.activeElement.classList.contains('ProseMirror-focused')
19        ) {
20            for (let index in items) {
21                const item = items[index];
22                if (item.kind === 'string' && item.type === 'text/html') {
23                    e.preventDefault();
24                    e.stopPropagation();
25
26                    item.getAsString(async html => {
27                            html = await processHTML(html);
28                            const pm = window.Prosemirror.view;
29                            const parser = window.Prosemirror.classes.DOMParser.fromSchema(pm.state.schema);
30                            const nodes = parser.parse(html);
31                            pm.dispatch(pm.state.tr.replaceSelectionWith(nodes));
32                        }
33                    );
34
35                    return; // we found an HTML item, no need to continue
36                }
37            }
38        }
39
40        // if we're still here, handle files
41        for (let index in items) {
42            const item = items[index];
43
44            if (item.kind === 'file') {
45                const reader = new FileReader();
46                reader.onload = event => {
47                    uploadData(event.target.result);
48                };
49                reader.readAsDataURL(item.getAsFile());
50
51                // we had at least one file, prevent default
52                e.preventDefault();
53                e.stopPropagation();
54            }
55        }
56    }
57
58    /**
59     * Creates and shows the progress dialog
60     *
61     * @returns {HTMLDivElement}
62     */
63    function progressDialog() {
64        // create dialog
65        const offset = document.querySelectorAll('.plugin_imagepaste').length * 3;
66        const box = document.createElement('div');
67        box.className = 'plugin_imagepaste';
68        box.innerText = LANG.plugins.imgpaste.inprogress;
69        box.style.position = 'fixed';
70        box.style.top = offset + 'em';
71        box.style.left = '1em';
72        document.querySelector('.dokuwiki').append(box);
73        return box;
74    }
75
76    /**
77     * Processes the given HTML and downloads all images
78     *
79     * @param html
80     * @returns {Promise<HTMLDivElement>}
81     */
82    async function processHTML(html) {
83        const box = progressDialog();
84
85        const div = document.createElement('div');
86        div.innerHTML = html;
87        const imgs = Array.from(div.querySelectorAll('img'));
88        await Promise.all(imgs.map(async img => {
89            if (img.src.startsWith(DOKU_BASE)) return; // skip local images
90            if (!img.src.match(/^(https?:\/\/|data:)/i)) return; // we only handle http(s) and data URLs
91
92            try {
93                let result;
94                if (img.src.startsWith('data:')) {
95                    result = await uploadDataURL(img.src);
96                } else {
97                    result = await downloadData(img.src);
98                }
99
100                img.src = result.url;
101                img.className = 'media';
102                img.dataset.relid = getRelativeID(result.id);
103            } catch (e) {
104                console.error(e);
105            }
106        }));
107
108        box.remove();
109        return div;
110    }
111
112    /**
113     * Tell the backend to download the given URL and return the new ID
114     *
115     * @param {string} imgUrl
116     * @returns {Promise<object>} The JSON response
117     */
118    async function downloadData(imgUrl) {
119        const formData = new FormData();
120        formData.append('call', 'plugin_imgpaste');
121        formData.append('url', imgUrl);
122        formData.append('id', JSINFO.id);
123
124        const response = await fetch(
125            DOKU_BASE + 'lib/exe/ajax.php',
126            {
127                method: 'POST',
128                body: formData
129            }
130        );
131
132        if (!response.ok) {
133            throw new Error(response.statusText);
134        }
135
136        return await response.json();
137    }
138
139    /**
140     * Tell the backend to create a file from the given dataURL and return the new ID
141     *
142     * @param {string} dataURL
143     * @returns {Promise<object>} The JSON response
144     */
145    async function uploadDataURL(dataURL) {
146        const formData = new FormData();
147        formData.append('call', 'plugin_imgpaste');
148        formData.append('data', dataURL);
149        formData.append('id', JSINFO.id);
150
151        const response = await fetch(
152            DOKU_BASE + 'lib/exe/ajax.php',
153            {
154                method: 'POST',
155                body: formData
156            }
157        );
158
159        if (!response.ok) {
160            throw new Error(response.statusText);
161        }
162
163        return await response.json();
164    }
165
166    /**
167     * Uploads the given dataURL to the server and displays a progress dialog, inserting the syntax on success
168     *
169     * @param {string} dataURL
170     */
171    async function uploadData(dataURL) {
172        const box = progressDialog();
173
174        try {
175            const data = await uploadDataURL(dataURL);
176            box.classList.remove('info');
177            box.classList.add('success');
178            box.innerText = data.message;
179            setTimeout(() => {
180                box.remove();
181            }, 1000);
182            insertSyntax(data.id);
183        } catch (e) {
184            box.classList.remove('info');
185            box.classList.add('error');
186            box.innerText = e.message;
187            setTimeout(() => {
188                box.remove();
189            }, 1000);
190        }
191    }
192
193    /**
194     * Create a link ID for the given ID, preferrably relative to the current page
195     *
196     * @param {string} id
197     * @returns {string}
198     */
199    function getRelativeID(id) {
200        // TODO remove the "if" check after LinkWizard.createRelativeID() is available in stable (after Kaos)
201        if (typeof LinkWizard !== 'undefined' && typeof LinkWizard.createRelativeID === 'function') {
202            id = LinkWizard.createRelativeID(JSINFO.id, id);
203        } else {
204            id = ':' + id;
205        }
206        return id;
207    }
208
209    /**
210     * Inserts the given ID into the current editor
211     *
212     * @todo add support for other editors like CKEditor
213     * @param {string} id The newly uploaded file ID
214     */
215    function insertSyntax(id) {
216        id = getRelativeID(id);
217
218        if (typeof window.proseMirrorIsActive !== 'undefined' && window.proseMirrorIsActive === true) {
219            const pm = window.Prosemirror.view;
220            const imageNode = pm.state.schema.nodes.image.create({id: id});
221            pm.dispatch(pm.state.tr.replaceSelectionWith(imageNode));
222        } else {
223            insertAtCarret('wiki__text', '{{' + id + '}}');
224        }
225    }
226
227    // main
228    window.addEventListener('paste', handlePaste, true);
229
230})();
231