
/**
 * betterEditor v0.2
 * A compact textarea enhancemenet for writting contributions to weblogs or wikis
 * It is not WYSIWYG, even better - it's what you code is what you get :) WYCIWYG
 *
 * Works for IE 5+ PC, and Mozilla 1.3+ all Plattforms
 * Sorry Opera and Safari are not supported until they provide functions to manipulate selected text
 *
 * Used for twoday / antville
 *
 * Copyright (c) 2003-2004 Knallgrau New Medias Solutions GmbH, Vienna - Austria
 *
 * Written by Matthias Platzer at http://knallgrau.at
 * inspired by code found at various places in the net
 * especially by HTMLArea by Mihai Bazon http://students.infoiasi.ro/~mishoo
 * 
 * Use it as you need it
 * It is distributed under a BSD style license
 */

/**
 * BetterEditor
 *   constructor
 *   Example:
 *     var textarea = document.getElementsByTagName("textarea")[0];
 *     function myConfig() {
 *        this.enableAutoCompletion = false;
 *     }
 *     var editor = new BetterEditor(textarea, myConfig);
 *     editor.activate();
 *
 * @param textarea   textarea HtmlElement 
 * @param config     config function which overwrites BetterEditor.config()
 */
function BetterEditor(textarea, customConfigFunction) {
  this.author = "Matthias Platzer AT knallgrau.at for twoday.net";
  this.copyright = "Copyright (c) 2003-2004 Knallgrau New Medias Solutions GmbH, Vienna - Austria";
  this.license = "Helma";

  this.lastUpdate = "2004-04-16";
  this.version = "0.5";
  this.history = new Array();
  this.historyNumber = 0;
  this.imgURL = "";
  this.dialogURL = "";
  
  this.textarea = (typeof textarea == "string") ? document.getElementById(textarea) : textarea;
  if (textarea.tagName.toLowerCase() != "textarea") {
    alert("textarea is not a valid textarea element");
    return;
  }
  this.saveHistoryState();

  this.config();
  if (typeof customConfigFunction == "function") {
    this.customConfig = customConfigFunction;
    this.customConfig();
  }
}


/** Helper function: replace all TEXTAREA-s in the document with HTMLArea-s. */
BetterEditor.replaceAll = function(config) {
  if (!isWinIE && !isGecko) return;
	var tas = document.getElementsByTagName("textarea");
	for (var i = tas.length - 1; i >= 0; --i) {
      if (!tas[i].getAttribute("modbettereditordisable")) { 
         new BetterEditor(tas[i], config).activate() 
      }
   }
};

/** Helper function: replaces the TEXTAREA with the given ID with HTMLArea. */
BetterEditor.replace = function(id, config) {
	var ta = document.getElementById(id);
	return ta ? (new BetterEditor(ta, config)).activate() : null;
};

BetterEditor.prototype.config = function() {
  this.enableautoCompletion = true;

  this.resetToolbarOptions();
  this.resetAutoCompletion();
  this.resetCommands();
}

BetterEditor.prototype.activate = function() {
  var editor = this;
  addEvent(this.textarea, (isWinIE) ? "keydown" : "keypress", function(evt) { editor.keyListener(evt) } );
  
  this.drawToolbar();
}

BetterEditor.prototype.drawToolbar = function() {
  var containerDiv = document.createElement("div");
  containerDiv.setAttribute("id", "betterEditorToolbar" + this.id);
  containerDiv.className = "betterEditorToolbar";

  for (var i = 0; i < this.toolbar.length; i++) {
    var button = (this.toolbar[i] == "separator") ? this.toolbarSeparator : this.commands[this.toolbar[i]];

    var toolbarElement = document.createElement("img");
    toolbarElement.setAttribute("src", this.imgURL + button.icon);
    if (this.toolbar[i] == "separator") {
      toolbarElement.setAttribute("alt", button.alt);
    } else {
      toolbarElement.setAttribute("alt", "[" + (button.alt ? button.alt : button.command) + "]");
      var ctrlTip = (button.shortcut) ? " (" + this.shortCutKey + "+" + button.shortcut.toUpperCase() + ")" : "";
      toolbarElement.setAttribute("title", button.tooltip + ctrlTip);
    }
    toolbarElement.setAttribute("width", (button.width) ? button.width : this.toolbarIconWidth);
    toolbarElement.setAttribute("height", (button.height) ? button.height : this.toolbarIconHeight);
    toolbarElement.className = button.className;
    if (i == 0) addClass(toolbarElement, "first");
    if (i == this.toolbar.length - 1) addClass(toolbarElement, "last");

    if (this.toolbar[i] != "separator") {
      toolbarElement.setAttribute("name", "betterEditorToolbarIcon" + this.toolbar[i].command);
      toolbarElement.setAttribute("command", this.toolbar[i]);
      var editor = this;
      addEvent(toolbarElement, "click", function(evt) { editor.doFormat(evt) } );
      addEvent(toolbarElement, "mouseover", function(evt) { var icon = evt.target ? evt.target : evt.srcElement; addClass(icon, "hover") } )
      addEvent(toolbarElement, "mouseout", function(evt) { var icon = evt.target ? evt.target : evt.srcElement; removeClass(icon, "hover") } )
    }
  
    containerDiv.appendChild(toolbarElement);
  }

  this.textarea.parentNode.insertBefore(containerDiv, this.textarea);
  this.toolbarDiv = containerDiv;


  this.toolbarDiv.style.width = this.textarea.offsetWidth + "px";
}

var bookMark01;

BetterEditor.prototype.getSelection = function() {
  if (document.all) {
    return this.textarea.selection;
  } else {
    return window.getSelection();
  }
}

BetterEditor.prototype.getSelectedText = function() {
  if (document.all) {
    var selection = document.selection;
    if (!selection) return "";
    if (selection.type.toLowerCase() != "text") return "";
    this.textarea.focus();
    var range = selection.createRange();
    if (range.parentElement() != this.textarea) return "";

    return range.text;

  } else {
    var selectionStart = this.textarea.selectionStart;
    var selectionEnd = this.textarea.selectionEnd;
    if (selectionStart >= this.textarea.length) return "";

    this.textarea.setSelectionRange(selectionStart, selectionEnd);
    this.textarea.focus();

    return this.textarea.value.slice(selectionStart, selectionEnd);

  }

  return "";
}


var betterEditorDialog;
var openedDialog = null;

BetterEditor.prototype.doFormat = function(evt) {
  evt = evt ? evt : window.event;
  var icon = evt.target ? evt.target : evt.srcElement;
  var command = icon.getAttribute("command");
  var editableArea = this.textarea;

  if (this.commands[command]) {
    if (this.commands[command].dialog) {
      if (betterEditorDialog) betterEditorDialog.close();
      activeBetterEditor = this;
      betterEditorDialog = window.open(this.dialogURL + this.commands[command].dialog, 'dialog', 'width=550,height=550,resizable');
      betterEditorOpenedDialog = new Object();

    } else if (this.commands[command].command) {
      this.doCommand(this.commands[command].command);

    } else if (this.commands[command].insert) {
      this.pasteText(this.commands[command].insert);

    }
  }
}

BetterEditor.prototype.doCommand = function(cmd) {
  eval("this." + cmd + "_command()");
}

BetterEditor.prototype.saveHistoryState = function() {
  if (this.historyNumber < this.history.length) {
    this.history.length = this.historyNumber;
  }
  this.history.push(this.textarea.value);
  this.historyNumber = this.history.length;
}

BetterEditor.prototype.undo_command = function() {
  if (isIE) {
    document.execCommand("undo");
  } else {
    if (this.textarea.value != this.history[this.historyNumber - 1]) {
      this.saveHistoryState();
    }
    if (this.historyNumber > 1) {
      var rememberScrollTop =  this.textarea.scrollTop;
      this.textarea.value = this.history[--this.historyNumber - 1];
      this.textarea.scrollTop = rememberScrollTop;
    }
  }
}

BetterEditor.prototype.redo_command = function() {
  if (isIE) {
    document.execCommand("redo");
  } else {
    if (this.historyNumber < this.history.length) {
      var rememberScrollTop =  this.textarea.scrollTop;
      this.textarea.value = this.history[++this.historyNumber - 1];
      this.textarea.scrollTop = rememberScrollTop;
    }
  }
}

BetterEditor.prototype.insertLink_command = function() {
  var linkURL = window.prompt((BetterEditor.I18N && BetterEditor.I18N.dialogs && BetterEditor.I18N.dialogs.insertLink) ? BetterEditor.I18N.dialogs.insertLink : "Gib hier eine vollständige URL ein:", "http://");
  if (linkURL) this.pasteText('<a href="' + linkURL + '">{text}^!</a>');
}

BetterEditor.prototype.pasteText = function(insert) {
  var selectedText = this.getSelectedText();

  var selectedTextPattern = /\{text\}/;
  var insertedText = insert.replace(selectedTextPattern, selectedText);

  var cursorPosPattern = /\^\!/;
  var cursorPos = insertedText.search(cursorPosPattern);
  insertedText = insertedText.replace(cursorPosPattern, "");

  cursorPos =  (cursorPos < 0) ? cursorPos = insertedText.length : cursorPos;
  var moveCursorBy = cursorPos - insertedText.length;

  if (isIE) {
    var selection = document.selection;
    if (!selection) return false;

    if (selection.type.toLowerCase() != "text" && selection.type.toLowerCase() != "none") return;
    this.textarea.focus();
    var range = selection.createRange();
    if (range.parentElement() != this.textarea) return false;

    range.text = insertedText;
    bookMark01 = range.getBookmark();
    range.moveStart("character", moveCursorBy);
    range.collapse(true);
    range.select();
    this.textarea.focus();

  } else {
    var selectionStart = this.textarea.selectionStart;
    var selectionEnd = this.textarea.selectionEnd;
    if (selectionStart >= this.textarea.length) return false;

    if (this.textarea.value != this.history[this.history.length - 1]) {
      this.saveHistoryState();
    }
    this.textarea.value.slice(selectionStart, selectionEnd);
    var rememberScrollTop =  this.textarea.scrollTop;
    this.textarea.value = this.textarea.value.slice(0, selectionStart) + insertedText + this.textarea.value.slice(selectionEnd);
    this.textarea.setSelectionRange(selectionStart + cursorPos, selectionStart + cursorPos);
    this.saveHistoryState();
    this.textarea.scrollTop = rememberScrollTop;
    this.textarea.focus();

  }

  return true;
}

BetterEditor.prototype.keyListener = function(evt) {
  evt = evt ? evt : window.event;
  var ele = evt.target ? evt.target : evt.srcElement;
  var keyCode = (isIE) ? evt.keyCode : evt.charCode;

  if (evt.ctrlKey && !evt.altKey) {
    for (var i in this.commands) {
      if (this.commands[i].shortcut == String.fromCharCode(keyCode).toLowerCase() && evt.shiftKey == (this.commands[i].shiftKey == true)) {
        if (this.commands[i].dialog) {
          if (betterEditorDialog) betterEditorDialog.close();
          betterEditorDialog = window.open(this.commands[i].dialog, 'dialog', 'width=550,height=550,resizable');

        } else if (this.commands[i].command) {
          this.doCommand(this.commands[i].command);

        } else if (this.commands[i].insert) {
          this.pasteText(this.commands[i].insert);

        }
        stopEvent(evt);
        return;
      }
    }
  }

  if (!this.enableautoCompletion) return;
  if (!((isIE && evt.keyCode == KEY_SPACE) || (!isIE && evt.charCode == KEY_SPACE) || evt.keyCode == KEY_TAB || evt.keyCode == KEY_ENTER)) return;

  for (var i in this.autoCompletion) {
    if (this.autoCompletion[i].key != null && this.autoCompletion[i].key != evt.keyCode) {
      continue;
    }

    if (isIE) {
      var preRange = document.selection.createRange();
      preRange.moveStart("character", -20);
      var preText = preRange.text;

      var postRange = document.selection.createRange();
      postRange.moveEnd("character", 20);
      var postText = postRange.text;

    } else {
      var selectionStart = ele.selectionStart;
      var selectionEnd = ele.selectionEnd;

      var preText = ele.value.substring(Math.max(0, selectionStart - 20), selectionEnd);
      var postText = ele.value.substring(selectionEnd, Math.min(ele.value.length, selectionEnd + 20));

    }

    if (this.autoCompletion[i].direction == "forward") {
      var searchPattern = new RegExp('^' + this.autoCompletion[i].pattern);
      var testText = postText;
    } else {
      var searchPattern = new RegExp(this.autoCompletion[i].pattern + '$');
      var testText = preText;
    }

    var match = searchPattern.exec(testText);
    if (match) {
      if (isIE) {
        var range = document.selection.createRange();
        if (this.autoCompletion[i].direction == "forward") {
          range.moveEnd("character", match[0].length);
        } else {
          range.moveStart("character", match.index - testText.length);
        }
        range.select();
      } else {
        if (this.autoCompletion[i].direction == "forward") {
          ele.setSelectionRange(selectionEnd, selectionEnd + match[0].length);
        } else {
          ele.setSelectionRange(selectionEnd - testText.length + match.index, selectionEnd);
        }
      }
      this.pasteText(this.autoCompletion[i].insert);
      stopEvent(evt);
      return;
    }
  }
}

BetterEditor.prototype.addToolbarOption = function(option, position) {
  var position = position ? parseInt(position) : this.toolbar.length;
  this.toolbar.splice(position, 0, option);
}

BetterEditor.prototype.removeToolbarOption = function(commandOrPos) {
  if (parseInt(commandOrPos) == commandOrPos) {
    var position = commandOrPos; 
  } else {
    var position = -1;
    for (var i = 0; i < this.toolbar.length; i++) {
      if (this.toolbar[i] == commandOrPos) {
        position = i;
      }
    }
  }
  if (position > -1) {
    this.toolbar.splice(position, 1);
  }
}

BetterEditor.prototype.resetToolbarOptions = function() {

  this.toolbarIconWidth = 24;
  this.toolbarIconHeight = 24;

  this.toolbarSeparator = {
    width:   8,
    height:  24,
    icon:    'http://static.twoday.net/modBetterEditor/editor_divider.gif',
    alt:     '|'
  };

  this.toolbar = [
    'bold', 'italic', 'strike', 'cite', 'separator',
    'link', 'image', 'file', 'separator',
    'ol', 'ul', 'separator' /* ,'help' */
  ]
}

/*
named object of commandObjects

@param insert     string to be inserted
                  ^! indicates the cursor position
                  {text} indicates the selected string
@param shortcut   ctrl + shortcut will call this command
@param icon       image for the toolbar
@param tooltip    diplayed when hovering the button
@param alt        used in the toolbar
@param command    function foo_command will be called
@param dialog     url for a popUpWindow

*/
BetterEditor.prototype.resetCommands = function() {
  this.commands = {
    bold: { 
      insert:     '<b>{text}^!</b>',
      shortcut:   'b',
      icon:       'http://static.twoday.net/modBetterEditor/editor_icon_bold.gif',
      tooltip:    'Fett',
      alt:        'B' },
    italic: {
      insert:     '<i>{text}^!</i>',
      shortcut:   'i',
      icon:       'http://static.twoday.net/modBetterEditor/editor_icon_italic.gif',
      tooltip:    'Kursiv',
      alt:        'i' },
    underline: {
      insert:     '<u>{text}^!</u>',
      shortcut:   'u',
      icon:       '',
      tooltip:    'Unterstrichen',
      alt:        'u' },
    strike: {
      insert:     '<strike>{text}^!</strike>',
      icon:       'http://static.twoday.net/modBetterEditor/editor_icon_strike.gif',
      tooltip:    'Durchgestrichen',
      alt:        's' },
    cite: {
      insert:     '<cite>{text}^!</cite>',
      shortcut:   '\"',
      icon:       'http://static.twoday.net/modBetterEditor/editor_icon_cite.gif',
      tooltip:    'Zitat',
      alt:        '\"' },
    ul: {
      insert:     '<ul>\n  <li>{text}^!</li>\n</ul>',
      shortcut:   '*',
      icon:       'http://static.twoday.net/modBetterEditor/editor_icon_ul.gif',
      tooltip:    'Aufzählungsliste einfügen',
      alt:        'ul' },
    ol: {
      insert:     '<ol>\n  <li>{text}^!</li>\n</ol>',
      shortcut:   '.',
      icon:       'http://static.twoday.net/modBetterEditor/editor_icon_ol.gif',
      tooltip:    'Liste einfügen',
      alt:        'ol' },
    file: {
      dialog:     'http://pezwo.twoday.net/modBetterEditorWizard/insertAsset?action=choose&type=file',
      shortcut:   'f',
      icon:       'http://static.twoday.net/modBetterEditor/editor_icon_file.gif',
      tooltip:    'Download einfügen',
      alt:        'file' },
    image: {
      dialog:     'http://pezwo.twoday.net/modBetterEditorWizard/insertAsset?action=choose&type=image',
      shortcut:   'm',
      icon:       'http://static.twoday.net/modBetterEditor/editor_icon_image.gif',
      tooltip:    'Bild einfügen',
      alt:        'img' },
    link: {
      command:    'insertLink',
      shortcut:   'k',
      icon:       'http://static.twoday.net/modBetterEditor/editor_icon_link.gif',
      tooltip:    'Link einfügen',
      alt:        'link' },
    help: {
      dialog:     'editorHelp',
      shortcut:   'h',
      icon:       'http://static.twoday.net/modBetterEditor/editor_icon_help.gif',
      tooltip:    'Hilfe',
      alt:        '?' },
    undo: {
      shortcut:   'z',
      command:    'Rückgängig machen' },
    redo: {
      shortcut:   'z',
      shiftKey:   true,
      command:    'Wieder herstellen' },
    redo2: {
      shortcut:   'y',
      command:    'Wieder herstellen' }
  }

	// initialize tooltips from the I18N module
	for (var i in this.commands) {
		if (BetterEditor.I18N && BetterEditor.I18N.tooltips && typeof BetterEditor.I18N.tooltips[i] != "undefined") {
			this.commands[i].tooltip = BetterEditor.I18N.tooltips[i];
		}
	}

  this.shortCutKey = (BetterEditor.I18N && BetterEditor.I18N.shortCutKey) ? BetterEditor.I18N.shortCutKey : "Strg";
}


/**
 * Adds an autoCompletion option
 * will add a new autoComplition option as an object
 *  Object:
 *    pattern:    required, RegExp String that has to be matched, example: '</td></tr>',
 *    insert:     required, String that will be inserted, example: '</td></tr>\n  <tr><td>^!</td></tr>'
 *                  ^! identifies the cursor position
 *    direction:  optional, 'forward'|'back', default='back', String representing the search direction, 'forward',
 *    key:        optional,  KEY_ENTER|KEY_TAB|KEY_SPACE, Number of the keyCode
 * 
 * @example
 *   editor.removeToolbarOption("bold");
 *   
 * @param option   Object
 */
BetterEditor.prototype.addAutoCompletion = function(patternOrObj, insert, direction, key) {
  if (typeof patternOrObj == "string") {
    var option = {
      pattern:   patternOrObj,
      insert:    insert,
      direction: direction,
      key:       key
    }
  } else {
    this.autoCompletion.push(patternOrObj);
  }
}


/**
 * Removes an autoCompletion Option
 * the function accepts as the first parameter a Number identifying the
 * position in the .autoCompletion Array, or a String which will match
 * an existing autoCompletion.pattern
 * 
 * @example
 *   editor.removeAutoCompletion("<a");
 *
 * @param patternOrPos   String or Number
 */
BetterEditor.prototype.removeAutoCompletion = function(patternOrPos) {
  if (parseInt(patterOrPos) == patternOrPos) {
    var position = patternOrPos; 
  } else {
    var position = -1;
    for (var i = 0; i < this.autoCompletion.length; i++) {
      if (this.autoCompletion[i].pattern == patternOrPos) {
        position = i;
      }
    }
  }
  if (position > -1) {
    this.autoCompletion.splice(position, 1);
  }
}

BetterEditor.prototype.resetAutoCompletion = function() {
  this.autoCompletion = [
    { pattern:    '<a',
      insert:     '<a href="^!"></a>' },
    { pattern:    '"></a>',
      insert:     '">^!</a>',
      direction:  'forward',
      key:        KEY_TAB  },
    { pattern:    '</a>]',
      insert:     '</a>] ^!',
      direction:  'forward',
      key:        KEY_TAB  },
    { pattern:    '</a>',
      insert:     '</a> ^!',
      direction:  'forward',
      key:        KEY_TAB  },
    { pattern:    '<img',
      insert:     '<img src="^!" alt="">' },
    { pattern:    '" alt="',
      insert:     '" alt="^!',
      direction:  'forward',
      key:        KEY_TAB  },
    { pattern:    '\n[^/ ]?table>?',
      insert:     '\n<table><tbody>\n  <tr><td>^!</td></tr>\n</tbody></table>' },
    { pattern:    '</td>',
      insert:     '</td><td>^!</td>',
      direction:  'forward',
      key:        KEY_TAB  },
    { pattern:    '</td></tr>',
      insert:     '</td></tr>\n  <tr><td>^!</td></tr>',
      direction:  'forward',
      key:        KEY_ENTER  },
    { pattern:    '---',
      insert:     '<hr />\n',
      key:        KEY_ENTER  },
    { pattern:    '\n[^/ ]?ol>?',
      insert:     '\n<ol>\n  <li>^!</li>\n</ol>\n' },
    { pattern:    '\n[^/ ]?ul>?',
      insert:     '\n<ul>\n  <li>^!</li>\n</ul>\n' },
    { pattern:    '\n\\*',
      insert:     '\n<ul>\n  <li>^!</li>\n</ul>\n',
      key:        KEY_ENTER },
    { pattern:    '\n\\*',
      insert:     '\n<ul>\n  <li>^!</li>\n</ul>\n',
      key:        KEY_TAB },
    { pattern:    '\n1\\.',
      insert:     '\n<ol>\n  <li>^!</li>\n</ol>\n',
      key:        KEY_ENTER },
    { pattern:    '\n1\\.',
      insert:     '\n<ol>\n  <li>^!</li>\n</ol>\n',
      key:        KEY_TAB },
    { pattern:    '</li>',
      insert:     '</li>\n  <li>^!</li>',
      direction:  'forward',
      key:        KEY_ENTER  },
    { pattern:    '</li>',
      insert:     '</li>\n  <li>^!</li>',
      key:        KEY_ENTER  },
    { pattern:    '\\[via',
      insert:     '[via <a href="^!"></a>]' },
    { pattern:    '\n[^/ ]?cite>?',
      insert:     '\n<cite>\n^!\n</cite>\n' },
    { pattern:    '\n[^/ ]?code>?',
      insert:     '\n<pre><code>\n^!\n</code></pre>\n' },
    { pattern:    '\n"',
      insert:     '\n<cite>\n^!\n</cite>\n',
      key:        KEY_ENTER  },
    { pattern:    '<\% image',
      insert:     '<\% image name="^!" %>' },
    { pattern:    '<\% file',
      insert:     '<\% file name="^!" %>' },
    { pattern:    '" %>',
      insert:     '" %>^!',
      direction:  'forward',
      key:        KEY_TAB  }
  ]
}
