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