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