xref: /dokuwiki/lib/scripts/editor.js (revision f461b96d476f9ab838d4503a50f28a9b82b23a4f)
1/**
2 * The DokuWiki editor features
3 *
4 * These are the advanced features of the editor. It does NOT contain any
5 * code for the toolbar buttons and it functions. See toolbar.js for that.
6 */
7
8var dw_editor = {
9
10    /**
11     * initialize the default editor functionality
12     *
13     * All other functions can also be called separately for non-default
14     * textareas
15     */
16    init: function(){
17        var $editor = jQuery('#wiki__text');
18        if($editor.length === 0) {
19            return;
20        }
21
22        dw_editor.initSizeCtl('#size__ctl',$editor);
23
24        if($editor.attr('readOnly')) {
25            return;
26        }
27
28        // in Firefox, keypress doesn't send the correct keycodes,
29        // in Opera, the default of keydown can't be prevented
30        if (jQuery.browser.opera) {
31            $editor.keypress(dw_editor.keyHandler);
32        } else {
33            $editor.keydown(dw_editor.keyHandler);
34        }
35
36    },
37
38    /**
39     * Add the edit window size and wrap controls
40     *
41     * Initial values are read from cookie if it exists
42     *
43     * @param selector ctlarea the div to place the controls
44     * @param selector editor  the textarea to control
45     */
46    initSizeCtl: function(ctlarea,editor){
47        var $ctl      = jQuery(ctlarea),
48            $textarea = jQuery(editor);
49
50        if($ctl.length === 0 || $textarea.length === 0) {
51            return;
52        }
53
54        $textarea.css('height', DokuCookie.getValue('sizeCtl') || '300px');
55
56        var wrp = DokuCookie.getValue('wrapCtl');
57        if(wrp){
58            dw_editor.setWrap($textarea[0], wrp);
59        } // else use default value
60
61        jQuery.each([
62            ['larger', function(){dw_editor.sizeCtl(editor,100);}],
63            ['smaller', function(){dw_editor.sizeCtl(editor,-100);}],
64            ['wrap', function(){dw_editor.toggleWrap(editor);}]
65        ], function (_, img) {
66            jQuery(document.createElement('IMG'))
67                .attr('src', DOKU_BASE+'lib/images/' + img[0] + '.gif')
68                .attr('alt', '')
69                .click(img[1])
70                .appendTo($ctl);
71        });
72    },
73
74    /**
75     * This sets the vertical size of the editbox and adjusts the cookie
76     *
77     * @param selector editor  the textarea to control
78     * @param int val          the relative value to resize in pixel
79     */
80    sizeCtl: function(editor,val){
81        var $textarea = jQuery(editor),
82            height = parseInt($textarea.css('height')) + val;
83        $textarea.css('height', height+'px');
84        DokuCookie.setValue('sizeCtl',$textarea.css('height'));
85    },
86
87    /**
88     * Toggle the wrapping mode of the editor textarea and adjusts the
89     * cookie
90     *
91     * @param selector editor  the textarea to control
92     */
93    toggleWrap: function(editor){
94        var $textarea = jQuery(editor),
95            wrap = $textarea.attr('wrap');
96        dw_editor.setWrap($textarea[0],
97                          (wrap && wrap.toLowerCase() == 'off') ? 'soft' : 'off');
98        DokuCookie.setValue('wrapCtl',$textarea.attr('wrap'));
99    },
100
101    /**
102     * Set the wrapping mode of a textarea
103     *
104     * @author Fluffy Convict <fluffyconvict@hotmail.com>
105     * @author <shutdown@flashmail.com>
106     * @link   http://news.hping.org/comp.lang.javascript.archive/12265.html
107     * @link   https://bugzilla.mozilla.org/show_bug.cgi?id=41464
108     * @param  DomObject textarea
109     * @param  string wrapAttrValue
110     */
111    setWrap: function(textarea, wrapAttrValue){
112        textarea.setAttribute('wrap', wrapAttrValue);
113
114        // Fix display for mozilla
115        var parNod = textarea.parentNode;
116        var nxtSib = textarea.nextSibling;
117        parNod.removeChild(textarea);
118        parNod.insertBefore(textarea, nxtSib);
119    },
120
121    /**
122     * Make intended formattings easier to handle
123     *
124     * Listens to all key inputs and handle indentions
125     * of lists and code blocks
126     *
127     * Currently handles space, backspace, enter and
128     * ctrl-enter presses
129     *
130     * @author Andreas Gohr <andi@splitbrain.org>
131     * @fixme handle tabs
132     * @param event e - the key press event object
133     */
134    keyHandler: function(e){
135        if(jQuery.inArray(e.keyCode,[8, 10, 13, 32]) === -1) {
136            return;
137        }
138        var selection = DWgetSelection(this);
139        if(selection.getLength() > 0) {
140            return; //there was text selected, keep standard behavior
141        }
142        var search    = "\n"+this.value.substr(0,selection.start);
143        var linestart = Math.max(search.lastIndexOf("\n"),
144                                 search.lastIndexOf("\r")); //IE workaround
145        search = search.substr(linestart);
146
147        if((e.keyCode == 13 || e.keyCode == 10) && e.ctrlKey) { // Ctrl-Enter (With Chrome workaround)
148            // Submit current edit
149            jQuery('#edbtn__save').click();
150            e.preventDefault(); // prevent enter key
151            return false;
152        }else if(e.keyCode == 13){ // Enter
153            // keep current indention for lists and code
154            var match = search.match(/(\n  +([\*-] ?)?)/);
155            if(match){
156                var scroll = this.scrollHeight;
157                var match2 = search.match(/^\n  +[\*-]\s*$/);
158                // Cancel list if the last item is empty (i. e. two times enter)
159                if (match2 && this.value.substr(selection.start).match(/^($|\r?\n)/)) {
160                    this.value = this.value.substr(0, linestart) + "\n" +
161                                 this.value.substr(selection.start);
162                    selection.start = linestart + 1;
163                    selection.end = linestart + 1;
164                    DWsetSelection(selection);
165                } else {
166                    insertAtCarret(this.id,match[1]);
167                }
168                this.scrollTop += (this.scrollHeight - scroll);
169                e.preventDefault(); // prevent enter key
170                return false;
171            }
172        }else if(e.keyCode == 8){ // Backspace
173            // unindent lists
174            var match = search.match(/(\n  +)([*-] ?)$/);
175            if(match){
176                var spaces = match[1].length-1;
177
178                if(spaces > 3){ // unindent one level
179                    this.value = this.value.substr(0,linestart)+
180                                 this.value.substr(linestart+2);
181                    selection.start = selection.start - 2;
182                    selection.end   = selection.start;
183                }else{ // delete list point
184                    this.value = this.value.substr(0,linestart)+
185                                 this.value.substr(selection.start);
186                    selection.start = linestart;
187                    selection.end   = linestart;
188                }
189                DWsetSelection(selection);
190                e.preventDefault(); // prevent backspace
191                return false;
192            }
193        }else if(e.keyCode == 32){ // Space
194            // intend list item
195            var match = search.match(/(\n  +)([*-] )$/);
196            if(match){
197                this.value = this.value.substr(0,linestart)+'  '+
198                             this.value.substr(linestart);
199                selection.start = selection.start + 2;
200                selection.end   = selection.start;
201                DWsetSelection(selection);
202                e.preventDefault(); // prevent space
203                return false;
204            }
205        }
206    }
207
208
209};
210
211jQuery(dw_editor.init);
212