Ingy döt Net - Wikiwyg-0.10

Documentation | Source

NAME

Wikiwyg - Turn any HTML div into a wysiwyg and wikitext edit area.

SYNOPSIS

    // This code enables a div to be a Wikiwyg area
    var myDiv = document.getElementById('some_id');
    var myConfig = {
        baseUri: '../../',        // location of `images` directory
        doubleClickToEdit: true   // doubleclick on div turns on Wikiwyg
    }
    var myWikiwyg = new Wikiwyg.MySubclass();

    // Attempt to enable Wikiwyg for some div
    myWikiwyg.createWikiwygArea(myDiv, myConfig);
    
    // Change some link(s) to turn on the wikiwyg area
    if (myWikiwyg.enabled) {
        Wikiwyg.changeLinksMatching(/action=edit/);
    }


    // You'll likely want to sublass Wikiwyg for your application.
    // Wikiwyg will work fine on its own but some behavior is undefined.
    Wikiwyg.MySubclass = function() {}
    Wikiwyg.MySubclass.prototype = new Wikiwyg();

    Wikiwyg.MySubclass.prototype.modeList = [
        'Wikiwyg.Wysiwyg',
        'Wikiwyg.Wikitext.MySublass',
        'Wikitext.Preview'
    ];

    Wikiwyg.MySublass.prototype.saveChanges = function() {
        // code to save edit changes to your system
    }

    // A subclass to customize Wikiwyg.Wikitext for your wiki variation
    Wikiwyg.Wikitext.MySubclass = function() {}
    Wikiwyg.Wikitext.MySubclass.prototype = new Wikiwyg.Wikitext();
    // etc...

DESCRIPTION

Wikiwyg is a Javascript library that can be easily integrated into any wiki or blog software. It offers the user multiple ways to edit/view a piece of content:

    * Wysiwyg mode - Simple, HTML, Design Mode editing.
    * Wikitext mode - Standard, Wiki, Text Area editing.
    * Preview mode - Display mode without saving changes.

Wikiwyg allows you to switch between modes, delegating some of the processing to the server when necessary.

FEATURES

    * Can lay over any html div and be hooked into the existing edit buttons
      provided by the system.
    * Gracefully falls back to existing functionality if browser does not
      support Wikiwyg.
    * Subclassable to the environment it is being integrated into.
    * Configurable to tweak basic options.
    * Can edit multiple divs on the same page at the same time.
    * Instantaneous switch from view to edit.
    * Implemented as a clean OO library where each enabled div is a Wikiwyg
      object.
    * Toolbar does the right thing in both Wysiwyg and Wikitext modes.
    * Control key shortcuts apply styles without using the toolbar.
    * Adding new modes is as simple as adding new classes to modeList.

PROJECT GOALS

Wikiwyg wants to be a wysiwyg and also traditional editor for preexisting wiki and weblog software packages. The initial targets are the Kwiki wiki project and Socialtext Workspace(tm).

Wikiwyg should become the new TEXTAREA for applications that want more advanced input but don't want to rearchitect their infrastructure.

Wikiwyg is simple to integrate into existing software. In many cases it should require no changes to the server side.

EXTENDING

Wikiwyg uses CamelCase identifiers for public methods and attributes and lower_case_with_underscore identifiers for private methods and attributes.

Nothing is really private in Javascript but the idea is that "private" identifiers are Subject to Change. If you need to override private stuff, please go right ahead; then consider notifying the author of your use case, so that things can be refactored to use public methods in a subsequent release.

On the other hand you are highly encouraged to override public methods. That's why they are public :)

For example, to change the layout of the toolbar buttons, you would define your own setToolbarButtons method (after loading the Wikiwyg library).

    Wikiwyg.Toolbar.prototype.setToolbarButtons = function() {
        this.add_toolbar_button('foo');
        ...
    }

NOTE: Creating a flexible library for something like Wikiwyg is a challenge but it is entirely possible. Avoid the temptation to fork the code and instead learn to work with it and contribute back to the project. That way as Wikiwyg matures, so will your integration of it.

SUBCLASSING

NOTE: Javascript doesn't have classes and subsclasses per se, but you can fake them and get encapsulation and inheritance benefits. So please check your purist hat at the front desk...

Wikiwyg is intended to be subclassed. The nature of this beast is that it can only do so much for you. This is your system after all, and Wikiwyg doesn't know all the ins and outs of it. Wikiwyg provides methods for the things it wants to do conceptually and leaves it up to you to implement the ones that are specific to your system.

Wikiwyg is actually comprised of several classes (even though they live in one module/file):

    * Wikiwyg -- the main driver class
    * Wikiwyg.Toolbar -- the class for creating/handling the toolbar
    * The Mode Classes:
    ** Wikiwyg.Wysiwyg -- the DesignMode wysiwyg editor
    ** Wikiwyg.Wikitext -- textarea editor with wiki syntax
    ** Wikiwyg.Preview -- an html preview of your changes

You can specify any mode classes you wish with the Wikiwyg.modeList attribute. This is often done in your subclass of Wikiwyg.

The toolbar class is specified with the Wikiwyg.toolbarClass property.

RESOURCES

    * http://www.wikiwyg.net/
    * http://wiki.wikiwyg.net/
    * http://demo.wikiwyg.net/
    * http://www.openjsan.org/
    * wikiwyg-dev@wikiwyg.net

BUGS AND CAVEATS

Wikiwyg currently only works in Gecko browsers like Mozilla Firefox. IE is not currently supported but will be very soon. Other browsers like Safari and Opera will hopefully be supported later.

CREDITS

The Wikiwyg library was written almost from scratch after playing with the code from RTE and WikiEdit. Wikiwyg differs highly in that it is a pure Javascript implementation, and is completely object oriented.

Socialtext

Socialtext is responsible for starting the Wikiwyg effort and putting it into the open for others to use.

Brian Ingerson

Brian started the Wikiwyg project. is currently an employee of Socialtext and also the author of the Kwiki wiki software (http://www.kwiki.org).

Chris Dent

Chris did all the work that makes the Wikitext mode toolbar so smart and context sensitive.

See: http://www.socialtext.com

Roman "Kukutz" Ivanov <thingol@mail.ru>

Roman wrote WikiEdit which is basically a textarea toolbar. http://wackowiki.com/WikiEdit

Kevin Roth

Kevin wrote the cross-browser rich-text editor (RTE). http://www.kevinroth.com/rte/demo.htm

AUTHORS

    Brian Ingerson <ingy@cpan.org>
    Chris Dent <cdent@burningchrome.com>
    Dave Rolsky <autarch@urth.org>
    Matt Liggett <mml@pobox.com>

COPYRIGHT

    Copyright (c) 2005 Socialtext Corporation 
    655 High Street
    Palo Alto, CA 94301 U.S.A.
    All rights reserved.

Wikiwyg is free software.

This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.

This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.

    http://www.gnu.org/copyleft/lesser.txt
/*==============================================================================
Wikiwyg - Turn any HTML div into a wikitext /and/ wysiwyg edit area.

DESCRIPTION:

Wikiwyg is a Javascript library that can be easily integrated into any
wiki or blog software. It offers the user multiple ways to edit/view a
piece of content: Wysiwyg, Wikitext, Raw-HTML and Preview.

The library is easy to use, completely object oriented, configurable and
extendable.

See the Wikiwyg documentation for details.

AUTHORS:

    Brian Ingerson <ingy@cpan.org>
    Chris Dent <cdent@burningchrome.com>
    Dave Rolsky <autarch@urth.org>
    Matt Liggett <mml@pobox.com>

COPYRIGHT:

    Copyright (c) 2005 Socialtext Corporation 
    655 High Street
    Palo Alto, CA 94301 U.S.A.
    All rights reserved.

Wikiwyg is free software. 

This library is free software; you can redistribute it and/or modify it
under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or (at
your option) any later version.

This library is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
General Public License for more details.

    http://www.gnu.org/copyleft/lesser.txt

==============================================================================*/

/*==============================================================================
Developer's Notes:

TODO:
    * Release code to JSAN
    * Integrate back into a working Kwiki
    ** With a Wikiwyg.Kwiki subclass
    * Support IE
    * Merge config (default, class attributes, custom)
    * Finish toolbar
    ** Insert Link
    ** Insert Table
    ** Insert Image
    * add Rawhtml mode
    * Make toolbar button width configurable (for small text areas)
    * change escape_plus() to encodeURIComponent() (per rking)

REFACTOR:
    * Find a good way to call super methods

BUGS:
    * Control-key shortcuts don't work after preview (sometimes)

IDEAS:
    * Spike an integration with TiddlyWiki
    * Make a greasemonkey overlay for Flickr comments 
    * Spike integration with these wikis:
    ** Media Wiki
    ** TWiki
    ** MoinMoin
==============================================================================*/

/*==============================================================================
Wikiwyg - Primary Wikiwyg base class
==============================================================================*/

// Constructor and class methods
Wikiwyg = function() {};
Wikiwyg.VERSION = '0.10';

Wikiwyg.is_gecko = (navigator.userAgent.toLowerCase().indexOf("gecko") != -1);
Wikiwyg.browserIsSupported = Wikiwyg.is_gecko;

// Config attributes
// Override these values in a subclass to control which modes
Wikiwyg.prototype.toolbarClass = 'Wikiwyg.Toolbar';
Wikiwyg.prototype.modeList = [ 
    'Wikiwyg.Wysiwyg',
    'Wikiwyg.Wikitext',
    'Wikiwyg.Preview'
];

// Wikiwyg environment setup public methods
Wikiwyg.prototype.createWikiwygArea = function(div, config) {
    this.set_config(config);
    this.initializeObject(div);
};

Wikiwyg.prototype.defaultConfig = function() {
    return {
        baseUri: '',
        doubleClickToEdit: false,
    };
}

Wikiwyg.prototype.initializeObject = function(div) {
    if (! Wikiwyg.browserIsSupported) return;
    if (div.wikiwyg_enabled) return;
    div.wikiwyg_enabled = true;
    this.div = div;
    this.divHeight = this.div.clientHeight;

    this.mode_objects = {};
    for (var i in this.modeList) {
        var class_name = this.modeList[i];
        var mode_object = eval('new ' + class_name + '()');
        mode_object.initializeObject(this);
        this.mode_objects[class_name] = mode_object;
        if (! this.first_mode) {
            this.first_mode = mode_object;
        }
    }

    if (this.toolbarClass) {
        this.toolbarObject = eval('new ' + this.toolbarClass + '()');
        this.toolbarObject.initializeObject(this);
        this.insert_div_before(this.toolbarObject.div);
    }

    // These objects must be _created_ before the toolbar is created
    // but _inserted_ after.
    for (var key in this.mode_objects) {
        var mode_object = this.mode_objects[key];
        this.insert_div_before(mode_object.div);
    }

    if (this.config.doubleClickToEdit) {
        var self = this;
        this.div.ondblclick = function() { self.editMode() }; 
    }
}

// Wikiwyg environment setup private methods
Wikiwyg.prototype.set_config = function(user_config) {
    this.config = this.defaultConfig();
    if (user_config == null) return;
    for (var key in this.config) {
        if (user_config[key] != null)
            this.config[key] = user_config[key];
    }
}

Wikiwyg.prototype.insert_div_before = function(div) {
    div.style.display = 'none';
    this.div.parentNode.insertBefore(div, this.div);
}

// Wikiwyg actions - public interface methods
Wikiwyg.prototype.saveChanges = function() {
    alert('Wikiwyg.prototype.saveChanges not subclassed');
}

// Wikiwyg actions - public methods
Wikiwyg.prototype.editMode = function() {
    this.current_mode = this.first_mode;
    this.current_mode.enableThis();
    this.current_mode.fromHtml(this.div.innerHTML);
    this.toolbarObject.resetModeSelector();
}

Wikiwyg.prototype.displayMode = function() {
    for (var key in this.mode_objects) {
        this.mode_objects[key].disableThis();
    }
    this.toolbarObject.disableThis();
    this.div.style.display = 'block';
    this.divHeight = this.div.clientHeight;
}

Wikiwyg.prototype.switchMode = function(new_mode_key) {
    var new_mode = this.mode_objects[new_mode_key];
    var old_mode = this.current_mode;
    var self = this;
    old_mode.toHtml(
        function(html) {
            new_mode.fromHtml(html);
            old_mode.disableThis();
            new_mode.enableThis();
            self.current_mode = new_mode;
        }
    );
}

Wikiwyg.prototype.cancelEdit = function() {
    this.displayMode();
}

Wikiwyg.prototype.fromHtml = function(html) {
    this.div.innerHTML = html;
}

// Class level helper methods
Wikiwyg.unique_id_base = 0;
Wikiwyg.createUniqueId = function() {
    return 'wikiwyg_' + Wikiwyg.unique_id_base++;
}

Wikiwyg.get_live_update = function(url, query_string, callback) {
    var req = new XMLHttpRequest()
    req.open('GET', url + '?' + query_string)
    req.onreadystatechange = function() {
        if (req.readyState == 4 && req.status == 200) {
            callback(req.responseText)
        }
    }
    req.send(null)
}

Wikiwyg.live_update = function(url, postdata, callback) {
    var req = new XMLHttpRequest()
    req.open('POST', url)
    req.onreadystatechange = function() {
        if (req.readyState == 4 && req.status == 200) {
            callback(req.responseText)
        }
    }
    req.setRequestHeader("Content-Type", "application/x-www-form-urlencoded")
    req.send(postdata)
}

Wikiwyg.escape_plus = function(string) {
    string = string.toString()
    string = escape(string)
    string = string.replace(/\+/g, '%2B');
    return string
}

/*==============================================================================
This class provides toolbar support
==============================================================================*/
Wikiwyg.Toolbar = function() {}
Wikiwyg.Toolbar.prototype.initializeObject = function(wikiwyg) {
    this.wikiwyg = wikiwyg;
    this.div = document.createElement('div');
    this.div.innerHTML = '\
<table class="wikiwyg_background" \
       cellpadding="0" cellspacing="0" width="100%"> \
<tr><td colspan="100"></td></tr><tr></tr></table>';

    var trs = this.div.getElementsByTagName('tr');
    this.control_row = trs[0];
    this.button_row = trs[1];

    this.control_cell =
        this.control_row.getElementsByTagName('td')[0];

    this.setControls();
    this.setButtons();

    // XXX - Hack to get buttons flush left - use css
    var td = document.createElement('td');
    td.setAttribute('width', '100%');
    this.button_row.insertBefore(td, null);
}

Wikiwyg.Toolbar.prototype.enableThis = function() {
    this.div.style.display = 'block';
}

Wikiwyg.Toolbar.prototype.disableThis = function() {
    this.div.style.display = 'none';
}

Wikiwyg.Toolbar.prototype.setControls = function() {
    this.addControlItem('Save', 'saveChanges');
    this.addControlItem('Cancel', 'cancelEdit');
    this.addModeSelector();
}

Wikiwyg.Toolbar.prototype.setButtons = function() {
    this.add_styles();
    this.add_button('bold');
    this.add_button('italic');
    this.add_button('underline');
    this.add_button('strike', 'strikethrough', 'Strike Through');
    this.add_separator();
    this.add_button('hr', 'inserthorizontalrule', 'Horizontal Rule');
    this.add_separator();
    this.add_button('ordered', 'insertorderedlist',
                            'Ordered list');
    this.add_button('unordered', 'insertunorderedlist',
                            'Unordered list');
    this.add_separator();
    this.add_button('outdent');
    this.add_button('indent');
    this.add_separator();
    this.add_help_button();
}

Wikiwyg.Toolbar.prototype.add_button = function(type, command, label) {
    if (!command)
        command = type;
    if (!label)
        label = type;
    var td = document.createElement('td');
    var base = this.wikiwyg.config.baseUri;
    td.innerHTML =
      '<img class="wikiwyg_img" src="' + base + 'images/' + type + '.gif" \
      width="25" height="24" \
      alt="' + label + '" title="' + label + '">';

    var self = this;
    td.onclick = function() {
        self.wikiwyg.current_mode.process_command(command, null);
    };
    this.button_row.insertBefore(td, null);
}

Wikiwyg.Toolbar.prototype.add_help_button = function() {
    var td = document.createElement('td');
    var base = this.wikiwyg.config.baseUri;
    td.innerHTML = 
      '<a target="wikiwyg-about" href="http://www.wikiwyg.net/about/"> \
      <img class="wikiwyg_img" src="' + base + 'images/help.gif" \
      width="20" height="20" alt="Help" title="Help"></a>';

    var self = this;
    td.onclick = function() {
        var wikiwyg = self.wikiwyg;
        wikiwyg.current_mode.process_command(command, null);
    };
    this.button_row.insertBefore(td, null);
}

Wikiwyg.Toolbar.prototype.add_separator = function() {
    var td = document.createElement('td');
    var base = this.wikiwyg.config.baseUri;
    td.innerHTML = '\
<img class="wikiwyg_separator" src="' + base + 'images/blackdot.gif"\
     width="1" height="20" border="0" alt="">';
    this.button_row.insertBefore(td, null);
}

Wikiwyg.Toolbar.prototype.addControlItem = function(text, method) {
    var span = document.createElement('span');
    span.setAttribute('class', 'wikiwyg_control_link');

    var link = document.createElement('a');
    span.appendChild(link);

    link.setAttribute('href', '#');
    link.innerHTML = text;
    
    var self = this;
    link.onclick = function() { eval('self.wikiwyg.' + method + '()'); return false };

    this.control_cell.insertBefore(span, null);
}

Wikiwyg.Toolbar.prototype.resetModeSelector = function() {
    this.firstModeRadio.click();
}

Wikiwyg.Toolbar.prototype.addModeSelector = function() {
    var div = document.createElement('span');

    var radio_name = Wikiwyg.createUniqueId();
    for (var i in this.wikiwyg.modeList) {
        var class_name = this.wikiwyg.modeList[i];
        var mode_object = this.wikiwyg.mode_objects[class_name];
        var radio = document.createElement('input');
        if (!this.firstModeRadio)
            this.firstModeRadio = radio;

        radio.setAttribute('type', 'radio');
        radio.setAttribute('name', radio_name);
        var radio_id = Wikiwyg.createUniqueId();
        radio.setAttribute('id', radio_id);
        radio.setAttribute('value', mode_object.className);

        if (i == 0) {
            radio.setAttribute('checked', 'checked');
        }

        var self = this;
        radio.onclick = function() { 
            self.wikiwyg.switchMode(this.value);
        };

        var label = document.createElement('label');
        label.setAttribute('for', radio_id);
        label.innerHTML = mode_object.modeDescription;

        div.appendChild(radio);
        div.appendChild(label);
    }
    this.control_cell.insertBefore(div, null);
}

Wikiwyg.Toolbar.prototype.add_styles = function() {
    var td = document.createElement('td');
    td.innerHTML = '\
<select> \
<option value="">[Style]</option> \
<option value="p">Paragraph &lt;p&gt;</option> \
<option value="h1">Heading 1 &lt;h1&gt;</option> \
<option value="h2">Heading 2 &lt;h2&gt;</option> \
<option value="h3">Heading 3 &lt;h3&gt;</option> \
<option value="h4">Heading 4 &lt;h4&gt;</option> \
<option value="h5">Heading 5 &lt;h5&gt;</option> \
<option value="h6">Heading 6 &lt;h6&gt;</option> \
<option value="pre">Formatted &lt;pre&gt;</option> \
</select> \
';
    this.styleSelect = td.getElementsByTagName('select')[0];
    var self = this;
    this.styleSelect.onchange = function() { 
        self.wikiwyg.current_mode.set_style(this.value) 
    };
    this.button_row.insertBefore(td, null);
}

/*==============================================================================
Base class for Wikiwyg Mode classes
==============================================================================*/
Wikiwyg.Mode = function() {}

Wikiwyg.Mode.prototype.enableThis = function() {
    this.div.style.display = 'block';
    this.wikiwyg.toolbarObject.enableThis();
    this.setupThis();
    this.wikiwyg.div.style.display = 'none';
}

Wikiwyg.Mode.prototype.disableThis = function() {
    this.div.style.display = 'none';
}

Wikiwyg.Mode.prototype.setupThis = function() {}

/*==============================================================================
This mode supports a DesignMode wysiwyg editor with toolbar buttons
==============================================================================*/
Wikiwyg.Wysiwyg = function() {}

Wikiwyg.Wysiwyg.prototype = new Wikiwyg.Mode();

Wikiwyg.Wysiwyg.prototype.className = 'Wikiwyg.Wysiwyg';
Wikiwyg.Wysiwyg.prototype.modeDescription = 'Wysiwyg';

Wikiwyg.Wysiwyg.prototype.initializeObject = function(wikiwyg) {
    this.wikiwyg = wikiwyg;
    this.div = document.createElement('div');
    this.div.innerHTML =
        '<iframe width="100%"><html><body></body></html></iframe>';
    this.edit_iframe = this.div.getElementsByTagName('iframe')[0];
}

Wikiwyg.Wysiwyg.prototype.fromHtml = function(html) {
    this.edit_iframe.contentWindow.document.body.innerHTML = html;
}

Wikiwyg.Wysiwyg.prototype.toHtml = function(func) {
    var html = this.edit_iframe.contentWindow.document.body.innerHTML;
    func(html);
}

Wikiwyg.Wysiwyg.prototype.setupThis = function() {
    this.edit_iframe.style.height =
      this.wikiwyg.divHeight +
      this.wikiwyg.toolbarObject.div.clientHeight;

    var doc = this.edit_iframe.contentWindow.document;         
    this.edit_iframe.contentDocument.designMode = "on";
    doc.addEventListener("keypress", this.get_key_press_function(), true);
}

Wikiwyg.Wysiwyg.prototype.process_command = function(command, option) {
    var win = this.edit_iframe.contentWindow;         
    try {
        win.focus();
        win.document.execCommand(command, false, option);
        win.focus();
    } 
    catch (e) {
    }
}

Wikiwyg.Wysiwyg.prototype.get_key_press_function = function() {
    var self = this;
    return function(evt) {
        last_key = evt;
        if (! evt.ctrlKey) return;
        var key = String.fromCharCode(evt.charCode).toLowerCase();
        var cmd = '';
        switch (key) {
            case 'b': cmd = "bold"; break;
            case 'i': cmd = "italic"; break;
            case 'u': cmd = "underline"; break;
            case 'd': cmd = "strikethrough"; break;
        };

        if (cmd) {
            self.process_command(cmd, null);
            evt.preventDefault();
            evt.stopPropagation();
        }
    };
}

Wikiwyg.Wysiwyg.prototype.set_style = function(style_name) {
    var idx = this.wikiwyg.toolbarObject.styleSelect.selectedIndex;
    // First one is always a label
    if (idx != 0)
        this.process_command('formatblock', style_name);
    this.wikiwyg.toolbarObject.styleSelect.selectedIndex = 0;
}

/*==============================================================================
This mode supports a textarea editor with toolbar buttons.
==============================================================================*/
Wikiwyg.Wikitext = function() {}

Wikiwyg.Wikitext.prototype = new Wikiwyg.Mode();

// XXX - we hate this but cannot find a way to get this dynamically
Wikiwyg.Wikitext.prototype.className = 'Wikiwyg.Wikitext';
Wikiwyg.Wikitext.prototype.modeDescription = 'Wikitext';

Wikiwyg.Wikitext.prototype.setupThis = function() {
    this.textarea.style.width = '100%';
    this.textarea.style.height = '200px';
}

Wikiwyg.Wikitext.prototype.initializeObject = function(wikiwyg) {
    this.wikiwyg = wikiwyg;
    this.div = document.createElement('div');
    this.div.innerHTML = '<textarea></textarea>';
    this.textarea = this.div.getElementsByTagName('textarea')[0];
    this.area = this.textarea;
}

Wikiwyg.Wikitext.prototype.toHtml = function(func) {
    var wikitext = this.textarea.value;
    this.convertWikitextToHtml(wikitext, func);
}

Wikiwyg.Wikitext.prototype.fromHtml = function(html) {
    this.textarea.value = 'Loading...';
    var textarea = this.textarea;
    this.convertHtmlToWikitext(
        html, 
        function(value) { textarea.value = value }
    );
}

// These two conversion routines are for demo purposes only. They need to
// be implemented in a subclass of Wikiwyg.Wikitext.
Wikiwyg.Wikitext.prototype.convertWikitextToHtml = function(wikitext, func) {
    func('<p>The Wikitext editor was invoked...</p>\n' + this.copyhtml);
}

Wikiwyg.Wikitext.prototype.convertHtmlToWikitext = function(html, func) {
    this.copyhtml = html;
    value = '\
This default implementation cannot convert HTML to Wikitext.\n\
\n\
But here is some sample demo text anyway:\n\
\n\
* *Bold*\n\
* /Italic/\n\
* _Underline_\n\
';
    func(value);
}

Wikiwyg.Wikitext.prototype.process_command = function(command, option) {
    eval("this.do_" + command + "()");
}

Wikiwyg.Wikitext.prototype.set_style = function(style_name) {
    var idx = this.wikiwyg.toolbarObject.styleSelect.selectedIndex;
    // First one is always a label
    if (idx != 0)
        this.process_command(style_name);
    this.wikiwyg.toolbarObject.styleSelect.selectedIndex = 0;
}

Wikiwyg.Wikitext.phrase_end_re = /[\s\.\:\;\,\!\?\(\)]/;

// XXX this is getting absurd
Wikiwyg.Wikitext.prototype.find_left = function(t, selection_start, matcher) {
    var substring = t.substr(selection_start - 1, 1);
    var nextstring = t.substr(selection_start - 2, 1);
    if (selection_start == 0) 
        return selection_start;
    if (substring.match(matcher)) {
        // special case for word.word
        if ((substring != '.') || (nextstring.match(/\s/))) 
            return selection_start;
    }
    return this.find_left(t, selection_start - 1, matcher);
}  

Wikiwyg.Wikitext.prototype.find_right = function(t, selection_end, matcher) {
    var substring = t.substr(selection_end, 1);
    var nextstring = t.substr(selection_end + 1, 1);
    if (selection_end >= t.length)
        return selection_end;
    if (substring.match(matcher)) {
        // special case for word.word
        if ((substring != '.') || (nextstring.match(/\s/)))
            return selection_end;
    }
    return this.find_right(t, selection_end + 1, matcher);
}

Wikiwyg.Wikitext.prototype.getLines = function() {
    t = this.area;
    var selection_start = t.selectionStart;
    var selection_end = t.selectionEnd;

    if (selection_start == null || selection_end == null)
        return false

    var our_text = t.value.replace(/\r/g, '');
    selection = our_text.substr(selection_start,
        selection_end - selection_start);

    selection_start = this.find_right(our_text, selection_start, /[^\r\n]/);
    selection_end = this.find_left(our_text, selection_end, /[^\r\n]/);

    this.selection_start = this.find_left(our_text, selection_start, /[\r\n]/);
    this.selection_end = this.find_right(our_text, selection_end, /[\r\n]/);
    t.setSelectionRange(selection_start, selection_end);
    t.focus();

    this.start = our_text.substr(0,this.selection_start);
    this.sel = our_text.substr(this.selection_start, this.selection_end -
        this.selection_start);
    this.finish = our_text.substr(this.selection_end, our_text.length);

    return true;
}

Wikiwyg.Wikitext.prototype.alarm_on = function() {
    var area = this.area;
    var background = area.style.background;
    area.style.background = '#f88';

    function alarm_off() {
        area.style.background = background;
    }

    window.setTimeout(alarm_off, 250);
    area.focus()
}

Wikiwyg.Wikitext.prototype.getWords = function() {
    function is_insane(selection) {
        return selection.match(/\r?\n(\r?\n|\*+ |\#+ |\=+ )/);
    }   

    t = this.area;
    var selection_start = t.selectionStart;
    var selection_end = t.selectionEnd;
    if (selection_start == null || selection_end == null)
        return false;
        
    var our_text = t.value.replace(/\r/g, '');
    selection = our_text.substr(selection_start,
        selection_end - selection_start);

    selection_start = this.find_right(our_text, selection_start, /(\S|\r?\n)/);
    if (selection_start > selection_end)
        selection_start = selection_end;
    selection_end = this.find_left(our_text, selection_end, /(\S|\r?\n)/);
    if (selection_end < selection_start)
        selection_end = selection_start;

    if (is_insane(selection)) {
        this.alarm_on();
        return false;
    }

    this.selection_start =
        this.find_left(our_text, selection_start, Wikiwyg.Wikitext.phrase_end_re);
    this.selection_end =
        this.find_right(our_text, selection_end, Wikiwyg.Wikitext.phrase_end_re);

    t.setSelectionRange(this.selection_start, this.selection_end);
    t.focus();

    this.start = our_text.substr(0,this.selection_start);
    this.sel = our_text.substr(this.selection_start, this.selection_end -
        this.selection_start);
    this.finish = our_text.substr(this.selection_end, our_text.length);

    return true;
}

Wikiwyg.Wikitext.prototype.markup_is_on = function(start, finish) {
    return (this.sel.match(start) && this.sel.match(finish));
}

Wikiwyg.Wikitext.prototype.clean_selection = function(start, finish) {
    this.sel = this.sel.replace(start, '');
    this.sel = this.sel.replace(finish, '');
}

Wikiwyg.Wikitext.prototype.toggle_same_format = function(start, finish) {
    start = this.cleanRE(start);
    finish = this.cleanRE(finish);
    var start_re = new RegExp('^' + start);
    var finish_re = new RegExp(finish + '$');
    if (this.markup_is_on(start_re, finish_re)) {
        this.clean_selection(start_re, finish_re);
        return true;
    }
    return false;
}

Wikiwyg.Wikitext.prototype.cleanRE = function(string) {
    string = string.replace(/([\^\*\[\]\{\}])/g, '\\' + "$1");
    return string;
}

Wikiwyg.Wikitext.prototype.setTextandSelection = function(text, start, end) {
    this.area.value = text;
    this.area.setSelectionRange(start, end);
}

Wikiwyg.Wikitext.prototype.addMarkupWords =
function(markup_start, markup_finish, example) {
    if (this.toggle_same_format(markup_start, markup_finish)) {
        this.selection_end = this.selection_end -
            (markup_start.length + markup_finish.length);
        markup_start = '';
        markup_finish = '';
    }
    if (this.sel.length == 0) {
        if (example)
            this.sel = example;
        var text = this.start + markup_start +
            this.sel + markup_finish + this.finish;
        var start = this.selection_start + markup_start.length;
        var end = this.selection_end + markup_start.length + this.sel.length;
        this.setTextandSelection(text, start, end);
    } else {
        var text = this.start + markup_start + this.sel +
            markup_finish + this.finish;
        var start = this.selection_start;
        var end = this.selection_end + markup_start.length +
            markup_finish.length;
        this.setTextandSelection(text, start, end);
    }
    this.area.focus();
}

Wikiwyg.Wikitext.prototype.addMarkupLines = function(markup_start) {
    var start_pattern = markup_start;
    start_pattern = start_pattern.replace(/(\=+) /, '$1');
    var already_set_re = new RegExp("^" + this.cleanRE(start_pattern) + " *",
        'gm');
    var other_markup_re = /^(\=+ *|\* |# )/gm;
    if (this.sel.match(already_set_re))
        this.sel = this.sel.replace(already_set_re, '');
    else if (this.sel.match(other_markup_re))
        this.sel = this.sel.replace(other_markup_re, markup_start);
    else if (this.sel.length > 0)
        this.sel = this.sel.replace(/^(.*\S+)/gm, markup_start + '$1');
    else
        this.sel = markup_start;
    var text = this.start + this.sel + this.finish;
    var start = this.selection_start;
    var end = this.selection_start + this.sel.length;
    this.setTextandSelection(text, start, end);
    this.area.focus();
}

Wikiwyg.Wikitext.prototype.startline = function(markup_start) {
    var scroll_top = this.area.scrollTop;
    if (this.getLines())
        this.addMarkupLines(markup_start + ' ');
    this.area.scrollTop = scroll_top;
}

Wikiwyg.Wikitext.prototype.boundword = function(markup_start, markup_finish, example) {
    var scroll_top = this.area.scrollTop;
    if (markup_finish == undefined)
        markup_finish = markup_start;
    if (this.getWords())
        this.addMarkupWords(markup_start, markup_finish, example);
    this.area.scrollTop = scroll_top;
}

Wikiwyg.Wikitext.prototype.do_bold = function() {
    this.boundword('*');
}

Wikiwyg.Wikitext.prototype.do_italic = function() {
    this.boundword('/');
}

Wikiwyg.Wikitext.prototype.do_underline = function() {
    this.boundword('_');
}

Wikiwyg.Wikitext.prototype.do_strikethrough = function() {
    this.boundword('-');
}

Wikiwyg.Wikitext.prototype.do_p = function() {
}

Wikiwyg.Wikitext.prototype.do_pre = function() {
}

Wikiwyg.Wikitext.prototype.do_h1 = function() {
    this.startline('=');
}

Wikiwyg.Wikitext.prototype.do_h2 = function() {
    this.startline('==');
}

Wikiwyg.Wikitext.prototype.do_h3 = function() {
    this.startline('===');
}

Wikiwyg.Wikitext.prototype.do_h4 = function() {
    this.startline('====');
}

Wikiwyg.Wikitext.prototype.do_h5 = function() {
    this.startline('=====');
}

Wikiwyg.Wikitext.prototype.do_h6 = function() {
    this.startline('======');
}

Wikiwyg.Wikitext.prototype.do_insertorderedlist = function() {
    this.startline('#');
}

Wikiwyg.Wikitext.prototype.do_insertunorderedlist = function() {
    this.startline('*');
}

Wikiwyg.Wikitext.prototype.do_dent = function(method) {
    var scroll_top = this.area.scrollTop;
    if (! this.getLines()) {
        this.area.scrollTop = scroll_top;
        return;
    }

    if (method(this)) {
        var text = this.start + this.sel + this.finish;
        var start = this.selection_start;
        var end = this.selection_start + this.sel.length;
        this.setTextandSelection(text, start, end);
    }
    this.area.focus();
}

Wikiwyg.Wikitext.prototype.do_indent = function() {
    this.do_dent(
        function(that) {
            if (that.sel == '') return false;
            that.sel = that.sel.replace(/^(([\*\-\#])+(?=\s))/gm, '$2$1');
            that.sel = that.sel.replace(/^([\>\=])/gm, '$1$1');
            that.sel = that.sel.replace(/^([^\>\*\-\#\=\r\n])/gm, '> $1');
            that.sel = that.sel.replace(/^\={7,}/gm, '======');
            return true;
        }
    )
}

Wikiwyg.Wikitext.prototype.do_outdent = function() {
    this.do_dent(
        function(that) {
            if (that.sel == '') return false;
            that.sel = that.sel.replace(/^([\>\*\-\#\=] ?)/gm, '');
            return true;
        }
    )
}

Wikiwyg.Wikitext.prototype.do_inserthorizontalrule = function() {
    var t = this.area;
    var scroll_top = t.scrollTop;
    var selection_start = t.selectionStart;
    var text = t.value;
    this.selection_start = this.find_right(text, selection_start, /\r?\n/);
    this.selection_end = this.selection_start;
    t.setSelectionRange(this.selection_start, this.selection_start);
    t.focus();

    this.start = t.value.substr(0, this.selection_start);
    this.finish = t.value.substr(this.selection_end, t.value.length);
    var text = this.start + '\n----' + this.finish;
    var start = this.selection_start + 5;
    var end = this.selection_end + 5;
    this.setTextandSelection(text, start, end);
    t.scrollTop = scroll_top;
}

/*==============================================================================
This mode supports a preview of current changes
==============================================================================*/
Wikiwyg.Preview = function() {}

Wikiwyg.Preview.prototype = new Wikiwyg.Mode();

Wikiwyg.Preview.prototype.className = 'Wikiwyg.Preview';
Wikiwyg.Preview.prototype.modeDescription = 'Preview';

Wikiwyg.Preview.prototype.initializeObject = function(wikiwyg) {
    this.wikiwyg = wikiwyg;
    this.div = document.createElement('div');
    this.div.style.backgroundColor = 'lightyellow';
}

Wikiwyg.Preview.prototype.fromHtml = function(html) {
    this.div.innerHTML = html;
}

Wikiwyg.Preview.prototype.toHtml = function(func) {
    func(this.div.innerHTML);
}