xref: /plugin/imgpaste/script.js (revision 41207488a372564c760faf71b06fb7271e210e42)
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                img.dataset.relid = getRelativeID(result.id);
93            } catch (e) {
94                console.error(e);
95            }
96        }));
97
98        box.remove();
99        return div;
100    }
101
102    /**
103     * Tell the backend to download the given URL and return the new ID
104     *
105     * @param {string} imgUrl
106     * @returns {Promise<object>} The JSON response
107     */
108    async function downloadData(imgUrl) {
109        const formData = new FormData();
110        formData.append('call', 'plugin_imgpaste');
111        formData.append('url', imgUrl);
112        formData.append('id', JSINFO.id);
113
114        const response = await fetch(
115            DOKU_BASE + 'lib/exe/ajax.php',
116            {
117                method: 'POST',
118                body: formData
119            }
120        );
121
122        if (!response.ok) {
123            throw new Error(response.statusText);
124        }
125
126        return await response.json();
127    }
128
129    /**
130     * Uploads the given dataURL to the server and displays a progress dialog
131     *
132     * @param {string} dataURL
133     */
134    function uploadData(dataURL) {
135        const box = progressDialog();
136
137        // upload via AJAX
138        jQuery.ajax({
139            url: DOKU_BASE + 'lib/exe/ajax.php',
140            type: 'POST',
141            data: {
142                call: 'plugin_imgpaste',
143                data: dataURL,
144                id: JSINFO.id
145            },
146
147            // insert syntax and close dialog
148            success: function (data) {
149                box.classList.remove('info');
150                box.classList.add('success');
151                box.innerText = data.message;
152                setTimeout(() => {
153                    box.remove();
154                }, 1000);
155                insertSyntax(data.id);
156            },
157
158            // display error and close dialog
159            error: function (xhr, status, error) {
160                box.classList.remove('info');
161                box.classList.add('error');
162                box.innerText = error;
163                setTimeout(() => {
164                    box.remove();
165                }, 1000);
166            }
167        });
168    }
169
170    /**
171     * Create a link ID for the given ID, preferrably relative to the current page
172     *
173     * @param {string} id
174     * @returns {string}
175     */
176    function getRelativeID(id) {
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        return id;
184    }
185
186    /**
187     * Inserts the given ID into the current editor
188     *
189     * @todo add support for other editors like CKEditor
190     * @param {string} id The newly uploaded file ID
191     */
192    function insertSyntax(id) {
193        id = getRelativeID(id);
194
195        if (typeof window.proseMirrorIsActive !== 'undefined' && window.proseMirrorIsActive === true) {
196            const pm = window.Prosemirror.view;
197            const imageNode = pm.state.schema.nodes.image.create({id: id});
198            pm.dispatch(pm.state.tr.replaceSelectionWith(imageNode));
199        } else {
200            insertAtCarret('wiki__text', '{{' + id + '}}');
201        }
202    }
203
204    // main
205    window.addEventListener('paste', handlePaste, true);
206
207})();
208