1 /*==============================================================================
2 This Wikiwyg mode supports a textarea editor with toolbar buttons.
6 Copyright (c) 2005 Socialtext Corporation
8 Palo Alto, CA 94301 U.S.A.
11 Wikiwyg is free software.
13 This library is free software; you can redistribute it and/or modify it
14 under the terms of the GNU Lesser General Public License as published by
15 the Free Software Foundation; either version 2.1 of the License, or (at
16 your option) any later version.
18 This library is distributed in the hope that it will be useful, but
19 WITHOUT ANY WARRANTY; without even the implied warranty of
20 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
21 General Public License for more details.
23 http://www.gnu.org/copyleft/lesser.txt
25 =============================================================================*/
27 proto = new Subclass('Wikiwyg.Wikitext', 'Wikiwyg.Mode');
28 klass = Wikiwyg.Wikitext;
30 proto.classtype = 'wikitext';
31 proto.modeDescription = 'Wikitext';
34 supportCamelCaseLinks: false,
35 javascriptLocation: null,
37 editHeightMinimum: 10,
38 editHeightAdjustment: 1.3,
40 link: ['bound_phrase', '[', ']'],
41 bold: ['bound_phrase', '*', '*'],
42 code: ['bound_phrase', '`', '`'],
43 italic: ['bound_phrase', '/', '/'],
44 underline: ['bound_phrase', '_', '_'],
45 strike: ['bound_phrase', '-', '-'],
46 p: ['start_lines', ''],
47 pre: ['start_lines', ' '],
48 h1: ['start_line', '= '],
49 h2: ['start_line', '== '],
50 h3: ['start_line', '=== '],
51 h4: ['start_line', '==== '],
52 h5: ['start_line', '===== '],
53 h6: ['start_line', '====== '],
54 ordered: ['start_lines', '#'],
55 unordered: ['start_lines', '*'],
56 indent: ['start_lines', '>'],
57 hr: ['line_alone', '----'],
58 table: ['line_alone', '| A | B | C |\n| | | |\n| | | |']
62 proto.initializeObject = function() { // See IE
63 this.initialize_object();
66 proto.initialize_object = function() {
67 this.div = document.createElement('div');
68 this.textarea = document.createElement('textarea');
69 this.textarea.setAttribute('id', 'wikiwyg_wikitext_textarea');
70 this.div.appendChild(this.textarea);
71 this.area = this.textarea;
72 this.clear_inner_text();
75 proto.clear_inner_text = function() {
77 this.area.onclick = function() {
78 var inner_text = self.area.value;
79 var clear = self.config.clearRegex;
80 if (clear && inner_text.match(clear))
85 proto.enableThis = function() {
86 this.superfunc('enableThis').call(this);
87 this.textarea.style.width = '100%';
88 this.setHeightOfEditor();
89 this.enable_keybindings();
92 proto.setHeightOfEditor = function() {
93 var config = this.config;
94 var adjust = config.editHeightAdjustment;
95 var area = this.textarea;
96 var text = this.textarea.value;
97 var rows = text.split(/\n/).length;
99 var height = parseInt(rows * adjust);
100 if (height < config.editHeightMinimum)
101 height = config.editHeightMinimum;
103 area.setAttribute('rows', height);
106 proto.toWikitext = function() {
107 return this.textarea.value;
110 proto.toHtml = function(func) {
111 var wikitext = this.textarea.value;
112 this.convertWikitextToHtml(wikitext, func);
115 proto.fromHtml = function(html) {
116 this.textarea.value = 'Loading...';
117 var textarea = this.textarea;
118 this.convertHtmlToWikitext(
120 function(value) { textarea.value = value }
124 proto.fromWikitext = function(html) {
125 this.textarea.value = html;
129 proto.convertWikitextToHtml = function(wikitext, func) {
130 alert('Wikitext changes cannot be converted to HTML\nWikiwyg.Wikitext.convertWikitextToHtml is not implemented here');
134 proto.convertHtmlToWikitext = function(html, func) {
135 func(this.convert_html_to_wikitext(html));
138 proto.get_keybinding_area = function() {
139 return this.textarea;
142 /*==============================================================================
143 Code to markup wikitext
144 =============================================================================*/
145 Wikiwyg.Wikitext.phrase_end_re = /[\s\.\:\;\,\!\?\(\)]/;
147 proto.find_left = function(t, selection_start, matcher) {
148 var substring = t.substr(selection_start - 1, 1);
149 var nextstring = t.substr(selection_start - 2, 1);
150 if (selection_start == 0)
151 return selection_start;
152 if (substring.match(matcher)) {
153 // special case for word.word
154 if ((substring != '.') || (nextstring.match(/\s/)))
155 return selection_start;
157 return this.find_left(t, selection_start - 1, matcher);
160 proto.find_right = function(t, selection_end, matcher) {
161 var substring = t.substr(selection_end, 1);
162 var nextstring = t.substr(selection_end + 1, 1);
163 if (selection_end >= t.length)
164 return selection_end;
165 if (substring.match(matcher)) {
166 // special case for word.word
167 if ((substring != '.') || (nextstring.match(/\s/)))
168 return selection_end;
170 return this.find_right(t, selection_end + 1, matcher);
173 proto.get_lines = function() {
175 var selection_start = t.selectionStart;
176 var selection_end = t.selectionEnd;
178 if (selection_start == null || selection_end == null)
181 var our_text = t.value.replace(/\r/g, '');
182 selection = our_text.substr(selection_start,
183 selection_end - selection_start);
185 selection_start = this.find_right(our_text, selection_start, /[^\r\n]/);
186 selection_end = this.find_left(our_text, selection_end, /[^\r\n]/);
188 this.selection_start = this.find_left(our_text, selection_start, /[\r\n]/);
189 this.selection_end = this.find_right(our_text, selection_end, /[\r\n]/);
190 t.setSelectionRange(selection_start, selection_end);
193 this.start = our_text.substr(0,this.selection_start);
194 this.sel = our_text.substr(this.selection_start, this.selection_end -
195 this.selection_start);
196 this.finish = our_text.substr(this.selection_end, our_text.length);
201 proto.alarm_on = function() {
202 var area = this.area;
203 var background = area.style.background;
204 area.style.background = '#f88';
206 function alarm_off() {
207 area.style.background = background;
210 window.setTimeout(alarm_off, 250);
214 proto.get_words = function() {
215 function is_insane(selection) {
216 return selection.match(/\r?\n(\r?\n|\*+ |\#+ |\=+ )/);
220 var selection_start = t.selectionStart;
221 var selection_end = t.selectionEnd;
222 if (selection_start == null || selection_end == null)
225 var our_text = t.value.replace(/\r/g, '');
226 selection = our_text.substr(selection_start,
227 selection_end - selection_start);
229 selection_start = this.find_right(our_text, selection_start, /(\S|\r?\n)/);
230 if (selection_start > selection_end)
231 selection_start = selection_end;
232 selection_end = this.find_left(our_text, selection_end, /(\S|\r?\n)/);
233 if (selection_end < selection_start)
234 selection_end = selection_start;
236 if (is_insane(selection)) {
241 this.selection_start =
242 this.find_left(our_text, selection_start, Wikiwyg.Wikitext.phrase_end_re);
244 this.find_right(our_text, selection_end, Wikiwyg.Wikitext.phrase_end_re);
246 t.setSelectionRange(this.selection_start, this.selection_end);
249 this.start = our_text.substr(0,this.selection_start);
250 this.sel = our_text.substr(this.selection_start, this.selection_end -
251 this.selection_start);
252 this.finish = our_text.substr(this.selection_end, our_text.length);
257 proto.markup_is_on = function(start, finish) {
258 return (this.sel.match(start) && this.sel.match(finish));
261 proto.clean_selection = function(start, finish) {
262 this.sel = this.sel.replace(start, '');
263 this.sel = this.sel.replace(finish, '');
266 proto.toggle_same_format = function(start, finish) {
267 start = this.clean_regexp(start);
268 finish = this.clean_regexp(finish);
269 var start_re = new RegExp('^' + start);
270 var finish_re = new RegExp(finish + '$');
271 if (this.markup_is_on(start_re, finish_re)) {
272 this.clean_selection(start_re, finish_re);
278 proto.clean_regexp = function(string) {
279 string = string.replace(/([\^\$\*\+\.\?\[\]\{\}])/g, '\\$1');
283 proto.set_text_and_selection = function(text, start, end) {
284 this.area.value = text;
285 this.area.setSelectionRange(start, end);
288 proto.add_markup_words = function(markup_start, markup_finish, example) {
289 if (this.toggle_same_format(markup_start, markup_finish)) {
290 this.selection_end = this.selection_end -
291 (markup_start.length + markup_finish.length);
295 if (this.sel.length == 0) {
298 var text = this.start + markup_start +
299 this.sel + markup_finish + this.finish;
300 var start = this.selection_start + markup_start.length;
301 var end = this.selection_end + markup_start.length + this.sel.length;
302 this.set_text_and_selection(text, start, end);
304 var text = this.start + markup_start + this.sel +
305 markup_finish + this.finish;
306 var start = this.selection_start;
307 var end = this.selection_end + markup_start.length +
308 markup_finish.length;
309 this.set_text_and_selection(text, start, end);
314 // XXX - A lot of this is hardcoded.
315 proto.add_markup_lines = function(markup_start) {
316 var already_set_re = new RegExp( '^' + this.clean_regexp(markup_start), 'gm');
317 var other_markup_re = /^(\^+|\=+|\*+|#+|>+| )/gm;
320 // if paragraph, reduce everything.
321 if (! markup_start.length) {
322 this.sel = this.sel.replace(other_markup_re, '');
323 this.sel = this.sel.replace(/^\ +/gm, '');
325 // if pre and not all indented, indent
326 else if ((markup_start == ' ') && this.sel.match(/^\S/m))
327 this.sel = this.sel.replace(/^/gm, markup_start);
328 // if not requesting heading and already this style, kill this style
330 (! markup_start.match(/[\=\^]/)) &&
331 this.sel.match(already_set_re)
333 this.sel = this.sel.replace(already_set_re, '');
334 if (markup_start != ' ')
335 this.sel = this.sel.replace(/^ */gm, '');
337 // if some other style, switch to new style
338 else if (match = this.sel.match(other_markup_re))
339 // if pre, just indent
340 if (markup_start == ' ')
341 this.sel = this.sel.replace(/^/gm, markup_start);
342 // if heading, just change it
343 else if (markup_start.match(/[\=\^]/))
344 this.sel = this.sel.replace(other_markup_re, markup_start);
345 // else try to change based on level
347 this.sel = this.sel.replace(
350 return markup_start.times(match.length);
353 // if something selected, use this style
354 else if (this.sel.length > 0)
355 this.sel = this.sel.replace(/^(.*\S+)/gm, markup_start + ' $1');
356 // just add the markup
358 this.sel = markup_start + ' ';
360 var text = this.start + this.sel + this.finish;
361 var start = this.selection_start;
362 var end = this.selection_start + this.sel.length;
363 this.set_text_and_selection(text, start, end);
367 // XXX - A lot of this is hardcoded.
368 proto.bound_markup_lines = function(markup_array) {
369 var markup_start = markup_array[1];
370 var markup_finish = markup_array[2];
371 var already_start = new RegExp('^' + this.clean_regexp(markup_start), 'gm');
372 var already_finish = new RegExp(this.clean_regexp(markup_finish) + '$', 'gm');
373 var other_start = /^(\^+|\=+|\*+|#+|>+) */gm;
374 var other_finish = /( +(\^+|\=+))?$/gm;
377 if (this.sel.match(already_start)) {
378 this.sel = this.sel.replace(already_start, '');
379 this.sel = this.sel.replace(already_finish, '');
381 else if (match = this.sel.match(other_start)) {
382 this.sel = this.sel.replace(other_start, markup_start);
383 this.sel = this.sel.replace(other_finish, markup_finish);
385 // if something selected, use this style
386 else if (this.sel.length > 0) {
387 this.sel = this.sel.replace(
389 markup_start + '$1' + markup_finish
392 // just add the markup
394 this.sel = markup_start + markup_finish;
396 var text = this.start + this.sel + this.finish;
397 var start = this.selection_start;
398 var end = this.selection_start + this.sel.length;
399 this.set_text_and_selection(text, start, end);
403 proto.markup_bound_line = function(markup_array) {
404 var scroll_top = this.area.scrollTop;
405 if (this.get_lines())
406 this.bound_markup_lines(markup_array);
407 this.area.scrollTop = scroll_top;
410 proto.markup_start_line = function(markup_array) {
411 var markup_start = markup_array[1];
412 markup_start = markup_start.replace(/ +/, '');
413 var scroll_top = this.area.scrollTop;
414 if (this.get_lines())
415 this.add_markup_lines(markup_start);
416 this.area.scrollTop = scroll_top;
419 proto.markup_start_lines = function(markup_array) {
420 var markup_start = markup_array[1];
421 var scroll_top = this.area.scrollTop;
422 if (this.get_lines())
423 this.add_markup_lines(markup_start);
424 this.area.scrollTop = scroll_top;
427 proto.markup_bound_phrase = function(markup_array) {
428 var markup_start = markup_array[1];
429 var markup_finish = markup_array[2];
430 var scroll_top = this.area.scrollTop;
431 if (markup_finish == 'undefined')
432 markup_finish = markup_start;
433 if (this.get_words())
434 this.add_markup_words(markup_start, markup_finish, null);
435 this.area.scrollTop = scroll_top;
438 klass.make_do = function(style) {
440 var markup = this.config.markupRules[style];
441 var handler = markup[0];
442 if (! this['markup_' + handler])
443 die('No handler for markup: "' + handler + '"');
444 this['markup_' + handler](markup);
448 proto.do_link = klass.make_do('link');
449 proto.do_bold = klass.make_do('bold');
450 proto.do_code = klass.make_do('code');
451 proto.do_italic = klass.make_do('italic');
452 proto.do_underline = klass.make_do('underline');
453 proto.do_strike = klass.make_do('strike');
454 proto.do_p = klass.make_do('p');
455 proto.do_pre = klass.make_do('pre');
456 proto.do_h1 = klass.make_do('h1');
457 proto.do_h2 = klass.make_do('h2');
458 proto.do_h3 = klass.make_do('h3');
459 proto.do_h4 = klass.make_do('h4');
460 proto.do_h5 = klass.make_do('h5');
461 proto.do_h6 = klass.make_do('h6');
462 proto.do_ordered = klass.make_do('ordered');
463 proto.do_unordered = klass.make_do('unordered');
464 proto.do_hr = klass.make_do('hr');
465 proto.do_table = klass.make_do('table');
467 proto.do_dent = function(method) {
468 var scroll_top = this.area.scrollTop;
469 if (! this.get_lines()) {
470 this.area.scrollTop = scroll_top;
475 var text = this.start + this.sel + this.finish;
476 var start = this.selection_start;
477 var end = this.selection_start + this.sel.length;
478 this.set_text_and_selection(text, start, end);
483 proto.do_indent = function() {
486 if (that.sel == '') return false;
487 that.sel = that.sel.replace(/^(([\*\-\#])+(?=\s))/gm, '$2$1');
488 that.sel = that.sel.replace(/^([\>\=])/gm, '$1$1');
489 that.sel = that.sel.replace(/^([^\>\*\-\#\=\r\n])/gm, '> $1');
490 that.sel = that.sel.replace(/^\={7,}/gm, '======');
496 proto.do_outdent = function() {
499 if (that.sel == '') return false;
500 that.sel = that.sel.replace(/^([\>\*\-\#\=] ?)/gm, '');
506 proto.markup_line_alone = function(markup_array) {
508 var scroll_top = t.scrollTop;
509 var selection_start = t.selectionStart;
511 this.selection_start = this.find_right(text, selection_start, /\r?\n/);
512 this.selection_end = this.selection_start;
513 t.setSelectionRange(this.selection_start, this.selection_start);
516 var markup = markup_array[1];
517 this.start = t.value.substr(0, this.selection_start);
518 this.finish = t.value.substr(this.selection_end, t.value.length);
519 var text = this.start + '\n' + markup + this.finish;
520 var start = this.selection_start + markup.length + 1;
521 var end = this.selection_end + markup.length + 1;
522 this.set_text_and_selection(text, start, end);
523 t.scrollTop = scroll_top;
527 /*==============================================================================
528 Code to convert from html to wikitext.
529 =============================================================================*/
530 proto.convert_html_to_wikitext = function(html) {
531 this.copyhtml = html;
532 var dom = document.createElement('div');
533 html = html.replace(/<!-=-/g, '<!--').
534 replace(/-=->/g, '-->');
535 dom.innerHTML = html;
538 this.indent_level = 0;
542 // add final whitespace
543 this.assert_new_line();
545 return this.join_output(this.output);
548 proto.appendOutput = function(string) {
549 this.output.push(string);
552 proto.join_output = function(output) {
553 var list = this.remove_stops(output);
554 list = this.cleanup_output(list);
555 return list.join('');
558 // This is a noop, but can be subclassed.
559 proto.cleanup_output = function(list) {
563 proto.remove_stops = function(list) {
565 for (var i = 0 ; i < list.length ; i++) {
566 if (typeof(list[i]) != 'string') continue;
572 proto.walk = function(element) {
573 if (!element) return;
574 for (var part = element.firstChild; part; part = part.nextSibling) {
575 if (part.nodeType == 1) {
576 this.dispatch_formatter(part);
578 else if (part.nodeType == 3) {
579 if (part.nodeValue.match(/\S/)) {
580 var string = part.nodeValue;
581 if (! string.match(/^[\.\,\?\!\)]/)) {
582 this.assert_space_or_newline();
583 string = this.trim(string);
585 this.appendOutput(this.collapse(string));
591 proto.dispatch_formatter = function(element) {
592 var dispatch = 'format_' + element.nodeName.toLowerCase();
593 if (! this[dispatch])
594 dispatch = 'handle_undefined';
595 this[dispatch](element);
598 proto.skip = function() { }
599 proto.pass = function(element) {
602 proto.handle_undefined = function(element) {
603 this.appendOutput('<' + element.nodeName + '>');
605 this.appendOutput('</' + element.nodeName + '>');
607 proto.handle_undefined = proto.skip;
609 proto.format_abbr = proto.pass;
610 proto.format_acronym = proto.pass;
611 proto.format_address = proto.pass;
612 proto.format_applet = proto.skip;
613 proto.format_area = proto.skip;
614 proto.format_basefont = proto.skip;
615 proto.format_base = proto.skip;
616 proto.format_bgsound = proto.skip;
617 proto.format_big = proto.pass;
618 proto.format_blink = proto.pass;
619 proto.format_body = proto.pass;
620 proto.format_br = proto.skip;
621 proto.format_button = proto.skip;
622 proto.format_caption = proto.pass;
623 proto.format_center = proto.pass;
624 proto.format_cite = proto.pass;
625 proto.format_col = proto.pass;
626 proto.format_colgroup = proto.pass;
627 proto.format_dd = proto.pass;
628 proto.format_dfn = proto.pass;
629 proto.format_dl = proto.pass;
630 proto.format_dt = proto.pass;
631 proto.format_embed = proto.skip;
632 proto.format_field = proto.skip;
633 proto.format_fieldset = proto.skip;
634 proto.format_font = proto.pass;
635 proto.format_form = proto.skip;
636 proto.format_frame = proto.skip;
637 proto.format_frameset = proto.skip;
638 proto.format_head = proto.skip;
639 proto.format_html = proto.pass;
640 proto.format_iframe = proto.pass;
641 proto.format_input = proto.skip;
642 proto.format_ins = proto.pass;
643 proto.format_isindex = proto.skip;
644 proto.format_label = proto.skip;
645 proto.format_legend = proto.skip;
646 proto.format_link = proto.skip;
647 proto.format_map = proto.skip;
648 proto.format_marquee = proto.skip;
649 proto.format_meta = proto.skip;
650 proto.format_multicol = proto.pass;
651 proto.format_nobr = proto.skip;
652 proto.format_noembed = proto.skip;
653 proto.format_noframes = proto.skip;
654 proto.format_nolayer = proto.skip;
655 proto.format_noscript = proto.skip;
656 proto.format_nowrap = proto.skip;
657 proto.format_object = proto.skip;
658 proto.format_optgroup = proto.skip;
659 proto.format_option = proto.skip;
660 proto.format_param = proto.skip;
661 proto.format_select = proto.skip;
662 proto.format_small = proto.pass;
663 proto.format_spacer = proto.skip;
664 proto.format_style = proto.skip;
665 proto.format_sub = proto.pass;
666 proto.format_submit = proto.skip;
667 proto.format_sup = proto.pass;
668 proto.format_tbody = proto.pass;
669 proto.format_textarea = proto.skip;
670 proto.format_tfoot = proto.pass;
671 proto.format_thead = proto.pass;
672 proto.format_wiki = proto.pass;
674 proto.format_img = function(element) {
675 var uri = element.getAttribute('src');
677 this.assert_space_or_newline();
678 this.appendOutput(uri);
682 // XXX This little dance relies on knowning lots of little details about where
683 // indentation fangs are added and deleted by the various insert/assert calls.
684 proto.format_blockquote = function(element) {
685 if (! this.indent_level) {
686 this.assert_new_line();
688 this.insert_new_line();
692 this.assert_new_line();
699 if (! this.indent_level)
700 this.assert_blank_line();
702 this.assert_new_line();
705 proto.format_div = function(element) {
706 if (this.is_opaque(element)) {
707 this.handle_opaque_block(element);
713 proto.format_span = function(element) {
714 if (this.is_opaque(element)) {
715 this.handle_opaque_phrase(element);
719 var style = element.getAttribute('style');
725 this.assert_space_or_newline();
726 if (style.match(/\bbold\b/))
727 this.appendOutput(this.config.markupRules.bold[1]);
728 if (style.match(/\bitalic\b/))
729 this.appendOutput(this.config.markupRules.italic[1]);
730 if (style.match(/\bunderline\b/))
731 this.appendOutput(this.config.markupRules.underline[1]);
732 if (style.match(/\bline-through\b/))
733 this.appendOutput(this.config.markupRules.strike[1]);
735 this.no_following_whitespace();
738 if (style.match(/\bline-through\b/))
739 this.appendOutput(this.config.markupRules.strike[2]);
740 if (style.match(/\bunderline\b/))
741 this.appendOutput(this.config.markupRules.underline[2]);
742 if (style.match(/\bitalic\b/))
743 this.appendOutput(this.config.markupRules.italic[2]);
744 if (style.match(/\bbold\b/))
745 this.appendOutput(this.config.markupRules.bold[2]);
748 klass.make_format = function(style) {
749 return function(element) {
750 var markup = this.config.markupRules[style];
751 var handler = markup[0];
752 this['handle_' + handler](element, markup);
756 proto.format_b = klass.make_format('bold');
757 proto.format_strong = proto.format_b;
758 proto.format_code = klass.make_format('code');
759 proto.format_kbd = proto.format_code;
760 proto.format_samp = proto.format_code;
761 proto.format_tt = proto.format_code;
762 proto.format_var = proto.format_code;
763 proto.format_i = klass.make_format('italic');
764 proto.format_em = proto.format_i;
765 proto.format_u = klass.make_format('underline');
766 proto.format_strike = klass.make_format('strike');
767 proto.format_del = proto.format_strike;
768 proto.format_s = proto.format_strike;
769 proto.format_hr = klass.make_format('hr');
770 proto.format_h1 = klass.make_format('h1');
771 proto.format_h2 = klass.make_format('h2');
772 proto.format_h3 = klass.make_format('h3');
773 proto.format_h4 = klass.make_format('h4');
774 proto.format_h5 = klass.make_format('h5');
775 proto.format_h6 = klass.make_format('h6');
776 proto.format_pre = klass.make_format('pre');
778 proto.format_p = function(element) {
779 this.assert_blank_line();
781 this.assert_blank_line();
784 proto.format_a = function(element) {
785 var label = Wikiwyg.htmlUnescape(element.innerHTML);
786 label = label.replace(/<[^>]*?>/g, ' ');
787 label = label.replace(/\s+/g, ' ');
788 label = label.replace(/^\s+/, '');
789 label = label.replace(/\s+$/, '');
790 this.make_wikitext_link(label, element.getAttribute('href'), element);
793 proto.format_table = function(element) {
794 this.assert_blank_line();
796 this.assert_blank_line();
799 proto.format_tr = function(element) {
801 this.appendOutput('|');
802 this.insert_new_line();
805 proto.format_td = function(element) {
806 this.appendOutput('| ');
808 this.appendOutput(' ');
810 proto.format_th = proto.format_td;
812 proto.format_ol = function(element) {
813 if (!this.list_type.length)
814 this.assert_blank_line();
816 this.assert_new_line();
818 this.list_type.push('ordered');
820 this.list_type.pop();
822 if (!this.list_type.length)
823 this.assert_blank_line();
826 // XXX - Maybe a refactoring for the duplication above
827 proto.format_ul = function(element) {
828 if (!this.list_type.length)
829 this.assert_blank_line();
831 this.assert_new_line();
833 this.list_type.push('unordered');
835 this.list_type.pop();
837 if (!this.list_type.length)
838 this.assert_blank_line();
841 proto.format_li = function(element) {
842 var level = this.list_type.length;
843 if (!level) die("List error");
844 var type = this.list_type[level - 1];
845 var markup = this.config.markupRules[type];
846 this.appendOutput(markup[1].times(level) + ' ');
851 this.insert_new_line();
855 proto.chomp = function() {
857 while (this.output.length) {
858 string = this.output.pop();
859 if (typeof(string) != 'string') {
860 this.appendOutput(string);
863 if (! string.match(/^\n>+ $/) && string.match(/\S/))
867 string = string.replace(/[\r\n\s]+$/, '');
868 this.appendOutput(string);
872 proto.collapse = function(string) {
873 return string.replace(/[ \r\n]+/g, ' ');
876 proto.trim = function(string) {
877 return string.replace(/^\s+/, '');
880 proto.insert_new_line = function() {
882 if (this.indent_level > 0)
883 fang = '>'.times(this.indent_level) + ' ';
884 // XXX - ('\n' + fang) MUST be in the same element in this.output so that
885 // it can be properly matched by chomp above.
886 if (this.output.length)
887 this.appendOutput('\n' + fang);
888 else if (fang.length)
889 this.appendOutput(fang);
892 proto.assert_new_line = function() {
894 this.insert_new_line();
897 proto.assert_blank_line = function() {
899 this.insert_new_line();
900 this.insert_new_line();
903 proto.assert_space_or_newline = function() {
905 if (! this.output.length) return;
907 string = this.output[this.output.length - 1];
909 if (! string.whitespace && ! string.match(/\s+$/))
910 this.appendOutput(' ');
913 proto.no_following_whitespace = function() {
914 this.appendOutput({whitespace: 'stop'});
917 proto.handle_bound_phrase = function(element, markup) {
918 this.assert_space_or_newline();
919 this.appendOutput(markup[1]);
920 this.no_following_whitespace();
922 // assume that walk leaves no trailing whitespace.
923 this.appendOutput(markup[2]);
926 // XXX - A very promising refactoring is that we don't need the trailing
927 // assert_blank_line in block formatters.
928 proto.handle_bound_line = function(element,markup) {
929 this.assert_blank_line();
930 this.appendOutput(markup[1]);
932 this.appendOutput(markup[2]);
933 this.assert_blank_line();
936 proto.handle_start_line = function (element, markup) {
937 this.assert_blank_line();
938 this.appendOutput(markup[1]);
940 this.assert_blank_line();
943 proto.handle_start_lines = function (element, markup) {
944 var text = element.firstChild.nodeValue;
946 this.assert_blank_line();
947 text = text.replace(/^/mg, markup[1]);
948 this.appendOutput(text);
949 this.assert_blank_line();
952 proto.handle_line_alone = function (element, markup) {
953 this.assert_blank_line();
954 this.appendOutput(markup[1]);
955 this.assert_blank_line();
958 proto.get_first_comment = function(element) {
959 var comment = element.firstChild;
960 if (comment && (comment.nodeType == 8))
966 proto.is_opaque = function(element) {
967 var comment = this.get_first_comment(element);
968 if (!comment) return false;
970 var text = comment.data;
971 if (text.match(/^\s*wiki:/)) return true;
975 proto.handle_opaque_phrase = function(element) {
976 var comment = this.get_first_comment(element);
978 var text = comment.data;
979 text = text.replace(/^ wiki:\s+/, '');
980 text = text.replace(/\s$/, '');
981 this.assert_space_or_newline();
982 this.appendOutput(text);
983 this.assert_space_or_newline();
987 proto.handle_opaque_block = function(element) {
988 var comment = this.get_first_comment(element);
989 if (!comment) return;
991 var text = comment.data;
992 text = text.replace(/^\s*wiki:\s+/, '');
993 this.appendOutput(text);
996 proto.make_wikitext_link = function(label, href, element) {
997 var before = this.config.markupRules.link[1];
998 var after = this.config.markupRules.link[2];
1000 this.assert_space_or_newline();
1002 this.appendOutput(label);
1004 else if (href == label) {
1005 this.appendOutput(href);
1007 else if (this.href_is_wiki_link(href)) {
1008 if (this.camel_case_link(label))
1009 this.appendOutput(label);
1011 this.appendOutput(before + label + after);
1014 this.appendOutput(before + href + ' ' + label + after);
1018 proto.camel_case_link = function(label) {
1019 if (! this.config.supportCamelCaseLinks)
1021 return label.match(/[a-z][A-Z]/);
1024 proto.href_is_wiki_link = function(href) {
1025 if (! this.looks_like_a_url(href))
1027 if (! href.match(/\?/))
1029 var no_arg_input = href.split('?')[0];
1030 var no_arg_current = location.href.split('?')[0];
1031 return no_arg_input == no_arg_current;
1034 proto.looks_like_a_url = function(string) {
1035 return string.match(/^(http|https|ftp|irc|mailto):/);
1038 /*==============================================================================
1039 Support for Internet Explorer in Wikiwyg.Wikitext
1040 =============================================================================*/
1041 if (Wikiwyg.is_ie) {
1043 proto.setHeightOf = function() {
1044 // XXX hardcode this until we can keep window from jumping after button
1046 this.textarea.style.height = '200px';
1049 proto.initializeObject = function() {
1050 this.initialize_object();
1051 this.area.addBehavior(this.config.javascriptLocation + "Selection.htc");
1054 } // end of global if