Ingy döt Net - Wikiwyg-0.12
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 = {
imagesLocation: '../../images/',
javascriptLocation: '../../lib/',
doubleClickToEdit: true
}
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(
'href', /action=edit/,
function() {
myWikiwyg.editMode();
return false;
}
);
}
// You'll likely want to sublass Wikiwyg for your application.
// Wikiwyg will work fine on its own but some behavior is undefined.
proto = new Subclass('Wikiwyg.MySubclass', 'Wikiwyg');
proto.modeClasses = [
'Wikiwyg.Wysiwyg',
'Wikiwyg.Wikitext.MySubclass',
'Wikitext.Preview'
];
proto.saveChanges = function() {
// code to save edit changes to your system
}
// A subclass to customize Wikiwyg.Wikitext for your wiki variation
proto = new Subclass('Wikiwyg.Wikitext.MySubclass', '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
* Works in Internet Explorer and Mozilla browsers.
* 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 the
modeClasses property.
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
To create a new subclass, Wikiwyg provides a special constructor called Subclass
. It takes two string arguments: the name of the new class, and optionally the name of the parent class. Subclass
provides each object with two extra attributes: classname
and superfunc
. Here is a typical usage in Wikiwyg:
proto = new Subclass('Wikiwyg.Wikitext.Mine', 'Wikiwyg.Wikitext');
proto.markupRules = {
bold: ['bound_phrase', "'''", "'''"],
italic: ['bound_phrase', "''", "''"]
};
proto.enableThis = function() {
this.superfunc('enableThis').call(this); // Call the base enableThis
this.textarea.height = '400px';
// alert(this.classname + ' is all set up!');
}
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 Internet Explorere 6.0 and in Gecko browsers like Mozilla Firefox. 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. He 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>
Casey West <casey@geeknest.com>
Chris Dent <cdent@burningchrome.com>
Matt Liggett <mml@pobox.com>
Ryan King <rking@panoptic.com>
Dave Rolsky <autarch@urth.org>
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> Casey West <casey@geeknest.com> Chris Dent <cdent@burningchrome.com> Matt Liggett <mml@pobox.com> Ryan King <rking@panoptic.com> Dave Rolsky <autarch@urth.org> 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 =============================================================================*/ /*============================================================================== Subclass - this can be used to create new classes =============================================================================*/ Subclass = function(name, base) { if (!name) die("Can't create a subclass without a name"); var parts = name.split('.'); var subclass = window; for (var i = 0; i < parts.length; i++) { if (! subclass[parts[i]]) subclass[parts[i]] = function() {}; subclass = subclass[parts[i]]; } if (base) { var baseclass = eval('new ' + base + '()'); subclass.prototype = baseclass; subclass.prototype.baseclass = base; subclass.prototype.superfunc = Subclass.generate_superfunc(); } subclass.prototype.classname = name; return subclass.prototype; } Subclass.generate_superfunc = function() { return function(func) { var p; for (var b = this.baseclass; b; b = p.baseclass) { p = eval(b + '.prototype'); if (p[func] && p[func] != this[func]) return p[func]; } die( "No superfunc function for: " + func + "\n" + "baseclass was: " + this.baseclass + "\n" + "caller was: " + arguments.callee.caller ); } } /*============================================================================== Wikiwyg - Primary Wikiwyg base class =============================================================================*/ // Constructor and class methods proto = new Subclass('Wikiwyg'); Wikiwyg.VERSION = '0.12'; // Browser support properties Wikiwyg.ua = navigator.userAgent.toLowerCase(); Wikiwyg.is_ie = ( Wikiwyg.ua.indexOf("msie") != -1 && Wikiwyg.ua.indexOf("opera") == -1 && Wikiwyg.ua.indexOf("webtv") == -1 ); Wikiwyg.is_gecko = ( Wikiwyg.ua.indexOf('gecko') != -1 && Wikiwyg.ua.indexOf('safari') == -1 ); Wikiwyg.is_safari = ( Wikiwyg.ua.indexOf('safari') != -1 ); Wikiwyg.is_opera = ( Wikiwyg.ua.indexOf('opera') != -1 ); Wikiwyg.browserIsSupported = ( Wikiwyg.is_gecko || Wikiwyg.is_ie ); // Wikiwyg environment setup public methods proto.createWikiwygArea = function(div, config) { this.set_config(config); this.initializeObject(div, config); }; proto.config = { javascriptLocation: '/doc/i/in/ingy/Wikiwyg/012/lib/lib/index.html', doubleClickToEdit: false, toolbarClass: 'Wikiwyg.Toolbar', modeClasses: [ 'Wikiwyg.Wysiwyg', 'Wikiwyg.Wikitext', 'Wikiwyg.Preview' ] }; proto.initializeObject = function(div, config) { if (! Wikiwyg.browserIsSupported) return; if (this.enabled) return; this.enabled = true; this.div = div; this.divHeight = this.div.offsetHeight; if (!config) config = {}; this.mode_objects = {}; for (var i = 0; i < this.config.modeClasses.length; i++) { var class_name = this.config.modeClasses[i]; var mode_object = eval('new ' + class_name + '()'); mode_object.wikiwyg = this; mode_object.set_config(config[mode_object.classtype]); mode_object.initializeObject(); this.mode_objects[class_name] = mode_object; if (! this.first_mode) { this.first_mode = mode_object; } } if (this.config.toolbarClass) { var class_name = this.config.toolbarClass; this.toolbarObject = eval('new ' + class_name + '()'); this.toolbarObject.wikiwyg = this; this.toolbarObject.set_config(config.toolbar); this.toolbarObject.initializeObject(); this.placeToolbar(this.toolbarObject.div); } // These objects must be _created_ before the toolbar is created // but _inserted_ after. for (var i = 0; i < this.config.modeClasses.length; i++) { var mode_class = this.config.modeClasses[i]; var mode_object = this.mode_objects[mode_class]; this.insert_div_before(mode_object.div); } if (this.config.doubleClickToEdit) { var self = this; this.div.ondblclick = function() { self.editMode() }; } } proto.placeToolbar = function(div) { this.insert_div_before(div); } // Wikiwyg environment setup private methods proto.set_config = function(user_config) { for (var key in this.config) if (user_config && user_config[key]) this.config[key] = user_config[key]; else if (this[key] != null) this.config[key] = this[key]; } proto.insert_div_before = function(div) { div.style.display = 'none'; if (! div.iframe_hack) { this.div.parentNode.insertBefore(div, this.div); } } // Wikiwyg actions - public interface methods proto.saveChanges = function() { alert('Wikiwyg.prototype.saveChanges not subclassed'); } // Wikiwyg actions - public methods proto.editMode = function() { // See IE, below this.current_mode = this.first_mode; this.current_mode.fromHtml(this.div.innerHTML); this.toolbarObject.resetModeSelector(); this.current_mode.enableThis(); } proto.displayMode = function() { for (var i = 0; i < this.config.modeClasses.length; i++) { var mode_class = this.config.modeClasses[i]; var mode_object = this.mode_objects[mode_class]; mode_object.disableThis(); } this.toolbarObject.disableThis(); this.div.style.display = 'block'; this.divHeight = this.div.offsetHeight; } proto.switchMode = function(new_mode_key) { var new_mode = this.mode_objects[new_mode_key]; var old_mode = this.current_mode; var self = this; new_mode.enableStarted(); old_mode.disableStarted(); old_mode.toHtml( function(html) { self.previous_mode = old_mode; new_mode.fromHtml(html); old_mode.disableThis(); new_mode.enableThis(); new_mode.enableFinished(); old_mode.disableFinished(); self.current_mode = new_mode; } ); } proto.cancelEdit = function() { this.displayMode(); } proto.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.liveUpdate = function(method, url, query, callback) { var req = new XMLHttpRequest(); var data = null; if (method == 'GET') url = url + '?' + query; else data = query; req.open(method, url); req.onreadystatechange = function() { if (req.readyState == 4 && req.status == 200) callback(req.responseText); } if (method == 'POST') { req.setRequestHeader( 'Content-Type', 'application/x-www-form-urlencoded' ); } req.send(data); } Wikiwyg.htmlUnescape = function(escaped) { // XXX Need a better way to unescape all entities return escaped .replace(/&/g, '&') .replace(/</g, '<') .replace(/>/g, '>'); } Wikiwyg.showById = function(id) { document.getElementById(id).style.visibility = 'inherit'; } Wikiwyg.hideById = function(id) { document.getElementById(id).style.visibility = 'hidden'; } Wikiwyg.changeLinksMatching = function(attribute, pattern, func) { var links = document.getElementsByTagName('a'); for (var i = 0; i < links.length; i++) { var link = links[i]; var my_attribute = link.getAttribute(attribute); if (my_attribute && my_attribute.match(pattern)) { link.setAttribute('href', '#'); link.onclick = func; } } } Wikiwyg.createElementWithAttrs = function(element, attrs, doc) { if (doc == null) doc = document; return Wikiwyg.create_element_with_attrs(element, attrs, doc); } // See IE, below Wikiwyg.create_element_with_attrs = function(element, attrs, doc) { var elem = doc.createElement(element); for (name in attrs) elem.setAttribute(name, attrs[name]); return elem; } die = function(e) { // See IE, below throw(e); } String.prototype.times = function(n) { return n ? this + this.times(n-1) : ""; } /*============================================================================== Base class for Wikiwyg classes =============================================================================*/ proto = new Subclass('Wikiwyg.Base'); proto.set_config = function(user_config) { for (var key in this.config) { if (user_config != null && user_config[key] != null) this.merge_config(key, user_config[key]); else if (this[key] != null) this.merge_config(key, this[key]); else if (this.wikiwyg.config[key] != null) this.merge_config(key, this.wikiwyg.config[key]); } } proto.merge_config = function(key, value) { if (value instanceof Array) { this.config[key] = value; } // cross-browser RegExp object check else if (typeof value.test == 'function') { this.config[key] = value; } else if (value instanceof Object) { if (!this.config[key]) this.config[key] = {}; for (var subkey in value) { this.config[key][subkey] = value[subkey]; } } else { this.config[key] = value; } } /*============================================================================== Base class for Wikiwyg Mode classes =============================================================================*/ proto = new Subclass('Wikiwyg.Mode', 'Wikiwyg.Base'); proto.enableThis = function() { this.div.style.display = 'block'; this.display_unsupported_toolbar_buttons('none'); this.wikiwyg.toolbarObject.enableThis(); this.wikiwyg.div.style.display = 'none'; } proto.display_unsupported_toolbar_buttons = function(display) { if (!this.config) return; var disabled = this.config.disabledToolbarButtons; if (!disabled || disabled.length < 1) return; var toolbar_div = this.wikiwyg.toolbarObject.div; var toolbar_buttons = toolbar_div.childNodes; for (var i in disabled) { var action = disabled[i]; for (var i in toolbar_buttons) { var button = toolbar_buttons[i]; var src = button.src; if (!src) continue; if (src.match(action)) { button.style.display = display; break; } } } } proto.enableStarted = function() {} proto.enableFinished = function() {} proto.disableStarted = function() {} proto.disableFinished = function() {} proto.disableThis = function() { this.display_unsupported_toolbar_buttons('inline'); this.div.style.display = 'none'; } proto.process_command = function(command) { if (this['do_' + command]) this['do_' + command](command); } proto.enable_keybindings = function() { // See IE if (!this.key_press_function) { this.key_press_function = this.get_key_press_function(); this.get_keybinding_area().addEventListener( 'keypress', this.key_press_function, true ); } } proto.get_key_press_function = function() { var self = this; return function(e) { if (! e.ctrlKey) return; var key = String.fromCharCode(e.charCode).toLowerCase(); var command = ''; switch (key) { case 'b': command = 'bold'; break; case 'i': command = 'italic'; break; case 'u': command = 'underline'; break; case 'd': command = 'strike'; break; case 'l': command = 'link'; break; }; if (command) { e.preventDefault(); e.stopPropagation(); self.process_command(command); } }; } proto.get_edit_height = function() { var height = parseInt( this.wikiwyg.divHeight * this.config.editHeightAdjustment ); var min = this.config.editHeightMinimum; return height < min ? min : height; } proto.setHeightOf = function(elem) { elem.height = this.get_edit_height() + 'px'; } /*============================================================================== Support for Internet Explorer in Wikiwyg =============================================================================*/ if (Wikiwyg.is_ie) { Wikiwyg.create_element_with_attrs = function(element, attrs, doc) { var str = ''; // Note the double-quotes (make sure your data doesn't use them): for (name in attrs) str += ' ' + name + '="' + attrs[name] + '"'; return doc.createElement('<' + element + str + '>'); } die = function(e) { alert(e); throw(e); } proto = Wikiwyg.Mode.prototype; proto.enable_keybindings = function() {} } // end of global if statement for IE overrides