]> CyberLeo.Net >> Repos - SourceForge/phpwiki.git/blob - themes/default/Wikiwyg/Wikiwyg.js
private
[SourceForge/phpwiki.git] / themes / default / Wikiwyg / Wikiwyg.js
1 /*==============================================================================
2 Wikiwyg - Turn any HTML div into a wikitext /and/ wysiwyg edit area.
3
4 DESCRIPTION:
5
6 Wikiwyg is a Javascript library that can be easily integrated into any
7 wiki or blog software. It offers the user multiple ways to edit/view a
8 piece of content: Wysiwyg, Wikitext, Raw-HTML and Preview.
9
10 The library is easy to use, completely object oriented, configurable and
11 extendable.
12
13 See the Wikiwyg documentation for details.
14
15 AUTHORS:
16
17     Brian Ingerson <ingy@cpan.org>
18     Casey West <casey@geeknest.com>
19     Chris Dent <cdent@burningchrome.com>
20     Matt Liggett <mml@pobox.com>
21     Ryan King <rking@panoptic.com>
22     Dave Rolsky <autarch@urth.org>
23
24 COPYRIGHT:
25
26     Copyright (c) 2005 Socialtext Corporation 
27     655 High Street
28     Palo Alto, CA 94301 U.S.A.
29     All rights reserved.
30
31 Wikiwyg is free software. 
32
33 This library is free software; you can redistribute it and/or modify it
34 under the terms of the GNU Lesser General Public License as published by
35 the Free Software Foundation; either version 2.1 of the License, or (at
36 your option) any later version.
37
38 This library is distributed in the hope that it will be useful, but
39 WITHOUT ANY WARRANTY; without even the implied warranty of
40 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
41 General Public License for more details.
42
43     http://www.gnu.org/copyleft/lesser.txt
44
45  =============================================================================*/
46
47 /*==============================================================================
48 Subclass - this can be used to create new classes
49  =============================================================================*/
50
51 Subclass = function(name, base) {
52     if (!name) die("Can't create a subclass without a name");
53
54     var parts = name.split('.');
55     var subclass = window;
56     for (var i = 0; i < parts.length; i++) {
57         if (! subclass[parts[i]])
58             subclass[parts[i]] = function() {};
59         subclass = subclass[parts[i]];
60     }
61
62     if (base) {
63         var baseclass = eval('new ' + base + '()');
64         subclass.prototype = baseclass;
65         subclass.prototype.baseclass = base;
66         subclass.prototype.superfunc = Subclass.generate_superfunc();
67     }
68     subclass.prototype.classname = name;
69     return subclass.prototype;
70 }
71
72 Subclass.generate_superfunc = function() {
73     return function(func) {
74         var p;
75         for (var b = this.baseclass; b; b = p.baseclass) {
76             p = eval(b + '.prototype');
77             if (p[func] && p[func] != this[func])
78                 return p[func];
79         }
80         die(
81             "No superfunc function for: " + func + "\n" +
82             "baseclass was: " + this.baseclass + "\n" +
83             "caller was: " + arguments.callee.caller
84         );
85     }
86 }
87
88 /*==============================================================================
89 Wikiwyg - Primary Wikiwyg base class
90  =============================================================================*/
91
92 // Constructor and class methods
93 proto = new Subclass('Wikiwyg');
94
95 Wikiwyg.VERSION = '0.12';
96
97 // Browser support properties
98 Wikiwyg.ua = navigator.userAgent.toLowerCase();
99 Wikiwyg.is_ie = (
100     Wikiwyg.ua.indexOf("msie") != -1 &&
101     Wikiwyg.ua.indexOf("opera") == -1 && 
102     Wikiwyg.ua.indexOf("webtv") == -1
103 );
104 Wikiwyg.is_gecko = (
105     Wikiwyg.ua.indexOf('gecko') != -1 &&
106     Wikiwyg.ua.indexOf('safari') == -1
107 );
108 Wikiwyg.is_safari = (
109     Wikiwyg.ua.indexOf('safari') != -1
110 );
111 Wikiwyg.is_opera = (
112     Wikiwyg.ua.indexOf('opera') != -1
113 );
114 Wikiwyg.browserIsSupported = (
115     Wikiwyg.is_gecko ||
116     Wikiwyg.is_ie
117 );
118
119 // Wikiwyg environment setup public methods
120 proto.createWikiwygArea = function(div, config) {
121     this.set_config(config);
122     this.initializeObject(div, config);
123 };
124
125 proto.config = {
126     javascriptLocation: 'Wikiwyg/',
127     doubleClickToEdit: false,
128     toolbarClass: 'Wikiwyg.Toolbar',
129     modeClasses: [ 
130         'Wikiwyg.Wysiwyg',
131         'Wikiwyg.Preview'
132     ]
133 };
134
135 proto.initializeObject = function(div, config) {
136   if (! Wikiwyg.browserIsSupported) {
137     alert('Your browser is not supported yet. The toolbar will not be activated.')
138       return;
139   }
140     if (this.enabled) return;
141     this.enabled = true;
142     this.div = div;
143     this.divHeight = this.div.offsetHeight;
144     if (!config) config = {};
145
146     this.mode_objects = {};
147     for (var i = 0; i < this.config.modeClasses.length; i++) {
148         var class_name = this.config.modeClasses[i];
149         var mode_object = eval('new ' + class_name + '()');
150         mode_object.wikiwyg = this;
151         mode_object.set_config(config[mode_object.classtype]);
152         mode_object.initializeObject();
153         this.mode_objects[class_name] = mode_object;
154         if (! this.first_mode) {
155             this.first_mode = mode_object;
156         }
157     }
158
159     if (this.config.toolbarClass) {
160         var class_name = this.config.toolbarClass;
161         this.toolbarObject = eval('new ' + class_name + '()');
162         this.toolbarObject.wikiwyg = this;
163         this.toolbarObject.set_config(config.toolbar);
164         this.toolbarObject.initializeObject();
165         this.placeToolbar(this.toolbarObject.div);
166     }
167
168     // These objects must be _created_ before the toolbar is created
169     // but _inserted_ after.
170     for (var i = 0; i < this.config.modeClasses.length; i++) {
171         var mode_class = this.config.modeClasses[i];
172         var mode_object = this.mode_objects[mode_class];
173         this.insert_div_before(mode_object.div);
174     }
175
176     if (this.config.doubleClickToEdit) {
177         var self = this;
178         this.div.ondblclick = function() { self.editMode() }; 
179     }
180 }
181
182 proto.placeToolbar = function(div) {
183     this.insert_div_before(div);
184 }
185
186 // Wikiwyg environment setup private methods
187 proto.set_config = function(user_config) {
188     for (var key in this.config)
189         if (user_config && user_config[key])
190             this.config[key] = user_config[key];
191         else if (this[key] != null)
192             this.config[key] = this[key];
193 }
194
195 proto.insert_div_before = function(div) {
196     div.style.display = 'none';
197     if (! div.iframe_hack) {
198         this.div.parentNode.insertBefore(div, this.div);
199     }
200 }
201
202 // Wikiwyg actions - public interface methods
203 proto.saveChanges = function() {
204     alert('Wikiwyg.prototype.saveChanges not subclassed');
205 }
206
207 // Wikiwyg actions - public methods
208 proto.editMode = function() { // See IE, below
209     this.current_mode = this.first_mode;
210     //this.current_mode.fromHtml(this.div.innerHTML);
211     this.current_mode.fromWikitext(this.div.value);
212     this.toolbarObject.resetModeSelector();
213     this.current_mode.enableThis();
214 }
215
216 proto.displayMode = function() {
217     for (var i = 0; i < this.config.modeClasses.length; i++) {
218         var mode_class = this.config.modeClasses[i];
219         var mode_object = this.mode_objects[mode_class];
220         mode_object.disableThis();
221     }
222     this.toolbarObject.disableThis();
223     this.div.style.display = 'block';
224     this.divHeight = this.div.offsetHeight;
225 }
226
227 proto.switchMode = function(new_mode_key) {
228     var new_mode = this.mode_objects[new_mode_key];
229     var old_mode = this.current_mode;
230
231     if(new_mode == old_mode)
232       return;
233
234     //Set cookie to keep last editing mode
235     //document.cookie = "Mode="+new_mode_key;
236
237     var self = this;
238     new_mode.enableStarted();
239     old_mode.disableStarted();
240     old_mode.toHtml(
241         function(html) {
242             self.previous_mode = old_mode;
243             new_mode.fromHtml(html);
244             old_mode.disableThis();
245             new_mode.enableThis();
246             new_mode.enableFinished();
247             old_mode.disableFinished();
248             self.current_mode = new_mode;
249         }
250     );
251 }
252
253 proto.cancelEdit = function() {
254     this.displayMode();
255 }
256
257 proto.fromHtml = function(html) {
258     this.div.innerHTML = html;
259 }
260
261 // Class level helper methods
262 Wikiwyg.unique_id_base = 0;
263 Wikiwyg.createUniqueId = function() {
264     return 'wikiwyg_' + Wikiwyg.unique_id_base++;
265 }
266
267 Wikiwyg.liveUpdate = function(method, url, query, callback) {
268     var req = new XMLHttpRequest();
269     var data = null;
270     if (method == 'GET')
271         url = url + '?' + query;
272     else
273         data = query;
274     req.open(method, url);
275     req.onreadystatechange = function() {
276         if (req.readyState == 4 && req.status == 200)
277             callback(req.responseText);
278     }
279     if (method == 'POST') {
280         req.setRequestHeader(
281             'Content-Type', 
282             'application/x-www-form-urlencoded'
283         );
284     }
285     req.send(data);
286 }
287
288 Wikiwyg.htmlUnescape = function(escaped) {
289     // XXX Need a better way to unescape all entities
290     return escaped
291         .replace(/&amp;/g, '&')
292         .replace(/&lt;/g, '<')
293         .replace(/&gt;/g, '>');
294 }
295
296 Wikiwyg.showById = function(id) {
297     document.getElementById(id).style.visibility = 'inherit';
298 }
299
300 Wikiwyg.hideById = function(id) {
301     document.getElementById(id).style.visibility = 'hidden';
302 }
303
304
305 Wikiwyg.changeLinksMatching = function(attribute, pattern, func) {
306     var links = document.getElementsByTagName('a');
307     for (var i = 0; i < links.length; i++) {
308         var link = links[i];
309         var my_attribute = link.getAttribute(attribute);
310         if (my_attribute && my_attribute.match(pattern)) {
311             link.setAttribute('href', '#');
312             link.onclick = func;
313         }
314     }
315 }
316
317 Wikiwyg.createElementWithAttrs = function(element, attrs, doc) {
318     if (doc == null)
319         doc = document;
320     return Wikiwyg.create_element_with_attrs(element, attrs, doc);
321 }
322
323 // See IE, below
324 Wikiwyg.create_element_with_attrs = function(element, attrs, doc) {
325     var elem = doc.createElement(element);
326     for (name in attrs)
327         elem.setAttribute(name, attrs[name]);
328     return elem;
329 }
330
331 die = function(e) { // See IE, below
332     throw(e);
333 }
334
335 String.prototype.times = function(n) {
336     return n ? this + this.times(n-1) : "";
337 }
338
339 /*==============================================================================
340 Base class for Wikiwyg classes
341  =============================================================================*/
342 proto = new Subclass('Wikiwyg.Base');
343
344 proto.set_config = function(user_config) {
345     for (var key in this.config) {
346         if (user_config != null && user_config[key] != null)
347             this.merge_config(key, user_config[key]);
348         else if (this[key] != null)
349             this.merge_config(key, this[key]);
350         else if (this.wikiwyg.config[key] != null)
351             this.merge_config(key, this.wikiwyg.config[key]);
352     }
353 }
354
355 proto.merge_config = function(key, value) {
356     if (value instanceof Array) {
357         this.config[key] = value;
358     }
359     // cross-browser RegExp object check
360     else if (typeof value.test == 'function') {
361         this.config[key] = value;
362     }
363     else if (value instanceof Object) {
364         if (!this.config[key])
365             this.config[key] = {};
366         for (var subkey in value) {
367             this.config[key][subkey] = value[subkey];
368         }
369     }
370     else {
371         this.config[key] = value;
372     }
373 }
374
375 /*==============================================================================
376 Base class for Wikiwyg Mode classes
377  =============================================================================*/
378 proto = new Subclass('Wikiwyg.Mode', 'Wikiwyg.Base');
379
380 proto.enableThis = function() {
381     this.div.style.display = 'block';
382     this.display_unsupported_toolbar_buttons('none');
383     this.wikiwyg.toolbarObject.enableThis();
384     this.wikiwyg.div.style.display = 'none';
385 }
386
387 proto.display_unsupported_toolbar_buttons = function(display) {
388     if (!this.config) return;
389     var disabled = this.config.disabledToolbarButtons;
390     if (!disabled || disabled.length < 1) return;
391
392     var toolbar_div = this.wikiwyg.toolbarObject.div;
393     var toolbar_buttons = toolbar_div.childNodes;
394     for (var i in disabled) {
395         var action = disabled[i];
396
397         for (var i in toolbar_buttons) {
398             var button = toolbar_buttons[i];
399             var src = button.src;
400             if (!src) continue;
401
402             if (src.match(action)) {
403                 button.style.display = display;
404                 break;
405             }
406         }
407     }
408 }
409
410 proto.enableStarted = function() {}
411 proto.enableFinished = function() {}
412 proto.disableStarted = function() {}
413 proto.disableFinished = function() {}
414
415 proto.disableThis = function() {
416     this.display_unsupported_toolbar_buttons('inline');
417     this.div.style.display = 'none';
418 }
419
420 proto.process_command = function(command) {
421     if (this['do_' + command])
422         this['do_' + command](command);
423 }
424
425 proto.enable_keybindings = function() { // See IE
426     if (!this.key_press_function) {
427         this.key_press_function = this.get_key_press_function();
428         this.get_keybinding_area().addEventListener(
429             'keypress', this.key_press_function, true
430         );
431     }
432 }
433
434 proto.get_key_press_function = function() {
435     var self = this;
436     return function(e) {
437         if (! e.ctrlKey) return;
438         var key = String.fromCharCode(e.charCode).toLowerCase();
439         var command = '';
440         switch (key) {
441             case 'b': command = 'bold'; break;
442             case 'i': command = 'italic'; break;
443             case 'u': command = 'underline'; break;
444             case 'd': command = 'strike'; break;
445             case 'l': command = 'link'; break;
446         };
447
448         if (command) {
449             e.preventDefault();
450             e.stopPropagation();
451             self.process_command(command);
452         }
453     };
454 }
455
456 proto.get_edit_height = function() {
457     var height = parseInt(
458         this.wikiwyg.divHeight *
459         this.config.editHeightAdjustment
460     );
461     var min = this.config.editHeightMinimum;
462     return height < min
463         ? min
464         : height;
465 }
466
467 proto.setHeightOf = function(elem) {
468     elem.height = this.get_edit_height() + 'px';
469 }
470
471 /*==============================================================================
472 Support for Internet Explorer in Wikiwyg
473  =============================================================================*/
474 if (Wikiwyg.is_ie) {
475
476 Wikiwyg.create_element_with_attrs = function(element, attrs, doc) {
477      var str = '';
478      // Note the double-quotes (make sure your data doesn't use them):
479      for (name in attrs)
480          str += ' ' + name + '="' + attrs[name] + '"';
481      return doc.createElement('<' + element + str + '>');
482 }
483
484 die = function(e) {
485     alert(e);
486     throw(e);
487 }
488
489 proto = Wikiwyg.Mode.prototype;
490
491 proto.enable_keybindings = function() {}
492
493 } // end of global if statement for IE overrides