2 * Functions for text editing (toolbar stuff)
4 * @todo most of the stuff in here should be revamped and then moved to toolbar.js
5 * @author Andreas Gohr <andi@splitbrain.org>
9 * Creates a toolbar button through the DOM
11 * Style the buttons through the toolbutton class
13 * @author Andreas Gohr <andi@splitbrain.org>
15 function createToolButton(icon,label,key,id){
16 var btn = document.createElement('button');
17 var ico = document.createElement('img');
19 // preapare the basic button stuff
20 btn.className = 'toolbutton';
23 btn.title += ' ['+key.toUpperCase()+']';
33 // create the icon and add it to the button
34 if(icon.substr(0,1) == '/'){
37 ico.src = DOKU_BASE+'lib/images/toolbar/'+icon;
45 * Creates a picker window for inserting text
47 * The given list can be an associative array with text,icon pairs
48 * or a simple list of text. Style the picker window through the picker
49 * class or the picker buttons with the pickerbutton class. Picker
50 * windows are appended to the body and created invisible.
52 * @param string id the ID to assign to the picker
53 * @param array props the properties for the picker
54 * @param string edid the ID of the textarea
55 * @rteurn DOMobject the created picker
56 * @author Andreas Gohr <andi@splitbrain.org>
58 function createPicker(id,props,edid){
59 var icobase = props['icobase'];
60 var list = props['list'];
62 // create the wrapping div
63 var picker = document.createElement('div');
64 picker.className = 'picker';
66 picker.className += ' '+props['class'];
69 picker.style.position = 'absolute';
70 picker.style.marginLeft = '-10000px'; // no display:none, to keep access keys working
73 if (!list.hasOwnProperty(key)) continue;
76 // associative array -> treat as image/value pairs
77 var btn = document.createElement('button');
78 btn.className = 'pickerbutton';
79 var ico = document.createElement('img');
80 if(list[key].substr(0,1) == '/'){
83 ico.src = DOKU_BASE+'lib/images/'+icobase+'/'+list[key];
87 addEvent(btn,'click',bind(pickerInsert,key,edid));
88 picker.appendChild(btn);
89 }else if(isString(list[key])){
90 // a list of text -> treat as text picker
91 var btn = document.createElement('button');
92 btn.className = 'pickerbutton';
93 var txt = document.createTextNode(list[key]);
94 btn.title = list[key];
96 addEvent(btn,'click',bind(pickerInsert,list[key],edid));
97 picker.appendChild(btn);
99 // a list of lists -> treat it as subtoolbar
100 initToolbar(picker,edid,list);
101 break; // all buttons handled already
105 var body = document.getElementsByTagName('body')[0];
106 body.appendChild(picker);
111 * Called by picker buttons to insert Text and close the picker again
113 * @author Andreas Gohr <andi@splitbrain.org>
115 function pickerInsert(text,edid){
116 insertAtCarret(edid,text);
121 * Add button action for signature button
123 * @param DOMElement btn Button element to add the action to
124 * @param array props Associative array of button properties
125 * @param string edid ID of the editor textarea
126 * @return boolean If button should be appended
127 * @author Gabriel Birke <birke@d-scribe.de>
129 function addBtnActionSignature(btn, props, edid) {
130 if(typeof(SIG) != 'undefined' && SIG != ''){
131 addEvent(btn,'click',bind(insertAtCarret,edid,SIG));
138 * Make intended formattings easier to handle
140 * Listens to all key inputs and handle indentions
141 * of lists and code blocks
143 * Currently handles space, backspce and enter presses
145 * @author Andreas Gohr <andi@splitbrain.org>
148 function keyHandler(e){
149 if(e.keyCode != 13 &&
151 e.keyCode != 32) return;
152 var field = e.target;
153 var selection = getSelection(field);
154 var search = "\n"+field.value.substr(0,selection.start);
155 var linestart = Math.max(search.lastIndexOf("\n"),
156 search.lastIndexOf("\r")); //IE workaround
157 search = search.substr(linestart);
159 if(e.keyCode == 13){ // Enter
160 // keep current indention for lists and code
161 var match = search.match(/(\n +([\*-] ?)?)/);
163 var scroll = field.scrollHeight;
164 insertAtCarret(field.id,match[1]);
165 field.scrollTop += (field.scrollHeight - scroll);
166 e.preventDefault(); // prevent enter key
168 }else if(e.keyCode == 8){ // Backspace
170 var match = search.match(/(\n +)([*-] ?)$/);
172 var spaces = match[1].length-1;
174 if(spaces > 3){ // unindent one level
175 field.value = field.value.substr(0,linestart)+
176 field.value.substr(linestart+2);
177 selection.start = selection.start - 2;
178 selection.end = selection.start;
179 }else{ // delete list point
180 field.value = field.value.substr(0,linestart)+
181 field.value.substr(selection.start);
182 selection.start = linestart;
183 selection.end = linestart;
185 setSelection(selection);
186 e.preventDefault(); // prevent backspace
188 }else if(e.keyCode == 32){ // Space
190 var match = search.match(/(\n +)([*-] )$/);
192 field.value = field.value.substr(0,linestart)+' '+
193 field.value.substr(linestart);
194 selection.start = selection.start + 2;
195 selection.end = selection.start;
196 setSelection(selection);
197 e.preventDefault(); // prevent space
202 //FIXME consolidate somewhere else
203 addInitEvent(function(){
204 var field = $('wiki__text');
206 addEvent(field,'keypress',keyHandler);
210 * Determine the current section level while editing
212 * @author Andreas Gohr <gohr@cosmocode.de>
214 function currentHeadlineLevel(textboxId){
215 var field = $(textboxId);
216 var selection = getSelection(field);
217 var search = "\n"+field.value.substr(0,selection.start);
218 var lasthl = search.lastIndexOf("\n==");
219 if(lasthl == -1 && field.form.prefix){
220 // we need to look in prefix context
221 search = field.form.prefix.value;
222 lasthl = search.lastIndexOf("\n==");
224 search = search.substr(lasthl+1,6);
226 if(search == '======') return 1;
227 if(search.substr(0,5) == '=====') return 2;
228 if(search.substr(0,4) == '====') return 3;
229 if(search.substr(0,3) == '===') return 4;
230 if(search.substr(0,2) == '==') return 5;
237 * global var used for not saved yet warning
239 var textChanged = false;
242 * Check for changes before leaving the page
244 function changeCheck(msg){
246 var ok = confirm(msg);
248 // remove a possibly saved draft using ajax
249 var dwform = $('dw__editform');
251 var params = 'call=draftdel';
252 params += '&id='+encodeURIComponent(dwform.elements.id.value);
254 var sackobj = new sack(DOKU_BASE + 'lib/exe/ajax.php');
255 sackobj.AjaxFailedAlert = '';
256 sackobj.encodeURIString = false;
257 sackobj.runAJAX(params);
258 // we send this request blind without waiting for
259 // and handling the returned data
269 * Add changeCheck to all Links and Forms (except those with a
270 * JSnocheck class), add handlers to monitor changes
272 * Sets focus to the editbox as well
274 * @fixme this is old and crappy code. needs to be redone
276 function initChangeCheck(msg){
277 var edit_text = document.getElementById('wiki__text');
278 if(!edit_text) return;
280 // add change check for links
281 var links = document.getElementsByTagName('a');
282 for(var i=0; i < links.length; i++){
283 if(links[i].className.indexOf('JSnocheck') == -1){
284 links[i].onclick = function(){
285 var rc = changeCheck(msg);
286 if(window.event) window.event.returnValue = rc;
291 // add change check for forms
292 var forms = document.forms;
293 for(i=0; i < forms.length; i++){
294 if(forms[i].className.indexOf('JSnocheck') == -1){
295 forms[i].onsubmit = function(){
296 var rc = changeCheck(msg);
297 if(window.event) window.event.returnValue = rc;
303 // reset change memory var on submit
304 var btn_save = document.getElementById('edbtn__save');
305 btn_save.onclick = function(){ textChanged = false; };
306 var btn_prev = document.getElementById('edbtn__preview');
307 btn_prev.onclick = function(){ textChanged = false; };
309 // add change memory setter
310 edit_text.onchange = function(){
311 textChanged = true; //global var
314 var summary = document.getElementById('edit__summary');
315 addEvent(summary, 'change', summaryCheck);
316 addEvent(summary, 'keyup', summaryCheck);
317 if (textChanged) summaryCheck();
324 * Checks if a summary was entered - if not the style is changed
326 * @author Andreas Gohr <andi@splitbrain.org>
328 function summaryCheck(){
329 var sum = document.getElementById('edit__summary');
330 if(sum.value === ''){
331 sum.className='missing';
333 sum.className='edit';
339 * Class managing the timer to display a warning on a expiring lock
341 function locktimer_class(){
345 this.lasttime = null;
349 var locktimer = new locktimer_class();
350 locktimer.init = function(timeout,msg,draft){
352 locktimer.timeout = timeout*1000;
354 locktimer.draft = draft;
355 locktimer.lasttime = new Date();
357 if(!$('dw__editform')) return;
358 locktimer.pageid = $('dw__editform').elements.id.value;
359 if(!locktimer.pageid) return;
361 // init ajax component
362 locktimer.sack = new sack(DOKU_BASE + 'lib/exe/ajax.php');
363 locktimer.sack.AjaxFailedAlert = '';
364 locktimer.sack.encodeURIString = false;
365 locktimer.sack.onCompletion = locktimer.refreshed;
367 // register refresh event
368 addEvent($('dw__editform').elements.wikitext,'keypress',function(){locktimer.refresh();});
375 * (Re)start the warning timer
377 locktimer.reset = function(){
379 locktimer.timerID = window.setTimeout("locktimer.warning()", locktimer.timeout);
383 * Display the warning about the expiring lock
385 locktimer.warning = function(){
387 alert(locktimer.msg);
391 * Remove the current warning timer
393 locktimer.clear = function(){
394 if(locktimer.timerID !== null){
395 window.clearTimeout(locktimer.timerID);
396 locktimer.timerID = null;
401 * Refresh the lock via AJAX
403 * Called on keypresses in the edit area
405 locktimer.refresh = function(){
406 var now = new Date();
407 // refresh every minute only
408 if(now.getTime() - locktimer.lasttime.getTime() > 30*1000){ //FIXME decide on time
409 var params = 'call=lock&id='+encodeURIComponent(locktimer.pageid);
411 var dwform = $('dw__editform');
412 params += '&prefix='+encodeURIComponent(dwform.elements.prefix.value);
413 params += '&wikitext='+encodeURIComponent(dwform.elements.wikitext.value);
414 params += '&suffix='+encodeURIComponent(dwform.elements.suffix.value);
415 params += '&date='+encodeURIComponent(dwform.elements.date.value);
417 locktimer.sack.runAJAX(params);
418 locktimer.lasttime = now;
424 * Callback. Resets the warning timer
426 locktimer.refreshed = function(){
427 var data = this.response;
428 var error = data.charAt(0);
429 data = data.substring(1);
431 $('draft__status').innerHTML=data;
432 if(error != '1') return; // locking failed
435 // end of locktimer class functions