]> BookStack Code Mirror - bookstack/blob - resources/assets/js/pages/page-form.js
6428ae9b57583cc97dfb41353714dcca74d706a2
[bookstack] / resources / assets / js / pages / page-form.js
1 "use strict";
2
3 function editorPaste(e, editor) {
4     if (!e.clipboardData) return
5     var items = e.clipboardData.items;
6     if (!items) return;
7     for (var i = 0; i < items.length; i++) {
8         if (items[i].type.indexOf("image") !== -1) {
9
10             var file = items[i].getAsFile();
11             var formData = new FormData();
12             var ext = 'png';
13             var xhr = new XMLHttpRequest();
14
15             if (file.name) {
16                 var fileNameMatches = file.name.match(/\.(.+)$/);
17                 if (fileNameMatches) {
18                     ext = fileNameMatches[1];
19                 }
20             }
21
22             var id = "image-" + Math.random().toString(16).slice(2);
23             var loadingImage = window.baseUrl('/loading.gif');
24             editor.execCommand('mceInsertContent', false, '<img src="'+ loadingImage +'" id="' + id + '">');
25
26             var remoteFilename = "image-" + Date.now() + "." + ext;
27             formData.append('file', file, remoteFilename);
28             formData.append('_token', document.querySelector('meta[name="token"]').getAttribute('content'));
29
30             xhr.open('POST', window.baseUrl('/images/gallery/upload'));
31             xhr.onload = function () {
32                 if (xhr.status === 200 || xhr.status === 201) {
33                     var result = JSON.parse(xhr.responseText);
34                     editor.dom.setAttrib(id, 'src', result.thumbs.display);
35                 } else {
36                     console.log('An error occurred uploading the image');
37                     console.log(xhr.responseText);
38                     editor.dom.remove(id);
39                 }
40             };
41             xhr.send(formData);
42         }
43     }
44 }
45
46 function registerEditorShortcuts(editor) {
47     // Headers
48     for (let i = 1; i < 5; i++) {
49         editor.addShortcut('ctrl+' + i, '', ['FormatBlock', false, 'h' + i]);
50     }
51
52     // Other block shortcuts
53     editor.addShortcut('ctrl+q', '', ['FormatBlock', false, 'blockquote']);
54     editor.addShortcut('ctrl+d', '', ['FormatBlock', false, 'p']);
55     editor.addShortcut('ctrl+e', '', ['FormatBlock', false, 'pre']);
56     editor.addShortcut('ctrl+s', '', ['FormatBlock', false, 'code']);
57 }
58
59 var mceOptions = module.exports = {
60     selector: '#html-editor',
61     content_css: [
62         window.baseUrl('/css/styles.css'),
63         window.baseUrl('/libs/material-design-iconic-font/css/material-design-iconic-font.min.css')
64     ],
65     body_class: 'page-content',
66     relative_urls: false,
67     remove_script_host: false,
68     document_base_url: window.baseUrl('/'),
69     statusbar: false,
70     menubar: false,
71     paste_data_images: false,
72     extended_valid_elements: 'pre[*]',
73     automatic_uploads: false,
74     valid_children: "-div[p|pre|h1|h2|h3|h4|h5|h6|blockquote]",
75     plugins: "image table textcolor paste link fullscreen imagetools code customhr autosave lists",
76     imagetools_toolbar: 'imageoptions',
77     toolbar: "undo redo | styleselect | bold italic underline strikethrough superscript subscript | forecolor backcolor | alignleft aligncenter alignright alignjustify | bullist numlist outdent indent | table image-insert link hr | removeformat code fullscreen",
78     content_style: "body {padding-left: 15px !important; padding-right: 15px !important; margin:0!important; margin-left:auto!important;margin-right:auto!important;}",
79     style_formats: [
80         {title: "Header 1", format: "h1"},
81         {title: "Header 2", format: "h2"},
82         {title: "Header 3", format: "h3"},
83         {title: "Paragraph", format: "p", exact: true, classes: ''},
84         {title: "Blockquote", format: "blockquote"},
85         {title: "Code Block", icon: "code", format: "pre"},
86         {title: "Inline Code", icon: "code", inline: "code"},
87         {title: "Callouts", items: [
88             {title: "Success", block: 'p', exact: true, attributes : {'class' : 'callout success'}},
89             {title: "Info", block: 'p', exact: true, attributes : {'class' : 'callout info'}},
90             {title: "Warning", block: 'p', exact: true, attributes : {'class' : 'callout warning'}},
91             {title: "Danger", block: 'p', exact: true, attributes : {'class' : 'callout danger'}}
92         ]}
93     ],
94     style_formats_merge: false,
95     formats: {
96         alignleft: {selector: 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li,table,img', classes: 'align-left'},
97         aligncenter: {selector: 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li,table,img', classes: 'align-center'},
98         alignright: {selector: 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li,table,img', classes: 'align-right'},
99     },
100     file_browser_callback: function (field_name, url, type, win) {
101
102         if (type === 'file') {
103             window.showEntityLinkSelector(function(entity) {
104                 var originalField = win.document.getElementById(field_name);
105                 originalField.value = entity.link;
106                 $(originalField).closest('.mce-form').find('input').eq(2).val(entity.name);
107             });
108         }
109
110         if (type === 'image') {
111             // Show image manager
112             window.ImageManager.showExternal(function (image) {
113
114                 // Set popover link input to image url then fire change event
115                 // to ensure the new value sticks
116                 win.document.getElementById(field_name).value = image.url;
117                 if ("createEvent" in document) {
118                     var evt = document.createEvent("HTMLEvents");
119                     evt.initEvent("change", false, true);
120                     win.document.getElementById(field_name).dispatchEvent(evt);
121                 } else {
122                     win.document.getElementById(field_name).fireEvent("onchange");
123                 }
124
125                 // Replace the actively selected content with the linked image
126                 var html = '<a href="' + image.url + '" target="_blank">';
127                 html += '<img src="' + image.thumbs.display + '" alt="' + image.name + '">';
128                 html += '</a>';
129                 win.tinyMCE.activeEditor.execCommand('mceInsertContent', false, html);
130             });
131         }
132
133     },
134     paste_preprocess: function (plugin, args) {
135         var content = args.content;
136         if (content.indexOf('<img src="file://') !== -1) {
137             args.content = '';
138         }
139     },
140     extraSetups: [],
141     setup: function (editor) {
142
143         // Run additional setup actions
144         // Used by the angular side of things
145         for (var i = 0; i < mceOptions.extraSetups.length; i++) {
146             mceOptions.extraSetups[i](editor);
147         }
148
149         registerEditorShortcuts(editor);
150
151         (function () {
152             var wrap;
153
154             function hasTextContent(node) {
155                 return node && !!( node.textContent || node.innerText );
156             }
157
158             editor.on('dragstart', function () {
159                 var node = editor.selection.getNode();
160
161                 if (node.nodeName === 'IMG') {
162                     wrap = editor.dom.getParent(node, '.mceTemp');
163
164                     if (!wrap && node.parentNode.nodeName === 'A' && !hasTextContent(node.parentNode)) {
165                         wrap = node.parentNode;
166                     }
167                 }
168             });
169
170             editor.on('drop', function (event) {
171                 var dom = editor.dom,
172                     rng = tinymce.dom.RangeUtils.getCaretRangeFromPoint(event.clientX, event.clientY, editor.getDoc());
173
174                 // Don't allow anything to be dropped in a captioned image.
175                 if (dom.getParent(rng.startContainer, '.mceTemp')) {
176                     event.preventDefault();
177                 } else if (wrap) {
178                     event.preventDefault();
179
180                     editor.undoManager.transact(function () {
181                         editor.selection.setRng(rng);
182                         editor.selection.setNode(wrap);
183                         dom.remove(wrap);
184                     });
185                 }
186
187                 wrap = null;
188             });
189         })();
190
191         // Image picker button
192         editor.addButton('image-insert', {
193             title: 'My title',
194             icon: 'image',
195             tooltip: 'Insert an image',
196             onclick: function () {
197                 window.ImageManager.showExternal(function (image) {
198                     var html = '<a href="' + image.url + '" target="_blank">';
199                     html += '<img src="' + image.thumbs.display + '" alt="' + image.name + '">';
200                     html += '</a>';
201                     editor.execCommand('mceInsertContent', false, html);
202                 });
203             }
204         });
205
206         // Paste image-uploads
207         editor.on('paste', function(event) {
208             editorPaste(event, editor);
209         });
210     }
211 };