diff --git a/inc/toolbar.php b/inc/toolbar.php index af1dc8d05163d0eab6d671f00d7f08553ebbbbf9..3188c9ac9c1e284e6824b233750c2c7f942618b8 100644 --- a/inc/toolbar.php +++ b/inc/toolbar.php @@ -172,6 +172,9 @@ function toolbar_JSdefines($varname){ 'icon' => 'sig.png', 'key' => 'y', ), + + + )); } // end event TOOLBAR_DEFINE default action $evt->advise_after(); diff --git a/lib/exe/js.php b/lib/exe/js.php index 26323e9b002a93f9fce0e91b00749901fd92b57a..7c5ad021cc3b674884f9b8ae94d6c7e959982b1a 100644 --- a/lib/exe/js.php +++ b/lib/exe/js.php @@ -51,6 +51,7 @@ function js_out(){ ); if($edit){ if($write){ + $files[] = DOKU_INC.'lib/scripts/textselection.js'; $files[] = DOKU_INC.'lib/scripts/toolbar.js'; $files[] = DOKU_INC.'lib/scripts/edit.js'; } diff --git a/lib/scripts/edit.js b/lib/scripts/edit.js index 34305c53dc96397d059b77b13e30dfd024f89e30..517daa086e2a4014439173da1eb4b5a2ba89ecb2 100644 --- a/lib/scripts/edit.js +++ b/lib/scripts/edit.js @@ -125,81 +125,6 @@ function showPicker(pickerid,btn){ } } -/** - * Create a toolbar - * - * @param string tbid ID of the element where to insert the toolbar - * @param string edid ID of the editor textarea - * @param array tb Associative array defining the buttons - * @author Andreas Gohr <andi@splitbrain.org> - */ -function initToolbar(tbid,edid,tb){ - var toolbar = $(tbid); - if(!toolbar) return; - - //empty the toolbar area: - toolbar.innerHTML=''; - - var cnt = tb.length; - for(var i=0; i<cnt; i++){ - // create new button - var btn = createToolButton(tb[i]['icon'], - tb[i]['title'], - tb[i]['key']); - - var actionFunc = 'addBtnAction'+tb[i]['type'].charAt(0).toUpperCase()+tb[i]['type'].substring(1); - var exists = eval("typeof("+actionFunc+") == 'function'"); - if(exists) - { - if(eval(actionFunc+"(btn, tb[i], edid, i)")) - toolbar.appendChild(btn); - }else{ - alert('unknown type: '+tb[i]['type']); - } - } // end for -} - -/** - * Add button action for format buttons - * - * @param DOMElement btn Button element to add the action to - * @param array props Associative array of button properties - * @param string edid ID of the editor textarea - * @return boolean If button should be appended - * @author Gabriel Birke <birke@d-scribe.de> - */ -function addBtnActionFormat(btn, props, edid) -{ - var sample = props['title']; - if(props['sample']){ sample = props['sample']; } - eval("btn.onclick = function(){insertTags('"+ - jsEscape(edid)+"','"+ - jsEscape(props['open'])+"','"+ - jsEscape(props['close'])+"','"+ - jsEscape(sample)+ - "');return false;}"); - - return true; -} - -/** - * Add button action for insert buttons - * - * @param DOMElement btn Button element to add the action to - * @param array props Associative array of button properties - * @param string edid ID of the editor textarea - * @return boolean If button should be appended - * @author Gabriel Birke <birke@d-scribe.de> - */ -function addBtnActionInsert(btn, props, edid) -{ - eval("btn.onclick = function(){insertAtCarret('"+ - jsEscape(edid)+"','"+ - jsEscape(props['insert'])+ - "');return false;}"); - return true; -} - /** * Add button action for signature button * diff --git a/lib/scripts/textselection.js b/lib/scripts/textselection.js new file mode 100644 index 0000000000000000000000000000000000000000..537e8d3481bb3211f0b4bbd1a4d4897351a2bc2a --- /dev/null +++ b/lib/scripts/textselection.js @@ -0,0 +1,175 @@ +/** + * Text selection related functions. + */ + +/** + * selection prototype + * + * Object that capsulates the selection in a textarea. Returned by getSelection. + * + * @author Andreas Gohr <andi@splitbrain.org> + */ +function selection_class(){ + this.start = 0; + this.end = 0; + this.obj = null; + this.rangeCopy = null; + + this.getLength = function(){ + return this.end - this.start; + }; + + this.getText = function(){ + if(!this.obj) return ''; + return this.obj.value.substring(this.start,this.end); + } +} + +/** + * Get current selection/cursor position in a given textArea + * + * @link http://groups.drupal.org/node/1210 + * @author Andreas Gohr <andi@splitbrain.org> + * @returns object - a selection object + */ +function getSelection(textArea) { + var sel = new selection_class(); + + sel.obj = textArea; + sel.start = textArea.value.length; + sel.end = textArea.value.length; + + textArea.focus(); + if(document.getSelection) { // Mozilla et al. + sel.start = textArea.selectionStart; + sel.end = textArea.selectionEnd; + } else if(document.selection) { // MSIE + // The current selection + var range = document.selection.createRange(); + sel.rangeCopy = range.duplicate(); + // Select all text + sel.rangeCopy.moveToElementText(textArea); + // Now move 'dummy' end point to end point of original range + sel.rangeCopy.setEndPoint( 'EndToEnd', range ); + // Now we can calculate start and end points + sel.start = sel.rangeCopy.text.length - range.text.length; + sel.end = sel.start + range.text.length; + } + return sel; +} + +/** + * Set the selection + * + * You need to get a selection object via getSelection() first, then modify the + * start and end properties and pass it back to this function. + * + * @link http://groups.drupal.org/node/1210 + * @author Andreas Gohr <andi@splitbrain.org> + * @param object selection - a selection object as returned by getSelection() + */ +function setSelection(selection){ + if(document.getSelection){ // FF + // what a pleasure in FF ;) + selection.obj.setSelectionRange(selection.start,selection.end); + } else if(document.selection) { // IE + // count number of newlines in str to work around stupid IE selection bug + var countNL = function(str) { + var m = str.split("\n"); + if (!m || !m.length) return 0; + return m.length-1; + }; + var fix = countNL(selection.obj.value.substring(0,selection.start)); + + selection.rangeCopy.collapse(true); + selection.rangeCopy.moveStart('character',selection.start - fix); + selection.rangeCopy.moveEnd('character',selection.end - selection.start); + selection.rangeCopy.select(); + } +} + +/** + * Inserts the given text at the current cursor position or replaces the current + * selection + * + * @author Andreas Gohr <andi@splitbrain.org> + * @param string text - the new text to be pasted + * @param objct selecttion - selection object returned by getSelection + * @param int opts.startofs - number of charcters at the start to skip from new selection + * @param int opts.endofs - number of charcters at the end to skip from new selection + * @param bool opts.ofs - set tru if new text should not be selected + */ +function pasteText(selection,text,opts){ + if(!opts) opts = {}; + // replace the content + selection.obj.value = + selection.obj.value.substring(0, selection.start) + text + + selection.obj.value.substring(selection.end, selection.obj.value.length); + + // set new selection + selection.end = selection.start + text.length; + + // modify the new selection if wanted + if(opts.startofs) selection.start += opts.startofs; + if(opts.endofs) selection.end -= opts.endofs; + + // no selection wanted? set cursor to end position + if(opts.nosel) selection.start = selection.end; + + setSelection(selection); +} + + +/** + * Format selection + * + * Apply tagOpen/tagClose to selection in textarea, use sampleText instead + * of selection if there is none. + * + * @author Andreas Gohr <andi@splitbrain.org> + */ +function insertTags(textAreaID, tagOpen, tagClose, sampleText){ + var txtarea = $(textAreaID); + + var selection = getSelection(txtarea); + var text = selection.getText(); + var opts; + + // don't include trailing space in selection + if(text.charAt(text.length - 1) == ' '){ + selection.end--; + text = selection.getText(); + } + + if(!text){ + // nothing selected, use the sample text and select it + text = sampleText; + opts = { + startofs: tagOpen.length, + endofs: tagClose.length + }; + }else{ + // place cursor at the end + opts = { + nosel: true + }; + } + + // surround with tags + text = tagOpen + text + tagClose; + + // do it + pasteText(selection,text,opts); +} + +/** + * Wraps around pasteText() for backward compatibility + * + * @author Andreas Gohr <andi@splitbrain.org> + */ +function insertAtCarret(textAreaID, text){ + var txtarea = $(textAreaID); + var selection = getSelection(txtarea); + pasteText(selection,text,{nosel: true}); +} + diff --git a/lib/scripts/toolbar.js b/lib/scripts/toolbar.js index f31e12887c0d274a9bc89453134f5fd5f4d68e74..18d2daedee33191f1a78622c4679a6db7f94e1aa 100644 --- a/lib/scripts/toolbar.js +++ b/lib/scripts/toolbar.js @@ -1,161 +1,99 @@ /** - * selection prototype + * Create a toolbar * - * Object that capsulates the selection in a textarea. Returned by getSelection. + * @param string tbid ID of the element where to insert the toolbar + * @param string edid ID of the editor textarea + * @param array tb Associative array defining the buttons + * @author Andreas Gohr <andi@splitbrain.org> */ -function selection_class(){ - this.start = 0; - this.end = 0; - this.obj = null; - this.rangeCopy = null; - - this.getLength = function(){ - return this.end - this.start; - }; - - this.getText = function(){ - if(!this.obj) return ''; - return this.obj.value.substring(this.start,this.end); - } -} +function initToolbar(tbid,edid,tb){ + var toolbar = $(tbid); + if(!toolbar) return; + + //empty the toolbar area: + toolbar.innerHTML=''; + + var cnt = tb.length; + for(var i=0; i<cnt; i++){ + var actionFunc; + + // create new button + var btn = createToolButton(tb[i]['icon'], + tb[i]['title'], + tb[i]['key']); + + + // type is a tb function -> assign it as onclick + actionFunc = 'tb_'+tb[i]['type']; + if( isFunction(window[actionFunc]) ){ + addEvent(btn,'click', function(func,btn, props, edid){ + return function(){ + window[func](btn, props, edid); + return false; + } + }(actionFunc,btn,tb[i],edid) ); + //above fixes the scope problem as descried at http://www.mennovanslooten.nl/blog/post/62 + toolbar.appendChild(btn); + continue; + } + + // type is a init function -> execute it + actionFunc = 'addBtnAction'+tb[i]['type'].charAt(0).toUpperCase()+tb[i]['type'].substring(1); + if( isFunction(window[actionFunc]) ){ + if(window[actionFunc](btn, tb[i], edid, i)){ + toolbar.appendChild(btn); + } + continue; + } + + console.log('unknown toolbar type: '+tb[i]['type']+' '+actionFunc); //FIXME make alert + } // end for -/** - * Get current selection/cursor position in a given textArea - * - * @link http://groups.drupal.org/node/1210 - * @returns object - a selection object - */ -function getSelection(textArea) { - var sel = new selection_class(); - - sel.obj = textArea; - sel.start = textArea.value.length; - sel.end = textArea.value.length; - - textArea.focus(); - if(document.getSelection) { // Mozilla et al. - sel.start = textArea.selectionStart; - sel.end = textArea.selectionEnd; - } else if(document.selection) { // MSIE - // The current selection - var range = document.selection.createRange(); - sel.rangeCopy = range.duplicate(); - // Select all text - sel.rangeCopy.moveToElementText(textArea); - // Now move 'dummy' end point to end point of original range - sel.rangeCopy.setEndPoint( 'EndToEnd', range ); - // Now we can calculate start and end points - sel.start = sel.rangeCopy.text.length - range.text.length; - sel.end = sel.start + range.text.length; - } - return sel; } /** - * Set the selection + * Button action for format buttons * - * You need to get a selection object via getSelection() first, then modify the - * start and end properties and pass it back to this function. - * - * @link http://groups.drupal.org/node/1210 - * @param object selection - a selection object as returned by getSelection() + * @param DOMElement btn Button element to add the action to + * @param array props Associative array of button properties + * @param string edid ID of the editor textarea + * @author Gabriel Birke <birke@d-scribe.de> + * @author Andreas Gohr <andi@splitbrain.org> */ -function setSelection(selection){ - if(document.getSelection){ // FF - // what a pleasure in FF ;) - selection.obj.setSelectionRange(selection.start,selection.end); - } else if(document.selection) { // IE - // count number of newlines in str to work around stupid IE selection bug - var countNL = function(str) { - var m = str.split("\n"); - if (!m || !m.length) return 0; - return m.length-1; - }; - var fix = countNL(selection.obj.value.substring(0,selection.start)); - - selection.rangeCopy.collapse(true); - selection.rangeCopy.moveStart('character',selection.start - fix); - selection.rangeCopy.moveEnd('character',selection.end - selection.start); - selection.rangeCopy.select(); +function tb_format(btn, props, edid) { + var sample = props['title']; + if(props['sample']){ + sample = props['sample']; } + insertTags(edid, + fixtxt(props['open']), + fixtxt(props['close']), + fixtxt(sample)); + return false; } /** - * Inserts the given text at the current cursor position or replaces the current - * selection + * Button action for insert buttons * + * @param DOMElement btn Button element to add the action to + * @param array props Associative array of button properties + * @param string edid ID of the editor textarea + * @author Gabriel Birke <birke@d-scribe.de> * @author Andreas Gohr <andi@splitbrain.org> - * @param string text - the new text to be pasted - * @param objct selecttion - selection object returned by getSelection - * @param int opts.startofs - number of charcters at the start to skip from new selection - * @param int opts.endofs - number of charcters at the end to skip from new selection - * @param bool opts.ofs - set tru if new text should not be selected */ -function pasteText(selection,text,opts){ - if(!opts) opts = {}; - // replace the content - selection.obj.value = - selection.obj.value.substring(0, selection.start) + text + - selection.obj.value.substring(selection.end, selection.obj.value.length); - - // set new selection - selection.end = selection.start + text.length; - - // modify the new selection if wanted - if(opts.startofs) selection.start += opts.startofs; - if(opts.endofs) selection.end -= opts.endofs; - - // no selection wanted? set cursor to end position - if(opts.nosel) selection.start = selection.end; - - setSelection(selection); +function tb_insert(btn, props, edid) { + insertAtCarret(edid,fixtxt(props['insert'])); } -/** - * Format selection - * - * Apply tagOpen/tagClose to selection in textarea, use sampleText instead - * of selection if there is none. - * - * @author Andreas Gohr <andi@splitbrain.org> - */ -function insertTags(textAreaID, tagOpen, tagClose, sampleText){ - var txtarea = document.getElementById(textAreaID); - - var selection = getSelection(txtarea); - var text = selection.getText(); - // don't include trailing space in selection - if(text.charAt(text.length - 1) == ' '){ - selection.end--; - text = selection.getText(); - } - - // nothing selected, use the sample text - if(!text) text = sampleText; - - // surround with tags - text = tagOpen + text + tagClose; - // prepare options - var opts = { - startofs: tagOpen.length, - endofs: tagClose.length - }; - // do it - pasteText(selection,text,opts); -} /** - * Wraps around pasteText() for backward compatibility - * - * @author Andreas Gohr <andi@splitbrain.org> + * Replaces \n with linebreaks */ -function insertAtCarret(textAreaID, text){ - var txtarea = document.getElementById(textAreaID); - var selection = getSelection(txtarea); - pasteText(selection,text,{nosel: true}); +function fixtxt(str){ + return str.replace(/\\n/g,"\n"); }