[ Index ]

PHP Cross Reference of DokuWiki

title

Body

[close]

/lib/scripts/ -> edit.js (source)

   1  /**
   2   * Functions for text editing (toolbar stuff)
   3   *
   4   * @todo I'm no JS guru please help if you know how to improve
   5   * @author Andreas Gohr <andi@splitbrain.org>
   6   */
   7  
   8  /**
   9   * Creates a toolbar button through the DOM
  10   *
  11   * Style the buttons through the toolbutton class
  12   *
  13   * @author Andreas Gohr <andi@splitbrain.org>
  14   */
  15  function createToolButton(icon,label,key,id){
  16      var btn = document.createElement('button');
  17      var ico = document.createElement('img');
  18  
  19      // preapare the basic button stuff
  20      btn.className = 'toolbutton';
  21      btn.title = label;
  22      if(key){
  23          btn.title += ' ['+key.toUpperCase()+']';
  24          btn.accessKey = key;
  25      }
  26  
  27      // set IDs if given
  28      if(id){
  29          btn.id = id;
  30          ico.id = id+'_ico';
  31      }
  32  
  33      // create the icon and add it to the button
  34      if(icon.substr(0,1) == '/'){
  35          ico.src = icon;
  36      }else{
  37          ico.src = DOKU_BASE+'lib/images/toolbar/'+icon;
  38      }
  39      btn.appendChild(ico);
  40  
  41      return btn;
  42  }
  43  
  44  /**
  45   * Creates a picker window for inserting text
  46   *
  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.
  51   *
  52   * @author Andreas Gohr <andi@splitbrain.org>
  53   */
  54  function createPicker(id,list,icobase,edid){
  55      var cnt = list.length;
  56  
  57      var picker = document.createElement('div');
  58      picker.className = 'picker';
  59      picker.id = id;
  60      picker.style.position = 'absolute';
  61      picker.style.display  = 'none';
  62  
  63      for(var key in list){
  64          if (!list.hasOwnProperty(key)) continue;
  65          var btn = document.createElement('button');
  66  
  67          btn.className = 'pickerbutton';
  68  
  69          // associative array?
  70          if(isNaN(key)){
  71              var ico = document.createElement('img');
  72              if(list[key].substr(0,1) == '/'){
  73                  ico.src = list[key];
  74              }else{
  75                  ico.src = DOKU_BASE+'lib/images/'+icobase+'/'+list[key];
  76              }
  77              btn.title     = key;
  78              btn.appendChild(ico);
  79              eval("btn.onclick = function(){pickerInsert('"+id+"','"+
  80                                    jsEscape(key)+"','"+
  81                                    jsEscape(edid)+"');return false;}");
  82          }else{
  83              var txt = document.createTextNode(list[key]);
  84              btn.title     = list[key];
  85              btn.appendChild(txt);
  86              eval("btn.onclick = function(){pickerInsert('"+id+"','"+
  87                                    jsEscape(list[key])+"','"+
  88                                    jsEscape(edid)+"');return false;}");
  89          }
  90  
  91          picker.appendChild(btn);
  92      }
  93      var body = document.getElementsByTagName('body')[0];
  94      body.appendChild(picker);
  95  }
  96  
  97  /**
  98   * Called by picker buttons to insert Text and close the picker again
  99   *
 100   * @author Andreas Gohr <andi@splitbrain.org>
 101   */
 102  function pickerInsert(pickerid,text,edid){
 103      // insert
 104      insertAtCarret(edid,text);
 105      // close picker
 106      pobj = document.getElementById(pickerid);
 107      pobj.style.display = 'none';
 108  }
 109  
 110  /**
 111   * Show a previosly created picker window
 112   *
 113   * @author Andreas Gohr <andi@splitbrain.org>
 114   */
 115  function showPicker(pickerid,btn){
 116      var picker = document.getElementById(pickerid);
 117      var x = findPosX(btn);
 118      var y = findPosY(btn);
 119      if(picker.style.display == 'none'){
 120          picker.style.display = 'block';
 121          picker.style.left = (x+3)+'px';
 122          picker.style.top = (y+btn.offsetHeight+3)+'px';
 123      }else{
 124          picker.style.display = 'none';
 125      }
 126  }
 127  
 128  /**
 129   * Create a toolbar
 130   *
 131   * @param  string tbid ID of the element where to insert the toolbar
 132   * @param  string edid ID of the editor textarea
 133   * @param  array  tb   Associative array defining the buttons
 134   * @author Andreas Gohr <andi@splitbrain.org>
 135   */
 136  function initToolbar(tbid,edid,tb){
 137      var toolbar = $(tbid);
 138      if(!toolbar) return;
 139  
 140      //empty the toolbar area:
 141      toolbar.innerHTML='';
 142  
 143      var cnt = tb.length;
 144      for(var i=0; i<cnt; i++){
 145          // create new button
 146          var btn = createToolButton(tb[i]['icon'],
 147                                 tb[i]['title'],
 148                                 tb[i]['key']);
 149  
 150          var actionFunc = 'addBtnAction'+tb[i]['type'].charAt(0).toUpperCase()+tb[i]['type'].substring(1);
 151          var exists = eval("typeof("+actionFunc+") == 'function'");
 152          if(exists)
 153          {
 154              if(eval(actionFunc+"(btn, tb[i], edid, i)"))
 155                  toolbar.appendChild(btn);
 156          }
 157      } // end for
 158  }
 159  
 160  /**
 161   * Add button action for format buttons
 162   *
 163   * @param  DOMElement btn   Button element to add the action to
 164   * @param  array      props Associative array of button properties
 165   * @param  string     edid  ID of the editor textarea
 166   * @return boolean    If button should be appended
 167   * @author Gabriel Birke <birke@d-scribe.de>
 168   */
 169  function addBtnActionFormat(btn, props, edid)
 170  {
 171      var sample = props['title'];
 172      if(props['sample']){ sample = props['sample']; }
 173      eval("btn.onclick = function(){insertTags('"+
 174          jsEscape(edid)+"','"+
 175          jsEscape(props['open'])+"','"+
 176          jsEscape(props['close'])+"','"+
 177          jsEscape(sample)+
 178      "');return false;}");
 179  
 180      return true;
 181  }
 182  
 183  /**
 184   * Add button action for insert buttons
 185   *
 186   * @param  DOMElement btn   Button element to add the action to
 187   * @param  array      props Associative array of button properties
 188   * @param  string     edid  ID of the editor textarea
 189   * @return boolean    If button should be appended
 190   * @author Gabriel Birke <birke@d-scribe.de>
 191   */
 192  function addBtnActionInsert(btn, props, edid)
 193  {
 194      eval("btn.onclick = function(){insertAtCarret('"+
 195          jsEscape(edid)+"','"+
 196          jsEscape(props['insert'])+
 197      "');return false;}");
 198      return true;
 199  }
 200  
 201  /**
 202   * Add button action for signature button
 203   *
 204   * @param  DOMElement btn   Button element to add the action to
 205   * @param  array      props Associative array of button properties
 206   * @param  string     edid  ID of the editor textarea
 207   * @return boolean    If button should be appended
 208   * @author Gabriel Birke <birke@d-scribe.de>
 209   */
 210  function addBtnActionSignature(btn, props, edid)
 211  {
 212      if(typeof(SIG) != 'undefined' && SIG != ''){
 213          eval("btn.onclick = function(){insertAtCarret('"+
 214              jsEscape(edid)+"','"+
 215              jsEscape(SIG)+
 216          "');return false;}");
 217          return true;
 218      }
 219      return false;
 220  }
 221  
 222  /**
 223   * Add button action for picker buttons and create picker element
 224   *
 225   * @param  DOMElement btn   Button element to add the action to
 226   * @param  array      props Associative array of button properties
 227   * @param  string     edid  ID of the editor textarea
 228   * @param  int        id    Unique number of the picker
 229   * @return boolean    If button should be appended
 230   * @author Gabriel Birke <birke@d-scribe.de>
 231   */
 232  function addBtnActionPicker(btn, props, edid, id)
 233  {
 234      createPicker('picker'+id,
 235           props['list'],
 236           props['icobase'],
 237           edid);
 238      eval("btn.onclick = function(){showPicker('picker"+id+
 239                                      "',this);return false;}");
 240      return true;
 241  }
 242  
 243  /**
 244   * Add button action for the mediapopup button
 245   *
 246   * @param  DOMElement btn   Button element to add the action to
 247   * @param  array      props Associative array of button properties
 248   * @return boolean    If button should be appended
 249   * @author Gabriel Birke <birke@d-scribe.de>
 250   */
 251  function addBtnActionMediapopup(btn, props)
 252  {
 253      eval("btn.onclick = function(){window.open('"+
 254          jsEscape(props['url']+encodeURIComponent(NS))+"','"+
 255          jsEscape(props['name'])+"','"+
 256          jsEscape(props['options'])+
 257      "');return false;}");
 258      return true;
 259  }
 260  
 261  /**
 262   * Format selection
 263   *
 264   * Apply tagOpen/tagClose to selection in textarea, use sampleText instead
 265   * of selection if there is none. Copied and adapted from phpBB
 266   *
 267   * @author phpBB development team
 268   * @author MediaWiki development team
 269   * @author Andreas Gohr <andi@splitbrain.org>
 270   * @author Jim Raynor <jim_raynor@web.de>
 271   */
 272  function insertTags(edid,tagOpen, tagClose, sampleText) {
 273    var txtarea = document.getElementById(edid);
 274    // IE
 275    if(document.selection  && !is_gecko) {
 276      var theSelection = document.selection.createRange().text;
 277      var replaced = true;
 278      if(!theSelection){
 279        replaced = false;
 280        theSelection=sampleText;
 281      }
 282      txtarea.focus();
 283  
 284      // This has change
 285      var text = theSelection;
 286      if(theSelection.charAt(theSelection.length - 1) == " "){// exclude ending space char, if any
 287        theSelection = theSelection.substring(0, theSelection.length - 1);
 288        r = document.selection.createRange();
 289        r.text = tagOpen + theSelection + tagClose + " ";
 290      } else {
 291        r = document.selection.createRange();
 292        r.text = tagOpen + theSelection + tagClose;
 293      }
 294      if(!replaced){
 295        r.moveStart('character',-text.length-tagClose.length);
 296        r.moveEnd('character',-tagClose.length);
 297      }
 298      r.select();
 299    // Mozilla
 300    } else if(txtarea.selectionStart || txtarea.selectionStart == '0') {
 301      replaced = false;
 302      var startPos = txtarea.selectionStart;
 303      var endPos   = txtarea.selectionEnd;
 304      if(endPos - startPos){ replaced = true; }
 305      var scrollTop=txtarea.scrollTop;
 306      var myText = (txtarea.value).substring(startPos, endPos);
 307      if(!myText) { myText=sampleText;}
 308      if(myText.charAt(myText.length - 1) == " "){ // exclude ending space char, if any
 309        subst = tagOpen + myText.substring(0, (myText.length - 1)) + tagClose + " ";
 310      } else {
 311        subst = tagOpen + myText + tagClose;
 312      }
 313      txtarea.value = txtarea.value.substring(0, startPos) + subst +
 314                      txtarea.value.substring(endPos, txtarea.value.length);
 315      txtarea.focus();
 316  
 317      //set new selection
 318      if(replaced){
 319        var cPos=startPos+(tagOpen.length+myText.length+tagClose.length);
 320        txtarea.selectionStart=cPos;
 321        txtarea.selectionEnd=cPos;
 322      }else{
 323        txtarea.selectionStart=startPos+tagOpen.length;
 324        txtarea.selectionEnd=startPos+tagOpen.length+myText.length;
 325      }
 326      txtarea.scrollTop=scrollTop;
 327    // All others
 328    } else {
 329      var copy_alertText=alertText;
 330      var re1=new RegExp("\\$1","g");
 331      var re2=new RegExp("\\$2","g");
 332      copy_alertText=copy_alertText.replace(re1,sampleText);
 333      copy_alertText=copy_alertText.replace(re2,tagOpen+sampleText+tagClose);
 334  
 335      if (sampleText) {
 336        text=prompt(copy_alertText);
 337      } else {
 338        text="";
 339      }
 340      if(!text) { text=sampleText;}
 341      text=tagOpen+text+tagClose;
 342      //append to the end
 343      txtarea.value += "\n"+text;
 344  
 345      // in Safari this causes scrolling
 346      if(!is_safari) {
 347        txtarea.focus();
 348      }
 349  
 350    }
 351    // reposition cursor if possible
 352    if (txtarea.createTextRange){
 353      txtarea.caretPos = document.selection.createRange().duplicate();
 354    }
 355  }
 356  
 357  /*
 358   * Insert the given value at the current cursor position
 359   *
 360   * @see http://www.alexking.org/index.php?content=software/javascript/content.php
 361   */
 362  function insertAtCarret(edid,value){
 363    var field = document.getElementById(edid);
 364  
 365    //IE support
 366    if (document.selection) {
 367      field.focus();
 368      sel = document.selection.createRange();
 369      sel.text = value;
 370  
 371    //MOZILLA/NETSCAPE support
 372    }else if (field.selectionStart || field.selectionStart == '0') {
 373      var startPos  = field.selectionStart;
 374      var endPos    = field.selectionEnd;
 375      var scrollTop = field.scrollTop;
 376      field.value = field.value.substring(0, startPos) +
 377                    value +
 378                    field.value.substring(endPos, field.value.length);
 379  
 380      field.focus();
 381      var cPos=startPos+(value.length);
 382      field.selectionStart=cPos;
 383      field.selectionEnd=cPos;
 384      field.scrollTop=scrollTop;
 385    } else {
 386      field.value += "\n"+value;
 387    }
 388    // reposition cursor if possible
 389    if (field.createTextRange){
 390      field.caretPos = document.selection.createRange().duplicate();
 391    }
 392    if(value){
 393      window.textChanged = true;
 394      summaryCheck();
 395    }
 396  }
 397  
 398  
 399  /**
 400   * global var used for not saved yet warning
 401   */
 402  var textChanged = false;
 403  
 404  /**
 405   * Check for changes before leaving the page
 406   */
 407  function changeCheck(msg){
 408    if(textChanged){
 409      var ok = confirm(msg);
 410      if(ok){
 411          // remove a possibly saved draft using ajax
 412          var dwform = $('dw__editform');
 413          if(dwform){
 414              var params = 'call=draftdel';
 415              params += '&id='+encodeURIComponent(dwform.elements.id.value);
 416  
 417              var sackobj = new sack(DOKU_BASE + 'lib/exe/ajax.php');
 418              sackobj.AjaxFailedAlert = '';
 419              sackobj.encodeURIString = false;
 420              sackobj.runAJAX(params);
 421              // we send this request blind without waiting for
 422              // and handling the returned data
 423          }
 424      }
 425      return ok;
 426    }else{
 427      return true;
 428    }
 429  }
 430  
 431  /**
 432   * Add changeCheck to all Links and Forms (except those with a
 433   * JSnocheck class), add handlers to monitor changes
 434   *
 435   * Sets focus to the editbox as well
 436   */
 437  function initChangeCheck(msg){
 438      if(!document.getElementById){ return false; }
 439      // add change check for links
 440      var links = document.getElementsByTagName('a');
 441      for(var i=0; i < links.length; i++){
 442          if(links[i].className.indexOf('JSnocheck') == -1){
 443              links[i].onclick = function(){
 444                                      var rc = changeCheck(msg);
 445                                      if(window.event) window.event.returnValue = rc;
 446                                      return rc;
 447                                 };
 448          }
 449      }
 450      // add change check for forms
 451      var forms = document.forms;
 452      for(i=0; i < forms.length; i++){
 453          if(forms[i].className.indexOf('JSnocheck') == -1){
 454              forms[i].onsubmit = function(){
 455                                      var rc = changeCheck(msg);
 456                                      if(window.event) window.event.returnValue = rc;
 457                                      return rc;
 458                                 };
 459          }
 460      }
 461  
 462      // reset change memory var on submit
 463      var btn_save        = document.getElementById('edbtn__save');
 464      btn_save.onclick    = function(){ textChanged = false; };
 465      var btn_prev        = document.getElementById('edbtn__preview');
 466      btn_prev.onclick    = function(){ textChanged = false; };
 467  
 468      // add change memory setter
 469      var edit_text   = document.getElementById('wiki__text');
 470      edit_text.onchange = function(){
 471          textChanged = true; //global var
 472          summaryCheck();
 473      };
 474      edit_text.onkeyup  = summaryCheck;
 475      var summary = document.getElementById('edit__summary');
 476      addEvent(summary, 'change', summaryCheck);
 477      addEvent(summary, 'keyup', summaryCheck);
 478      if (textChanged) summaryCheck();
 479  
 480      // set focus
 481      edit_text.focus();
 482  }
 483  
 484  /**
 485   * Checks if a summary was entered - if not the style is changed
 486   *
 487   * @author Andreas Gohr <andi@splitbrain.org>
 488   */
 489  function summaryCheck(){
 490      var sum = document.getElementById('edit__summary');
 491      if(sum.value === ''){
 492          sum.className='missing';
 493      }else{
 494          sum.className='edit';
 495      }
 496  }
 497  
 498  
 499  /**
 500   * Class managing the timer to display a warning on a expiring lock
 501   */
 502  function locktimer_class(){
 503          this.sack     = null;
 504          this.timeout  = 0;
 505          this.timerID  = null;
 506          this.lasttime = null;
 507          this.msg      = '';
 508          this.pageid   = '';
 509  };
 510  var locktimer = new locktimer_class();
 511      locktimer.init = function(timeout,msg,draft){
 512          // init values
 513          locktimer.timeout  = timeout*1000;
 514          locktimer.msg      = msg;
 515          locktimer.draft    = draft;
 516          locktimer.lasttime = new Date();
 517  
 518          if(!$('dw__editform')) return;
 519          locktimer.pageid = $('dw__editform').elements.id.value;
 520          if(!locktimer.pageid) return;
 521  
 522          // init ajax component
 523          locktimer.sack = new sack(DOKU_BASE + 'lib/exe/ajax.php');
 524          locktimer.sack.AjaxFailedAlert = '';
 525          locktimer.sack.encodeURIString = false;
 526          locktimer.sack.onCompletion = locktimer.refreshed;
 527  
 528          // register refresh event
 529          addEvent($('dw__editform').elements.wikitext,'keyup',function(){locktimer.refresh();});
 530  
 531          // start timer
 532          locktimer.reset();
 533      };
 534  
 535      /**
 536       * (Re)start the warning timer
 537       */
 538      locktimer.reset = function(){
 539          locktimer.clear();
 540          locktimer.timerID = window.setTimeout("locktimer.warning()", locktimer.timeout);
 541      };
 542  
 543      /**
 544       * Display the warning about the expiring lock
 545       */
 546      locktimer.warning = function(){
 547          locktimer.clear();
 548          alert(locktimer.msg);
 549      };
 550  
 551      /**
 552       * Remove the current warning timer
 553       */
 554      locktimer.clear = function(){
 555          if(locktimer.timerID !== null){
 556              window.clearTimeout(locktimer.timerID);
 557              locktimer.timerID = null;
 558          }
 559      };
 560  
 561      /**
 562       * Refresh the lock via AJAX
 563       *
 564       * Called on keypresses in the edit area
 565       */
 566      locktimer.refresh = function(){
 567          var now = new Date();
 568          // refresh every minute only
 569          if(now.getTime() - locktimer.lasttime.getTime() > 30*1000){ //FIXME decide on time
 570              var params = 'call=lock&id='+encodeURIComponent(locktimer.pageid);
 571              if(locktimer.draft){
 572                  var dwform = $('dw__editform');
 573                  params += '&prefix='+encodeURIComponent(dwform.elements.prefix.value);
 574                  params += '&wikitext='+encodeURIComponent(dwform.elements.wikitext.value);
 575                  params += '&suffix='+encodeURIComponent(dwform.elements.suffix.value);
 576                  params += '&date='+encodeURIComponent(dwform.elements.date.value);
 577              }
 578              locktimer.sack.runAJAX(params);
 579              locktimer.lasttime = now;
 580          }
 581      };
 582  
 583  
 584      /**
 585       * Callback. Resets the warning timer
 586       */
 587      locktimer.refreshed = function(){
 588          var data  = this.response;
 589          var error = data.charAt(0);
 590              data  = data.substring(1);
 591  
 592          $('draft__status').innerHTML=data;
 593          if(error != '1') return; // locking failed
 594          locktimer.reset();
 595      };
 596  // end of locktimer class functions
 597  


Generated: Tue Dec 2 01:30:01 2008 Cross-referenced by PHPXref 0.7