xref: /dokuwiki/lib/scripts/edit.js (revision 88d98d0c95b0f7d6f3216a18511bb7ae668f1ae0)
1/**
2 * Functions for text editing (toolbar stuff)
3 *
4 * @todo most of the stuff in here should be revamped and then moved to toolbar.js
5 * @author Andreas Gohr <andi@splitbrain.org>
6 */
7
8/**
9 * Creates a toolbar button through the DOM
10 * Called for each entry of toolbar definition array (built by inc/toolbar.php and extended via js)
11 *
12 * Style the buttons through the toolbutton class
13 *
14 * @param {string} icon      image filename, relative to folder lib/images/toolbar/
15 * @param {string} label     title of button, show on mouseover
16 * @param {string} key       hint in title of button for access key
17 * @param {string} id        id of button, and '<id>_ico' of icon
18 * @param {string} classname for styling buttons
19 *
20 * @author Andreas Gohr <andi@splitbrain.org>
21 * @author Michal Rezler <m.rezler@centrum.cz>
22 */
23function createToolButton(icon,label,key,id,classname){
24    var $btn = jQuery(document.createElement('button')),
25        $ico = jQuery(document.createElement('img'));
26
27    // prepare the basic button stuff
28    $btn.addClass('toolbutton');
29    if(classname){
30        $btn.addClass(classname);
31    }
32
33    $btn.attr('title', label).attr('aria-controls', 'wiki__text');
34    if(key){
35        $btn.attr('title', label + ' ['+key.toUpperCase()+']')
36            .attr('accessKey', key);
37    }
38
39    // set IDs if given
40    if(id){
41        $btn.attr('id', id);
42        $ico.attr('id', id+'_ico');
43    }
44
45    // create the icon and add it to the button
46    if(icon.substr(0,1) !== '/'){
47        icon = DOKU_BASE + 'lib/images/toolbar/' + icon;
48    }
49    $ico.attr('src', icon);
50    $ico.attr('alt', '');
51    $ico.attr('width', 16);
52    $ico.attr('height', 16);
53    $btn.append($ico);
54
55    // we have to return a DOM object (for compatibility reasons)
56    return $btn[0];
57}
58
59/**
60 * Creates a picker window for inserting text
61 *
62 * The given list can be an associative array with text,icon pairs
63 * or a simple list of text. Style the picker window through the picker
64 * class or the picker buttons with the pickerbutton class. Picker
65 * windows are appended to the body and created invisible.
66 *
67 * @param  {string} id    the ID to assign to the picker
68 * @param  {Array}  props the properties for the picker
69 * @param  {string} edid  the ID of the textarea
70 * @return DOMobject    the created picker
71 * @author Andreas Gohr <andi@splitbrain.org>
72 */
73function createPicker(id,props,edid){
74    // create the wrapping div
75    var $picker = jQuery(document.createElement('div'));
76
77    $picker.addClass('picker a11y');
78    if(props['class']){
79        $picker.addClass(props['class']);
80    }
81
82    $picker.attr('id', id).css('position', 'absolute');
83
84    function $makebutton(title) {
85        var $btn = jQuery(document.createElement('button'))
86            .addClass('pickerbutton').attr('title', title)
87            .attr('aria-controls', edid)
88            .on('click', bind(pickerInsert, title, edid))
89            .appendTo($picker);
90        return $btn;
91    }
92
93    jQuery.each(props.list, function (key, item) {
94        if (!props.list.hasOwnProperty(key)) {
95            return;
96        }
97
98        if(isNaN(key)){
99            // associative array -> treat as text => image pairs
100            if (item.substr(0,1) !== '/') {
101                item = DOKU_BASE+'lib/images/'+props.icobase+'/'+item;
102            }
103            jQuery(document.createElement('img'))
104                .attr('src', item)
105                .attr('alt', '')
106                .appendTo($makebutton(key));
107        }else if (typeof item == 'string'){
108            // a list of text -> treat as text picker
109            $makebutton(item).text(item);
110        }else{
111            // a list of lists -> treat it as subtoolbar
112            initToolbar($picker,edid,props.list);
113            return false; // all buttons handled already
114        }
115
116    });
117    jQuery('body').append($picker);
118
119    // we have to return a DOM object (for compatibility reasons)
120    return $picker[0];
121}
122
123/**
124 * Called by picker buttons to insert Text and close the picker again
125 *
126 * @author Andreas Gohr <andi@splitbrain.org>
127 */
128function pickerInsert(text,edid){
129    insertAtCarret(edid,text);
130    pickerClose();
131}
132
133/**
134 * Add button action for signature button
135 *
136 * @param  {jQuery} $btn   Button element to add the action to
137 * @param  {Array}  props  Associative array of button properties
138 * @param  {string} edid   ID of the editor textarea
139 * @return {string} picker id for aria-controls attribute
140 * @author Gabriel Birke <birke@d-scribe.de>
141 */
142function addBtnActionSignature($btn, props, edid) {
143    if(typeof SIG != 'undefined' && SIG != ''){
144        $btn.on('click', function (e) {
145            insertAtCarret(edid,SIG);
146            e.preventDefault();
147        });
148        return edid;
149    }
150    return '';
151}
152
153/**
154 * Determine the current section level while editing
155 *
156 * @param {string} textboxId   ID of the text field
157 *
158 * @author Andreas Gohr <gohr@cosmocode.de>
159 */
160function currentHeadlineLevel(textboxId){
161    var field = jQuery('#' + textboxId)[0],
162        s = false,
163        opts = [field.value.substr(0,DWgetSelection(field).start)];
164    if (field.form && field.form.prefix) {
165        // we need to look in prefix context
166        opts.push(field.form.prefix.value);
167    }
168
169    jQuery.each(opts, function (_, opt) {
170        // Check whether there is a headline in the given string
171        var str = "\n" + opt,
172            lasthl = str.lastIndexOf("\n==");
173        if (lasthl !== -1) {
174            s = str.substr(lasthl+1,6);
175            return false;
176        }
177    });
178    if (s === false) {
179        return 0;
180    }
181    return 7 - s.match(/^={2,6}/)[0].length;
182}
183
184
185/**
186 * global var used for not saved yet warning
187 */
188window.textChanged = false;
189
190/**
191 * global var which stores original editor content
192 */
193window.doku_edit_text_content = '';
194/**
195 * Delete the draft before leaving the page
196 */
197function deleteDraft() {
198    if (is_opera || window.keepDraft) {
199        return;
200    }
201
202    var $dwform = jQuery('#dw__editform');
203
204    if($dwform.length === 0) {
205        return;
206    }
207
208    // remove a possibly saved draft using ajax
209    jQuery.post(DOKU_BASE + 'lib/exe/ajax.php',
210        {
211            call: 'draftdel',
212            id: $dwform.find('input[name=id]').val()
213        }
214    );
215}
216
217/**
218 * Activate "not saved" dialog, add draft deletion to page unload,
219 * add handlers to monitor changes
220 * Note: textChanged could be set by e.g. html_edit() as well
221 *
222 * Sets focus to the editbox as well
223 */
224jQuery(function () {
225    var $editform = jQuery('#dw__editform');
226    if ($editform.length == 0) {
227        return;
228    }
229
230    var $edit_text = jQuery('#wiki__text');
231    if ($edit_text.length > 0) {
232        if($edit_text.attr('readOnly')) {
233            return;
234        }
235
236        // set focus and place cursor at the start
237        var sel = DWgetSelection($edit_text[0]);
238        sel.start = 0;
239        sel.end   = 0;
240        DWsetSelection(sel);
241        $edit_text.trigger('focus');
242
243        doku_edit_text_content = $edit_text.val();
244    }
245
246    var changeHandler = function() {
247        doku_hasTextBeenModified();
248
249        doku_summaryCheck();
250    };
251
252    $editform.change(changeHandler);
253    $editform.keydown(changeHandler);
254
255    window.onbeforeunload = function(){
256        if(window.textChanged) {
257            return LANG.notsavedyet;
258        }
259    };
260    window.onunload = deleteDraft;
261
262    // reset change memory var on submit
263    jQuery('#edbtn__save').on('click',
264        function() {
265            window.onbeforeunload = '';
266            textChanged = false;
267        }
268    );
269    jQuery('#edbtn__preview').on('click',
270        function() {
271            window.onbeforeunload = '';
272            textChanged = false;
273            window.keepDraft = true; // needed to keep draft on page unload
274        }
275    );
276
277    var $summary = jQuery('#edit__summary');
278    $summary.on('change keyup', doku_summaryCheck);
279
280    if (textChanged) doku_summaryCheck();
281});
282
283/**
284 * Updates textChanged variable if content of the editor has been modified
285 */
286function doku_hasTextBeenModified() {
287    if (!textChanged) {
288        var $edit_text = jQuery('#wiki__text');
289
290        if ($edit_text.length > 0) {
291            textChanged = doku_edit_text_content != $edit_text.val();
292        } else {
293            textChanged = true;
294        }
295    }
296}
297
298/**
299 * Checks if a summary was entered - if not the style is changed
300 *
301 * @author Andreas Gohr <andi@splitbrain.org>
302 */
303function doku_summaryCheck(){
304    var $sum = jQuery('#edit__summary'),
305        missing = $sum.val() === '';
306    $sum.toggleClass('missing', missing).toggleClass('edit', !missing);
307}
308