bcalloway-wysihat-engine 0.1.8

Sign up to get free protection for your applications and to get access to all the features.
Files changed (50) hide show
  1. data/.gitignore +6 -0
  2. data/CHANGELOG +51 -0
  3. data/MIT-LICENSE +20 -0
  4. data/README.textile +24 -0
  5. data/Rakefile +78 -0
  6. data/TODO +4 -0
  7. data/VERSION +1 -0
  8. data/app/controllers/wysihat_files_controller.rb +35 -0
  9. data/app/helpers/wysihat_files_helper.rb +2 -0
  10. data/app/models/wysihat_file.rb +9 -0
  11. data/app/views/wysihat_files/_form.html.erb +13 -0
  12. data/app/views/wysihat_files/_wysihat_file.html.erb +11 -0
  13. data/app/views/wysihat_files/index.html.erb +5 -0
  14. data/app/views/wysihat_files/new.html.erb +1 -0
  15. data/config/locales/en.yml +5 -0
  16. data/generators/wysihat/templates/css/facebox.css +60 -0
  17. data/generators/wysihat/templates/css/wysihat.css +44 -0
  18. data/generators/wysihat/templates/images/facebox/b.png +0 -0
  19. data/generators/wysihat/templates/images/facebox/bl.png +0 -0
  20. data/generators/wysihat/templates/images/facebox/br.png +0 -0
  21. data/generators/wysihat/templates/images/facebox/closelabel.gif +0 -0
  22. data/generators/wysihat/templates/images/facebox/loading.gif +0 -0
  23. data/generators/wysihat/templates/images/facebox/tl.png +0 -0
  24. data/generators/wysihat/templates/images/facebox/tr.png +0 -0
  25. data/generators/wysihat/templates/images/icons/arrow_redo.png +0 -0
  26. data/generators/wysihat/templates/images/icons/arrow_undo.png +0 -0
  27. data/generators/wysihat/templates/images/icons/exclamation.png +0 -0
  28. data/generators/wysihat/templates/images/icons/image.png +0 -0
  29. data/generators/wysihat/templates/images/icons/link.png +0 -0
  30. data/generators/wysihat/templates/images/icons/page_code.png +0 -0
  31. data/generators/wysihat/templates/images/icons/text_align_center.png +0 -0
  32. data/generators/wysihat/templates/images/icons/text_align_left.png +0 -0
  33. data/generators/wysihat/templates/images/icons/text_align_right.png +0 -0
  34. data/generators/wysihat/templates/images/icons/text_bold.png +0 -0
  35. data/generators/wysihat/templates/images/icons/text_italic.png +0 -0
  36. data/generators/wysihat/templates/images/icons/text_list_bullets.png +0 -0
  37. data/generators/wysihat/templates/images/icons/text_list_numbers.png +0 -0
  38. data/generators/wysihat/templates/images/icons/text_strikethrough.png +0 -0
  39. data/generators/wysihat/templates/images/icons/text_underline.png +0 -0
  40. data/generators/wysihat/templates/javascripts/facebox.js +180 -0
  41. data/generators/wysihat/templates/javascripts/wysihat.js +2073 -0
  42. data/generators/wysihat/templates/javascripts/wysihat_engine.js +101 -0
  43. data/generators/wysihat/templates/javascripts/wysihat_engine_pack.js +1 -0
  44. data/generators/wysihat/templates/migrations/create_wysihat_files.rb +15 -0
  45. data/generators/wysihat/templates/sass/facebox.sass +108 -0
  46. data/generators/wysihat/templates/sass/wysihat.sass +66 -0
  47. data/generators/wysihat/wysihat_generator.rb +83 -0
  48. data/lib/wysihat-engine.rb +38 -0
  49. data/wysihat-engine.gemspec +90 -0
  50. metadata +123 -0
@@ -0,0 +1,180 @@
1
+ /* Facebox for Prototype
2
+ *
3
+ * This is a slightly updated version of jeffkreeftmeijer's branch of facebox-for-prototype
4
+ * (http://github.com/jeffkreeftmeijer/facebox-for-prototype). The image locations are
5
+ * changed -- from /images/ to /images/facebox/ -- to keep the engine nice and tidy.
6
+ *
7
+ */
8
+
9
+ var Facebox = Class.create({
10
+ initialize : function(extra_set){
11
+ this.settings = {
12
+ loading_image : '/images/facebox/loading.gif',
13
+ close_image : '/images/facebox/closelabel.gif',
14
+ image_types : new RegExp('\.' + ['png', 'jpg', 'jpeg', 'gif'].join('|') + '$', 'i'),
15
+ inited : true,
16
+ facebox_html : '\
17
+ <div id="facebox" style="display:none;"> \
18
+ <div class="popup"> \
19
+ <table id="facebox_table"> \
20
+ <tbody> \
21
+ <tr> \
22
+ <td class="tl"/><td class="b"/><td class="tr"/> \
23
+ </tr> \
24
+ <tr> \
25
+ <td class="b"/> \
26
+ <td class="body"> \
27
+ <div class="content"> \
28
+ </div> \
29
+ <div class="footer"> \
30
+ <a href="#" class="close"> \
31
+ <img src="/images/facebox/closelabel.gif" title="close" class="close_image" /> \
32
+ </a> \
33
+ </div> \
34
+ </td> \
35
+ <td class="b"/> \
36
+ </tr> \
37
+ <tr> \
38
+ <td class="bl"/><td class="b"/><td class="br"/> \
39
+ </tr> \
40
+ </tbody> \
41
+ </table> \
42
+ </div> \
43
+ </div>'
44
+ };
45
+ if (extra_set) Object.extend(this.settings, extra_set);
46
+ $(document.body).insert({bottom: this.settings.facebox_html});
47
+
48
+ this.preload = [ new Image(), new Image() ];
49
+ this.preload[0].src = this.settings.close_image;
50
+ this.preload[1].src = this.settings.loading_image;
51
+
52
+ f = this;
53
+ $$('#facebox .b:first, #facebox .bl, #facebox .br, #facebox .tl, #facebox .tr').each(function(elem){
54
+ f.preload.push(new Image());
55
+ f.preload.slice(-1).src = elem.getStyle('background-image').replace(/url\((.+)\)/, '$1');
56
+ });
57
+
58
+ this.facebox = $('facebox');
59
+ this.keyPressListener = this.watchKeyPress.bindAsEventListener(this);
60
+
61
+ this.watchClickEvents();
62
+ fb = this;
63
+ Event.observe($$('#facebox .close').first(), 'click', function(e){
64
+ Event.stop(e);
65
+ fb.close()
66
+ });
67
+ Event.observe($$('#facebox .close_image').first(), 'click', function(e){
68
+ Event.stop(e);
69
+ fb.close()
70
+ });
71
+ },
72
+
73
+ watchKeyPress : function(e){
74
+ // Close if espace is pressed or if there's a click outside of the facebox
75
+ if (e.keyCode == 27 || !Event.element(e).descendantOf(this.facebox)) this.close();
76
+ },
77
+
78
+ watchClickEvents : function(e){
79
+ var f = this;
80
+ $$('a[rel=facebox]').each(function(elem,i){
81
+ Event.observe(elem, 'click', function(e){
82
+ Event.stop(e);
83
+ f.click_handler(elem, e);
84
+ });
85
+ });
86
+ },
87
+
88
+ loading : function() {
89
+ if ($$('#facebox .loading').length == 1) return true;
90
+
91
+ contentWrapper = $$('#facebox .content').first();
92
+ contentWrapper.childElements().each(function(elem, i){
93
+ elem.remove();
94
+ });
95
+ contentWrapper.insert({bottom: '<div class="loading"><img src="'+this.settings.loading_image+'"/></div>'});
96
+
97
+ var pageScroll = document.viewport.getScrollOffsets();
98
+ this.facebox.setStyle({
99
+ 'top': pageScroll.top + (document.viewport.getHeight() / 10) + 'px',
100
+ 'left': document.viewport.getWidth() / 2 - (this.facebox.getWidth() / 2) + 'px'
101
+ });
102
+
103
+ Event.observe(document, 'keypress', this.keyPressListener);
104
+ Event.observe(document, 'click', this.keyPressListener);
105
+ },
106
+
107
+ reveal : function(data, klass){
108
+ this.loading();
109
+ load = $$('#facebox .loading').first();
110
+ if(load) load.remove();
111
+
112
+ contentWrapper = $$('#facebox .content').first();
113
+ if (klass) contentWrapper.addClassName(klass);
114
+ //contentWrapper.insert({bottom: data});
115
+ contentWrapper.update(data);
116
+
117
+ $$('#facebox .body').first().childElements().each(function(elem,i){
118
+ elem.show();
119
+ });
120
+
121
+ if(!this.facebox.visible()) new Effect.Appear(this.facebox, {duration: .3});
122
+ this.facebox.setStyle({
123
+ 'left': document.viewport.getWidth() / 2 - (this.facebox.getWidth() / 2) + 'px'
124
+ });
125
+
126
+ Event.observe(document, 'keypress', this.keyPressListener);
127
+ Event.observe(document, 'click', this.keyPressListener);
128
+ },
129
+
130
+ close : function(){
131
+ new Effect.Fade('facebox', {duration: .3});
132
+ },
133
+
134
+ click_handler : function(elem, e){
135
+ this.loading();
136
+ Event.stop(e);
137
+
138
+ // support for rel="facebox[.inline_popup]" syntax, to add a class
139
+ var klass = elem.rel.match(/facebox\[\.(\w+)\]/);
140
+ if (klass) klass = klass[1];
141
+
142
+ new Effect.Appear(this.facebox, {duration: .3});
143
+
144
+ if (elem.href.match(/#/)){
145
+ var url = window.location.href.split('#')[0];
146
+ var target = elem.href.replace(url+'#','');
147
+ // var data = $$(target).first();
148
+ var d = $(target);
149
+ // create a new element so as to not delete the original on close()
150
+ var data = new Element(d.tagName);
151
+ data.innerHTML = d.innerHTML;
152
+ this.reveal(data, klass);
153
+ } else if (elem.href.match(this.settings.image_types)) {
154
+ var image = new Image();
155
+ fb = this;
156
+ image.onload = function() {
157
+ fb.reveal('<div class="image"><img src="' + image.src + '" /></div>', klass)
158
+ }
159
+ image.src = elem.href;
160
+ } else {
161
+ var fb = this;
162
+ var url = elem.href;
163
+ new Ajax.Request(url, {
164
+ method : 'get',
165
+ onFailure : function(transport){
166
+ fb.reveal(transport.responseText, klass);
167
+ },
168
+ onSuccess : function(transport){
169
+ fb.reveal(transport.responseText, klass);
170
+ }
171
+ });
172
+
173
+ }
174
+ }
175
+ });
176
+
177
+ var facebox;
178
+ document.observe('dom:loaded', function(){
179
+ facebox = new Facebox();
180
+ });
@@ -0,0 +1,2073 @@
1
+ /* WysiHat - WYSIWYG JavaScript framework, version 0.2
2
+ * (c) 2008-2009 Joshua Peek
3
+ *
4
+ * WysiHat is freely distributable under the terms of an MIT-style license.
5
+ *--------------------------------------------------------------------------*/
6
+
7
+
8
+ var WysiHat = {};
9
+
10
+ WysiHat.Editor = {
11
+ attach: function(textarea, options, block) {
12
+ options = $H(options);
13
+ textarea = $(textarea);
14
+ textarea.hide();
15
+
16
+ var model = options.get('model') || WysiHat.iFrame;
17
+ var initializer = block;
18
+
19
+ return model.create(textarea, function(editArea) {
20
+ var document = editArea.getDocument();
21
+ var window = editArea.getWindow();
22
+
23
+ editArea.load();
24
+
25
+ Event.observe(window, 'focus', function(event) { editArea.focus(); });
26
+ Event.observe(window, 'blur', function(event) { editArea.blur(); });
27
+
28
+ editArea._observeEvents();
29
+
30
+ if (Prototype.Browser.Gecko) {
31
+ editArea.execCommand('undo', false, null);
32
+ }
33
+
34
+ if (initializer)
35
+ initializer(editArea);
36
+
37
+ editArea.focus();
38
+ });
39
+ },
40
+
41
+ include: function(module) {
42
+ this.includedModules = this.includedModules || $A([]);
43
+ this.includedModules.push(module);
44
+ },
45
+
46
+ extend: function(object) {
47
+ var modules = this.includedModules || $A([]);
48
+ modules.each(function(module) {
49
+ Object.extend(object, module);
50
+ });
51
+ }
52
+ };
53
+
54
+ WysiHat.Commands = (function() {
55
+ function boldSelection() {
56
+ this.execCommand('bold', false, null);
57
+ }
58
+
59
+ function boldSelected() {
60
+ return this.queryCommandState('bold');
61
+ }
62
+
63
+ function underlineSelection() {
64
+ this.execCommand('underline', false, null);
65
+ }
66
+
67
+ function underlineSelected() {
68
+ return this.queryCommandState('underline');
69
+ }
70
+
71
+ function italicSelection() {
72
+ this.execCommand('italic', false, null);
73
+ }
74
+
75
+ function italicSelected() {
76
+ return this.queryCommandState('italic');
77
+ }
78
+
79
+ function strikethroughSelection() {
80
+ this.execCommand('strikethrough', false, null);
81
+ }
82
+
83
+ function blockquoteSelection() {
84
+ this.execCommand('blockquote', false, null);
85
+ }
86
+
87
+ function fontSelection(font) {
88
+ this.execCommand('fontname', false, font);
89
+ }
90
+
91
+ function fontSelected() {
92
+ var node = this.selection.getNode();
93
+ return Element.getStyle(node, 'fontFamily');
94
+ }
95
+
96
+ function fontSizeSelection(fontSize) {
97
+ this.execCommand('fontsize', false, fontSize);
98
+ }
99
+
100
+ function fontSizeSelected() {
101
+ var node = this.selection.getNode();
102
+ return standardizeFontSize(Element.getStyle(node, 'fontSize'));
103
+ }
104
+
105
+ function colorSelection(color) {
106
+ this.execCommand('forecolor', false, color);
107
+ }
108
+
109
+ function colorSelected() {
110
+ var node = this.selection.getNode();
111
+ return standardizeColor(Element.getStyle(node, 'color'));
112
+ }
113
+
114
+ function backgroundColorSelection(color) {
115
+ if(Prototype.Browser.Gecko) {
116
+ this.execCommand('hilitecolor', false, color);
117
+ } else {
118
+ this.execCommand('backcolor', false, color);
119
+ }
120
+ }
121
+
122
+ function backgroundColorSelected() {
123
+ var node = this.selection.getNode();
124
+ return standardizeColor(Element.getStyle(node, 'backgroundColor'));
125
+ }
126
+
127
+ function alignSelection(alignment) {
128
+ this.execCommand('justify' + alignment);
129
+ }
130
+
131
+ function alignSelected() {
132
+ var node = this.selection.getNode();
133
+ return Element.getStyle(node, 'textAlign');
134
+ }
135
+
136
+ function linkSelection(url) {
137
+ this.execCommand('createLink', false, url);
138
+ }
139
+
140
+ function unlinkSelection() {
141
+ var node = this.selection.getNode();
142
+ if (this.linkSelected())
143
+ this.selection.selectNode(node);
144
+
145
+ this.execCommand('unlink', false, null);
146
+ }
147
+
148
+ function linkSelected() {
149
+ var node = this.selection.getNode();
150
+ return node ? node.tagName.toUpperCase() == 'A' : false;
151
+ }
152
+
153
+ function insertOrderedList() {
154
+ this.execCommand('insertorderedlist', false, null);
155
+ }
156
+
157
+ function insertUnorderedList() {
158
+ this.execCommand('insertunorderedlist', false, null);
159
+ }
160
+
161
+ function insertImage(url) {
162
+ this.execCommand('insertImage', false, url);
163
+ }
164
+
165
+ function insertHTML(html) {
166
+ if (Prototype.Browser.IE) {
167
+ var range = this._selection.getRange();
168
+ range.pasteHTML(html);
169
+ range.collapse(false);
170
+ range.select();
171
+ } else {
172
+ this.execCommand('insertHTML', false, html);
173
+ }
174
+ }
175
+
176
+ function execCommand(command, ui, value) {
177
+ var document = this.getDocument();
178
+
179
+ if (Prototype.Browser.IE) this.selection.restore();
180
+
181
+ var handler = this.commands.get(command)
182
+ if (handler)
183
+ handler.bind(this)(value);
184
+ else
185
+ document.execCommand(command, ui, value);
186
+ }
187
+
188
+ function queryCommandState(state) {
189
+ var document = this.getDocument();
190
+
191
+ var handler = this.queryCommands.get(state)
192
+ if (handler)
193
+ return handler.bind(this)();
194
+ else
195
+ return document.queryCommandState(state);
196
+ }
197
+ var fontSizeNames = $w('xxx-small xx-small x-small small medium large x-large xx-large');
198
+ var fontSizePixels = $w('9px 10px 13px 16px 18px 24px 32px 48px');
199
+
200
+ if (Prototype.Browser.WebKit) {
201
+ fontSizeNames.shift();
202
+ fontSizeNames.push('-webkit-xxx-large');
203
+ }
204
+
205
+ function standardizeFontSize(fontSize) {
206
+ var newSize = fontSizeNames.indexOf(fontSize);
207
+ if (newSize >= 0) return newSize;
208
+
209
+ newSize = fontSizePixels.indexOf(fontSize);
210
+ if (newSize >= 0) return newSize;
211
+ return parseInt(fontSize);
212
+ }
213
+
214
+ function standardizeColor(color) {
215
+ if (!color || color.match(/[0-9a-f]{6}/i)) return color;
216
+ var m = color.toLowerCase().match(/^(rgba?|hsla?)\(([\s\.\-,%0-9]+)\)/);
217
+ if(m){
218
+ var c = m[2].split(/\s*,\s*/), l = c.length, t = m[1];
219
+ if((t == "rgb" && l == 3) || (t == "rgba" && l == 4)){
220
+ var r = c[0];
221
+ if(r.charAt(r.length - 1) == "%"){
222
+ var a = c.map(function(x){
223
+ return parseFloat(x) * 2.56;
224
+ });
225
+ if(l == 4){ a[3] = c[3]; }
226
+ return _colorFromArray(a);
227
+ }
228
+ return _colorFromArray(c);
229
+ }
230
+ if((t == "hsl" && l == 3) || (t == "hsla" && l == 4)){
231
+ var H = ((parseFloat(c[0]) % 360) + 360) % 360 / 360,
232
+ S = parseFloat(c[1]) / 100,
233
+ L = parseFloat(c[2]) / 100,
234
+ m2 = L <= 0.5 ? L * (S + 1) : L + S - L * S,
235
+ m1 = 2 * L - m2,
236
+ a = [_hue2rgb(m1, m2, H + 1 / 3) * 256,
237
+ _hue2rgb(m1, m2, H) * 256, _hue2rgb(m1, m2, H - 1 / 3) * 256, 1];
238
+ if(l == 4){ a[3] = c[3]; }
239
+ return _colorFromArray(a);
240
+ }
241
+ }
242
+ return null; // dojo.Color
243
+ }
244
+
245
+ function _colorFromArray(a) {
246
+ var arr = a.slice(0, 3).map(function(x){
247
+ var s = parseInt(x).toString(16);
248
+ return s.length < 2 ? "0" + s : s;
249
+ });
250
+ return "#" + arr.join(""); // String
251
+ }
252
+
253
+ function _hue2rgb(m1, m2, h){
254
+ if(h < 0){ ++h; }
255
+ if(h > 1){ --h; }
256
+ var h6 = 6 * h;
257
+ if(h6 < 1){ return m1 + (m2 - m1) * h6; }
258
+ if(2 * h < 1){ return m2; }
259
+ if(3 * h < 2){ return m1 + (m2 - m1) * (2 / 3 - h) * 6; }
260
+ return m1;
261
+ }
262
+
263
+ return {
264
+ boldSelection: boldSelection,
265
+ boldSelected: boldSelected,
266
+ underlineSelection: underlineSelection,
267
+ underlineSelected: underlineSelected,
268
+ italicSelection: italicSelection,
269
+ italicSelected: italicSelected,
270
+ strikethroughSelection: strikethroughSelection,
271
+ blockquoteSelection: blockquoteSelection,
272
+ fontSelection: fontSelection,
273
+ fontSelected: fontSelected,
274
+ fontSizeSelection: fontSizeSelection,
275
+ fontSizeSelected: fontSizeSelected,
276
+ colorSelection: colorSelection,
277
+ colorSelected: colorSelected,
278
+ backgroundColorSelection: backgroundColorSelection,
279
+ backgroundColorSelected: backgroundColorSelected,
280
+ alignSelection: alignSelection,
281
+ alignSelected: alignSelected,
282
+ linkSelection: linkSelection,
283
+ unlinkSelection: unlinkSelection,
284
+ linkSelected: linkSelected,
285
+ insertOrderedList: insertOrderedList,
286
+ insertUnorderedList: insertUnorderedList,
287
+ insertImage: insertImage,
288
+ insertHTML: insertHTML,
289
+ execCommand: execCommand,
290
+ queryCommandState: queryCommandState,
291
+
292
+ commands: $H({}),
293
+
294
+ queryCommands: $H({
295
+ link: linkSelected
296
+ })
297
+ };
298
+ })();
299
+
300
+ WysiHat.Editor.include(WysiHat.Commands);
301
+ WysiHat.Events = (function() {
302
+ var eventsToFoward = [
303
+ 'click',
304
+ 'dblclick',
305
+ 'mousedown',
306
+ 'mouseup',
307
+ 'mouseover',
308
+ 'mousemove',
309
+ 'mouseout',
310
+ 'keypress',
311
+ 'keydown',
312
+ 'keyup'
313
+ ];
314
+
315
+ function forwardEvents(document, editor) {
316
+ eventsToFoward.each(function(event) {
317
+ Event.observe(document, event, function(e) {
318
+ editor.fire('wysihat:' + event);
319
+ });
320
+ });
321
+ }
322
+
323
+ function observePasteEvent(window, document, editor) {
324
+ Event.observe(document, 'keydown', function(event) {
325
+ if (event.keyCode == 86)
326
+ editor.fire("wysihat:paste");
327
+ });
328
+
329
+ Event.observe(window, 'paste', function(event) {
330
+ editor.fire("wysihat:paste");
331
+ });
332
+ }
333
+
334
+ function observeFocus(window, editor) {
335
+ Event.observe(window, 'focus', function(event) {
336
+ editor.fire("wysihat:focus");
337
+ });
338
+
339
+ Event.observe(window, 'blur', function(event) {
340
+ editor.fire("wysihat:blur");
341
+ });
342
+ }
343
+
344
+ function observeSelections(document, editor) {
345
+ Event.observe(document, 'mouseup', function(event) {
346
+ var range = editor.selection.getRange();
347
+ if (!range.collapsed)
348
+ editor.fire("wysihat:select");
349
+ });
350
+ }
351
+
352
+ function observeChanges(document, editor) {
353
+ var previousContents = editor.rawContent();
354
+ Event.observe(document, 'keyup', function(event) {
355
+ var contents = editor.rawContent();
356
+ if (previousContents != contents) {
357
+ editor.fire("wysihat:change");
358
+ previousContents = contents;
359
+ }
360
+ });
361
+ }
362
+
363
+ function observeCursorMovements(document, editor) {
364
+ var previousRange = editor.selection.getRange();
365
+ var handler = function(event) {
366
+ var range = editor.selection.getRange();
367
+ if (previousRange != range) {
368
+ editor.fire("wysihat:cursormove");
369
+ previousRange = range;
370
+ }
371
+ };
372
+
373
+ Event.observe(document, 'keyup', handler);
374
+ Event.observe(document, 'mouseup', handler);
375
+ }
376
+
377
+ function observeEvents() {
378
+ if (this._observers_setup)
379
+ return;
380
+
381
+ var document = this.getDocument();
382
+ var window = this.getWindow();
383
+
384
+ forwardEvents(document, this);
385
+ observePasteEvent(window, document, this);
386
+ observeFocus(window, this);
387
+ observeSelections(document, this);
388
+ observeChanges(document, this);
389
+ observeCursorMovements(document, this);
390
+
391
+ this._observers_setup = true;
392
+ }
393
+
394
+ return {
395
+ _observeEvents: observeEvents
396
+ };
397
+ })();
398
+
399
+ WysiHat.Editor.include(WysiHat.Events);
400
+ WysiHat.Persistence = (function() {
401
+ function outputFilter(text) {
402
+ return text.formatHTMLOutput();
403
+ }
404
+
405
+ function inputFilter(text) {
406
+ return text.formatHTMLInput();
407
+ }
408
+
409
+ function content() {
410
+ return this.outputFilter(this.rawContent());
411
+ }
412
+
413
+ function setContent(text) {
414
+ this.setRawContent(this.inputFilter(text));
415
+ }
416
+
417
+ function save() {
418
+ this.textarea.value = this.content();
419
+ }
420
+
421
+ function load() {
422
+ this.setContent(this.textarea.value);
423
+ }
424
+
425
+ function reload() {
426
+ this.selection.setBookmark();
427
+ this.save();
428
+ this.load();
429
+ this.selection.moveToBookmark();
430
+ }
431
+
432
+ return {
433
+ outputFilter: outputFilter,
434
+ inputFilter: inputFilter,
435
+ content: content,
436
+ setContent: setContent,
437
+ save: save,
438
+ load: load,
439
+ reload: reload
440
+ };
441
+ })();
442
+
443
+ WysiHat.Editor.include(WysiHat.Persistence);
444
+ WysiHat.Window = (function() {
445
+ function getDocument() {
446
+ return this.contentDocument || this.contentWindow.document;
447
+ }
448
+
449
+ function getWindow() {
450
+ if (this.contentDocument && this.contentDocument.defaultView)
451
+ return this.contentDocument.defaultView;
452
+ else if (this.contentWindow.document)
453
+ return this.contentWindow;
454
+ else
455
+ return null;
456
+ }
457
+
458
+ function focus() {
459
+ this.getWindow().focus();
460
+
461
+ if (this.hasFocus)
462
+ return;
463
+
464
+ this.hasFocus = true;
465
+ }
466
+
467
+ function blur() {
468
+ this.hasFocus = false;
469
+ }
470
+
471
+ return {
472
+ getDocument: getDocument,
473
+ getWindow: getWindow,
474
+ focus: focus,
475
+ blur: blur
476
+ };
477
+ })();
478
+
479
+ WysiHat.Editor.include(WysiHat.Window);
480
+ WysiHat.iFrame = {
481
+ create: function(textarea, callback) {
482
+ var editArea = new Element('iframe', { 'id': textarea.id + '_editor', 'class': 'editor' });
483
+
484
+ Object.extend(editArea, WysiHat.iFrame.Methods);
485
+ WysiHat.Editor.extend(editArea);
486
+
487
+ editArea.attach(textarea, callback);
488
+ textarea.insert({before: editArea});
489
+
490
+ return editArea;
491
+ }
492
+ };
493
+
494
+ WysiHat.iFrame.Methods = {
495
+ attach: function(element, callback) {
496
+ this.textarea = element;
497
+
498
+ this.observe('load', function() {
499
+ try {
500
+ var document = this.getDocument();
501
+ } catch(e) { return; } // No iframe, just stop
502
+
503
+ this.selection = new WysiHat.Selection(this);
504
+
505
+ if (this.ready && document.designMode == 'on')
506
+ return;
507
+
508
+ this.setStyle({});
509
+ document.designMode = 'on';
510
+ callback(this);
511
+ this.ready = true;
512
+ this.fire('wysihat:ready');
513
+ });
514
+ },
515
+
516
+ unattach: function() {
517
+ this.remove();
518
+ },
519
+
520
+ whenReady: function(callback) {
521
+ if (this.ready) {
522
+ callback(this);
523
+ } else {
524
+ var editor = this;
525
+ editor.observe('wysihat:ready', function() { callback(editor); });
526
+ }
527
+ return this;
528
+ },
529
+
530
+ setStyle: function(styles) {
531
+ var document = this.getDocument();
532
+
533
+ var element = this;
534
+ if (!this.ready)
535
+ return setTimeout(function() { element.setStyle(styles); }, 1);
536
+
537
+ if (Prototype.Browser.IE) {
538
+ var style = document.createStyleSheet();
539
+ style.addRule("body", "border: 0");
540
+ style.addRule("p", "margin: 0");
541
+
542
+ $H(styles).each(function(pair) {
543
+ var value = pair.first().underscore().dasherize() + ": " + pair.last();
544
+ style.addRule("body", value);
545
+ });
546
+ } else if (Prototype.Browser.Opera) {
547
+ var style = Element('style').update("p { margin: 0; }");
548
+ var head = document.getElementsByTagName('head')[0];
549
+ head.appendChild(style);
550
+ } else {
551
+ Element.setStyle(document.body, styles);
552
+ }
553
+
554
+ return this;
555
+ },
556
+
557
+ /**
558
+ * WysiHat.iFrame.Methods#getStyle(style) -> string
559
+ * - style specificication (i.e. backgroundColor)
560
+ *
561
+ * Returns the style from the element based on the given style
562
+ */
563
+ getStyle: function(style) {
564
+ var document = this.getDocument();
565
+ return Element.getStyle(document.body, style);
566
+ },
567
+
568
+ rawContent: function() {
569
+ var document = this.getDocument();
570
+
571
+ if (document.body)
572
+ return document.body.innerHTML;
573
+ else
574
+ return "";
575
+ },
576
+
577
+ setRawContent: function(text) {
578
+ var document = this.getDocument();
579
+ if (document.body)
580
+ document.body.innerHTML = text;
581
+ }
582
+ };
583
+ WysiHat.Editable = {
584
+ create: function(textarea, callback) {
585
+ var editArea = new Element('div', {
586
+ 'id': textarea.id + '_editor',
587
+ 'class': 'editor',
588
+ 'contenteditable': 'true'
589
+ });
590
+ editArea.textarea = textarea;
591
+
592
+ WysiHat.Editor.extend(editArea);
593
+ Object.extend(editArea, WysiHat.Editable.Methods);
594
+
595
+ callback(editArea);
596
+
597
+ textarea.insert({before: editArea});
598
+
599
+ return editArea;
600
+ }
601
+ };
602
+
603
+ WysiHat.Editable.Methods = {
604
+ getDocument: function() {
605
+ return document;
606
+ },
607
+
608
+ getWindow: function() {
609
+ return window;
610
+ },
611
+
612
+ rawContent: function() {
613
+ return this.innerHTML;
614
+ },
615
+
616
+ setRawContent: function(text) {
617
+ this.innerHTML = text;
618
+ }
619
+ };
620
+
621
+ Object.extend(String.prototype, (function() {
622
+ function formatHTMLOutput() {
623
+ var text = String(this);
624
+ text = text.tidyXHTML();
625
+
626
+ if (Prototype.Browser.WebKit) {
627
+ text = text.replace(/(<div>)+/g, "\n");
628
+ text = text.replace(/(<\/div>)+/g, "");
629
+
630
+ text = text.replace(/<p>\s*<\/p>/g, "");
631
+
632
+ text = text.replace(/<br \/>(\n)*/g, "\n");
633
+ } else if (Prototype.Browser.Gecko) {
634
+ text = text.replace(/<p>/g, "");
635
+ text = text.replace(/<\/p>(\n)?/g, "\n");
636
+
637
+ text = text.replace(/<br \/>(\n)*/g, "\n");
638
+ } else if (Prototype.Browser.IE || Prototype.Browser.Opera) {
639
+ text = text.replace(/<p>(&nbsp;|&#160;|\s)<\/p>/g, "<p></p>");
640
+
641
+ text = text.replace(/<br \/>/g, "");
642
+
643
+ text = text.replace(/<p>/g, '');
644
+
645
+ text = text.replace(/&nbsp;/g, '');
646
+
647
+ text = text.replace(/<\/p>(\n)?/g, "\n");
648
+
649
+ text = text.gsub(/^<p>/, '');
650
+ text = text.gsub(/<\/p>$/, '');
651
+ }
652
+
653
+ text = text.gsub(/<b>/, "<strong>");
654
+ text = text.gsub(/<\/b>/, "</strong>");
655
+
656
+ text = text.gsub(/<i>/, "<em>");
657
+ text = text.gsub(/<\/i>/, "</em>");
658
+
659
+ text = text.replace(/\n\n+/g, "</p>\n\n<p>");
660
+
661
+ text = text.gsub(/(([^\n])(\n))(?=([^\n]))/, "#{2}<br />\n");
662
+
663
+ text = '<p>' + text + '</p>';
664
+
665
+ text = text.replace(/<p>\s*/g, "<p>");
666
+ text = text.replace(/\s*<\/p>/g, "</p>");
667
+
668
+ var element = Element("body");
669
+ element.innerHTML = text;
670
+
671
+ if (Prototype.Browser.WebKit || Prototype.Browser.Gecko) {
672
+ var replaced;
673
+ do {
674
+ replaced = false;
675
+ element.select('span').each(function(span) {
676
+ if (span.hasClassName('Apple-style-span')) {
677
+ span.removeClassName('Apple-style-span');
678
+ if (span.className == '')
679
+ span.removeAttribute('class');
680
+ replaced = true;
681
+ } else if (span.getStyle('fontWeight') == 'bold') {
682
+ span.setStyle({fontWeight: ''});
683
+ if (span.style.length == 0)
684
+ span.removeAttribute('style');
685
+ span.update('<strong>' + span.innerHTML + '</strong>');
686
+ replaced = true;
687
+ } else if (span.getStyle('fontStyle') == 'italic') {
688
+ span.setStyle({fontStyle: ''});
689
+ if (span.style.length == 0)
690
+ span.removeAttribute('style');
691
+ span.update('<em>' + span.innerHTML + '</em>');
692
+ replaced = true;
693
+ } else if (span.getStyle('textDecoration') == 'underline') {
694
+ span.setStyle({textDecoration: ''});
695
+ if (span.style.length == 0)
696
+ span.removeAttribute('style');
697
+ span.update('<u>' + span.innerHTML + '</u>');
698
+ replaced = true;
699
+ } else if (span.attributes.length == 0) {
700
+ span.replace(span.innerHTML);
701
+ replaced = true;
702
+ }
703
+ });
704
+ } while (replaced);
705
+
706
+ }
707
+
708
+ var acceptableBlankTags = $A(['BR', 'IMG']);
709
+
710
+ for (var i = 0; i < element.descendants().length; i++) {
711
+ var node = element.descendants()[i];
712
+ if (node.innerHTML.blank() && !acceptableBlankTags.include(node.nodeName) && node.id != 'bookmark')
713
+ node.remove();
714
+ }
715
+
716
+ text = element.innerHTML;
717
+ text = text.tidyXHTML();
718
+
719
+ text = text.replace(/<br \/>(\n)*/g, "<br />\n");
720
+ text = text.replace(/<\/p>\n<p>/g, "</p>\n\n<p>");
721
+
722
+ text = text.replace(/<p>\s*<\/p>/g, "");
723
+
724
+ text = text.replace(/\s*$/g, "");
725
+
726
+ return text;
727
+ }
728
+
729
+ function formatHTMLInput() {
730
+ var text = String(this);
731
+
732
+ var element = Element("body");
733
+ element.innerHTML = text;
734
+
735
+ if (Prototype.Browser.Gecko || Prototype.Browser.WebKit) {
736
+ element.select('strong').each(function(element) {
737
+ element.replace('<span style="font-weight: bold;">' + element.innerHTML + '</span>');
738
+ });
739
+ element.select('em').each(function(element) {
740
+ element.replace('<span style="font-style: italic;">' + element.innerHTML + '</span>');
741
+ });
742
+ element.select('u').each(function(element) {
743
+ element.replace('<span style="text-decoration: underline;">' + element.innerHTML + '</span>');
744
+ });
745
+ }
746
+
747
+ if (Prototype.Browser.WebKit)
748
+ element.select('span').each(function(span) {
749
+ if (span.getStyle('fontWeight') == 'bold')
750
+ span.addClassName('Apple-style-span');
751
+
752
+ if (span.getStyle('fontStyle') == 'italic')
753
+ span.addClassName('Apple-style-span');
754
+
755
+ if (span.getStyle('textDecoration') == 'underline')
756
+ span.addClassName('Apple-style-span');
757
+ });
758
+
759
+ text = element.innerHTML;
760
+ text = text.tidyXHTML();
761
+
762
+ text = text.replace(/<\/p>(\n)*<p>/g, "\n\n");
763
+
764
+ text = text.replace(/(\n)?<br( \/)?>(\n)?/g, "\n");
765
+
766
+ text = text.replace(/^<p>/g, '');
767
+ text = text.replace(/<\/p>$/g, '');
768
+
769
+ if (Prototype.Browser.Gecko) {
770
+ text = text.replace(/\n/g, "<br>");
771
+ text = text + '<br>';
772
+ } else if (Prototype.Browser.WebKit) {
773
+ text = text.replace(/\n/g, "</div><div>");
774
+ text = '<div>' + text + '</div>';
775
+ text = text.replace(/<div><\/div>/g, "<div><br></div>");
776
+ } else if (Prototype.Browser.IE || Prototype.Browser.Opera) {
777
+ text = text.replace(/\n/g, "</p>\n<p>");
778
+ text = '<p>' + text + '</p>';
779
+ text = text.replace(/<p><\/p>/g, "<p>&nbsp;</p>");
780
+ text = text.replace(/(<p>&nbsp;<\/p>)+$/g, "");
781
+ }
782
+
783
+ return text;
784
+ }
785
+
786
+ function tidyXHTML() {
787
+ var text = String(this);
788
+
789
+ text = text.gsub(/\r\n?/, "\n");
790
+
791
+ text = text.gsub(/<([A-Z]+)([^>]*)>/, function(match) {
792
+ return '<' + match[1].toLowerCase() + match[2] + '>';
793
+ });
794
+
795
+ text = text.gsub(/<\/([A-Z]+)>/, function(match) {
796
+ return '</' + match[1].toLowerCase() + '>';
797
+ });
798
+
799
+ text = text.replace(/<br>/g, "<br />");
800
+
801
+ return text;
802
+ }
803
+
804
+ return {
805
+ formatHTMLOutput: formatHTMLOutput,
806
+ formatHTMLInput: formatHTMLInput,
807
+ tidyXHTML: tidyXHTML
808
+ };
809
+ })());
810
+ Object.extend(String.prototype, {
811
+ sanitize: function(options) {
812
+ return Element("div").update(this).sanitize(options).innerHTML.tidyXHTML();
813
+ }
814
+ });
815
+
816
+ Element.addMethods({
817
+ sanitize: function(element, options) {
818
+ element = $(element);
819
+ options = $H(options);
820
+ var allowed_tags = $A(options.get('tags') || []);
821
+ var allowed_attributes = $A(options.get('attributes') || []);
822
+ var sanitized = Element(element.nodeName);
823
+
824
+ $A(element.childNodes).each(function(child) {
825
+ if (child.nodeType == 1) {
826
+ var children = $(child).sanitize(options).childNodes;
827
+
828
+ if (allowed_tags.include(child.nodeName.toLowerCase())) {
829
+ var new_child = Element(child.nodeName);
830
+ allowed_attributes.each(function(attribute) {
831
+ if ((value = child.readAttribute(attribute)))
832
+ new_child.writeAttribute(attribute, value);
833
+ });
834
+ sanitized.appendChild(new_child);
835
+
836
+ $A(children).each(function(grandchild) { new_child.appendChild(grandchild); });
837
+ } else {
838
+ $A(children).each(function(grandchild) { sanitized.appendChild(grandchild); });
839
+ }
840
+ } else if (child.nodeType == 3) {
841
+ sanitized.appendChild(child);
842
+ }
843
+ });
844
+ return sanitized;
845
+ }
846
+ });
847
+
848
+
849
+ if (typeof Range == 'undefined') {
850
+ Range = function(ownerDocument) {
851
+ this.ownerDocument = ownerDocument;
852
+
853
+ this.startContainer = this.ownerDocument.documentElement;
854
+ this.startOffset = 0;
855
+ this.endContainer = this.ownerDocument.documentElement;
856
+ this.endOffset = 0;
857
+
858
+ this.collapsed = true;
859
+ this.commonAncestorContainer = this._commonAncestorContainer(this.startContainer, this.endContainer);
860
+
861
+ this.detached = false;
862
+
863
+ this.START_TO_START = 0;
864
+ this.START_TO_END = 1;
865
+ this.END_TO_END = 2;
866
+ this.END_TO_START = 3;
867
+ }
868
+
869
+ Range.CLONE_CONTENTS = 0;
870
+ Range.DELETE_CONTENTS = 1;
871
+ Range.EXTRACT_CONTENTS = 2;
872
+
873
+ if (!document.createRange) {
874
+ document.createRange = function() {
875
+ return new Range(this);
876
+ };
877
+ }
878
+
879
+ Object.extend(Range.prototype, (function() {
880
+ function cloneContents() {
881
+ return _processContents(this, Range.CLONE_CONTENTS);
882
+ }
883
+
884
+ function cloneRange() {
885
+ try {
886
+ var clone = new Range(this.ownerDocument);
887
+ clone.startContainer = this.startContainer;
888
+ clone.startOffset = this.startOffset;
889
+ clone.endContainer = this.endContainer;
890
+ clone.endOffset = this.endOffset;
891
+ clone.collapsed = this.collapsed;
892
+ clone.commonAncestorContainer = this.commonAncestorContainer;
893
+ clone.detached = this.detached;
894
+
895
+ return clone;
896
+
897
+ } catch (e) {
898
+ return null;
899
+ };
900
+ }
901
+
902
+ function collapse(toStart) {
903
+ if (toStart) {
904
+ this.endContainer = this.startContainer;
905
+ this.endOffset = this.startOffset;
906
+ this.collapsed = true;
907
+ } else {
908
+ this.startContainer = this.endContainer;
909
+ this.startOffset = this.endOffset;
910
+ this.collapsed = true;
911
+ }
912
+ }
913
+
914
+ function compareBoundaryPoints(compareHow, sourceRange) {
915
+ try {
916
+ var cmnSelf, cmnSource, rootSelf, rootSource;
917
+
918
+ cmnSelf = this.commonAncestorContainer;
919
+ cmnSource = sourceRange.commonAncestorContainer;
920
+
921
+ rootSelf = cmnSelf;
922
+ while (rootSelf.parentNode) {
923
+ rootSelf = rootSelf.parentNode;
924
+ }
925
+
926
+ rootSource = cmnSource;
927
+ while (rootSource.parentNode) {
928
+ rootSource = rootSource.parentNode;
929
+ }
930
+
931
+ switch (compareHow) {
932
+ case this.START_TO_START:
933
+ return _compareBoundaryPoints(this, this.startContainer, this.startOffset, sourceRange.startContainer, sourceRange.startOffset);
934
+ break;
935
+ case this.START_TO_END:
936
+ return _compareBoundaryPoints(this, this.startContainer, this.startOffset, sourceRange.endContainer, sourceRange.endOffset);
937
+ break;
938
+ case this.END_TO_END:
939
+ return _compareBoundaryPoints(this, this.endContainer, this.endOffset, sourceRange.endContainer, sourceRange.endOffset);
940
+ break;
941
+ case this.END_TO_START:
942
+ return _compareBoundaryPoints(this, this.endContainer, this.endOffset, sourceRange.startContainer, sourceRange.startOffset);
943
+ break;
944
+ }
945
+ } catch (e) {};
946
+
947
+ return null;
948
+ }
949
+
950
+ function deleteContents() {
951
+ try {
952
+ _processContents(this, Range.DELETE_CONTENTS);
953
+ } catch (e) {}
954
+ }
955
+
956
+ function detach() {
957
+ this.detached = true;
958
+ }
959
+
960
+ function extractContents() {
961
+ try {
962
+ return _processContents(this, Range.EXTRACT_CONTENTS);
963
+ } catch (e) {
964
+ return null;
965
+ };
966
+ }
967
+
968
+ function insertNode(newNode) {
969
+ try {
970
+ var n, newText, offset;
971
+
972
+ switch (this.startContainer.nodeType) {
973
+ case Node.CDATA_SECTION_NODE:
974
+ case Node.TEXT_NODE:
975
+ newText = this.startContainer.splitText(this.startOffset);
976
+ this.startContainer.parentNode.insertBefore(newNode, newText);
977
+ break;
978
+ default:
979
+ if (this.startContainer.childNodes.length == 0) {
980
+ offset = null;
981
+ } else {
982
+ offset = this.startContainer.childNodes(this.startOffset);
983
+ }
984
+ this.startContainer.insertBefore(newNode, offset);
985
+ }
986
+ } catch (e) {}
987
+ }
988
+
989
+ function selectNode(refNode) {
990
+ this.setStartBefore(refNode);
991
+ this.setEndAfter(refNode);
992
+ }
993
+
994
+ function selectNodeContents(refNode) {
995
+ this.setStart(refNode, 0);
996
+ this.setEnd(refNode, refNode.childNodes.length);
997
+ }
998
+
999
+ function setStart(refNode, offset) {
1000
+ try {
1001
+ var endRootContainer, startRootContainer;
1002
+
1003
+ this.startContainer = refNode;
1004
+ this.startOffset = offset;
1005
+
1006
+ endRootContainer = this.endContainer;
1007
+ while (endRootContainer.parentNode) {
1008
+ endRootContainer = endRootContainer.parentNode;
1009
+ }
1010
+ startRootContainer = this.startContainer;
1011
+ while (startRootContainer.parentNode) {
1012
+ startRootContainer = startRootContainer.parentNode;
1013
+ }
1014
+ if (startRootContainer != endRootContainer) {
1015
+ this.collapse(true);
1016
+ } else {
1017
+ if (_compareBoundaryPoints(this, this.startContainer, this.startOffset, this.endContainer, this.endOffset) > 0) {
1018
+ this.collapse(true);
1019
+ }
1020
+ }
1021
+
1022
+ this.collapsed = _isCollapsed(this);
1023
+
1024
+ this.commonAncestorContainer = _commonAncestorContainer(this.startContainer, this.endContainer);
1025
+ } catch (e) {}
1026
+ }
1027
+
1028
+ function setStartAfter(refNode) {
1029
+ this.setStart(refNode.parentNode, _nodeIndex(refNode) + 1);
1030
+ }
1031
+
1032
+ function setStartBefore(refNode) {
1033
+ this.setStart(refNode.parentNode, _nodeIndex(refNode));
1034
+ }
1035
+
1036
+ function setEnd(refNode, offset) {
1037
+ try {
1038
+ this.endContainer = refNode;
1039
+ this.endOffset = offset;
1040
+
1041
+ endRootContainer = this.endContainer;
1042
+ while (endRootContainer.parentNode) {
1043
+ endRootContainer = endRootContainer.parentNode;
1044
+ }
1045
+ startRootContainer = this.startContainer;
1046
+ while (startRootContainer.parentNode) {
1047
+ startRootContainer = startRootContainer.parentNode;
1048
+ }
1049
+ if (startRootContainer != endRootContainer) {
1050
+ this.collapse(false);
1051
+ } else {
1052
+ if (_compareBoundaryPoints(this, this.startContainer, this.startOffset, this.endContainer, this.endOffset) > 0) {
1053
+ this.collapse(false);
1054
+ }
1055
+ }
1056
+
1057
+ this.collapsed = _isCollapsed(this);
1058
+
1059
+ this.commonAncestorContainer = _commonAncestorContainer(this.startContainer, this.endContainer);
1060
+
1061
+ } catch (e) {}
1062
+ }
1063
+
1064
+ function setEndAfter(refNode) {
1065
+ this.setEnd(refNode.parentNode, _nodeIndex(refNode) + 1);
1066
+ }
1067
+
1068
+ function setEndBefore(refNode) {
1069
+ this.setEnd(refNode.parentNode, _nodeIndex(refNode));
1070
+ }
1071
+
1072
+ function surroundContents(newParent) {
1073
+ try {
1074
+ var n, fragment;
1075
+
1076
+ while (newParent.firstChild) {
1077
+ newParent.removeChild(newParent.firstChild);
1078
+ }
1079
+
1080
+ fragment = this.extractContents();
1081
+ this.insertNode(newParent);
1082
+ newParent.appendChild(fragment);
1083
+ this.selectNode(newParent);
1084
+ } catch (e) {}
1085
+ }
1086
+
1087
+ function _compareBoundaryPoints(range, containerA, offsetA, containerB, offsetB) {
1088
+ var c, offsetC, n, cmnRoot, childA;
1089
+ if (containerA == containerB) {
1090
+ if (offsetA == offsetB) {
1091
+ return 0; // equal
1092
+ } else if (offsetA < offsetB) {
1093
+ return -1; // before
1094
+ } else {
1095
+ return 1; // after
1096
+ }
1097
+ }
1098
+
1099
+ c = containerB;
1100
+ while (c && c.parentNode != containerA) {
1101
+ c = c.parentNode;
1102
+ }
1103
+ if (c) {
1104
+ offsetC = 0;
1105
+ n = containerA.firstChild;
1106
+ while (n != c && offsetC < offsetA) {
1107
+ offsetC++;
1108
+ n = n.nextSibling;
1109
+ }
1110
+ if (offsetA <= offsetC) {
1111
+ return -1; // before
1112
+ } else {
1113
+ return 1; // after
1114
+ }
1115
+ }
1116
+
1117
+ c = containerA;
1118
+ while (c && c.parentNode != containerB) {
1119
+ c = c.parentNode;
1120
+ }
1121
+ if (c) {
1122
+ offsetC = 0;
1123
+ n = containerB.firstChild;
1124
+ while (n != c && offsetC < offsetB) {
1125
+ offsetC++;
1126
+ n = n.nextSibling;
1127
+ }
1128
+ if (offsetC < offsetB) {
1129
+ return -1; // before
1130
+ } else {
1131
+ return 1; // after
1132
+ }
1133
+ }
1134
+
1135
+ cmnRoot = range._commonAncestorContainer(containerA, containerB);
1136
+ childA = containerA;
1137
+ while (childA && childA.parentNode != cmnRoot) {
1138
+ childA = childA.parentNode;
1139
+ }
1140
+ if (!childA) {
1141
+ childA = cmnRoot;
1142
+ }
1143
+ childB = containerB;
1144
+ while (childB && childB.parentNode != cmnRoot) {
1145
+ childB = childB.parentNode;
1146
+ }
1147
+ if (!childB) {
1148
+ childB = cmnRoot;
1149
+ }
1150
+
1151
+ if (childA == childB) {
1152
+ return 0; // equal
1153
+ }
1154
+
1155
+ n = cmnRoot.firstChild;
1156
+ while (n) {
1157
+ if (n == childA) {
1158
+ return -1; // before
1159
+ }
1160
+ if (n == childB) {
1161
+ return 1; // after
1162
+ }
1163
+ n = n.nextSibling;
1164
+ }
1165
+
1166
+ return null;
1167
+ }
1168
+
1169
+ function _commonAncestorContainer(containerA, containerB) {
1170
+ var parentStart = containerA, parentEnd;
1171
+ while (parentStart) {
1172
+ parentEnd = containerB;
1173
+ while (parentEnd && parentStart != parentEnd) {
1174
+ parentEnd = parentEnd.parentNode;
1175
+ }
1176
+ if (parentStart == parentEnd) {
1177
+ break;
1178
+ }
1179
+ parentStart = parentStart.parentNode;
1180
+ }
1181
+
1182
+ if (!parentStart && containerA.ownerDocument) {
1183
+ return containerA.ownerDocument.documentElement;
1184
+ }
1185
+
1186
+ return parentStart;
1187
+ }
1188
+
1189
+ function _isCollapsed(range) {
1190
+ return (range.startContainer == range.endContainer && range.startOffset == range.endOffset);
1191
+ }
1192
+
1193
+ function _offsetInCharacters(node) {
1194
+ switch (node.nodeType) {
1195
+ case Node.CDATA_SECTION_NODE:
1196
+ case Node.COMMENT_NODE:
1197
+ case Node.ELEMENT_NODE:
1198
+ case Node.PROCESSING_INSTRUCTION_NODE:
1199
+ return true;
1200
+ default:
1201
+ return false;
1202
+ }
1203
+ }
1204
+
1205
+ function _processContents(range, action) {
1206
+ try {
1207
+
1208
+ var cmnRoot, partialStart = null, partialEnd = null, fragment, n, c, i;
1209
+ var leftContents, leftParent, leftContentsParent;
1210
+ var rightContents, rightParent, rightContentsParent;
1211
+ var next, prev;
1212
+ var processStart, processEnd;
1213
+ if (range.collapsed) {
1214
+ return null;
1215
+ }
1216
+
1217
+ cmnRoot = range.commonAncestorContainer;
1218
+
1219
+ if (range.startContainer != cmnRoot) {
1220
+ partialStart = range.startContainer;
1221
+ while (partialStart.parentNode != cmnRoot) {
1222
+ partialStart = partialStart.parentNode;
1223
+ }
1224
+ }
1225
+
1226
+ if (range.endContainer != cmnRoot) {
1227
+ partialEnd = range.endContainer;
1228
+ while (partialEnd.parentNode != cmnRoot) {
1229
+ partialEnd = partialEnd.parentNode;
1230
+ }
1231
+ }
1232
+
1233
+ if (action == Range.EXTRACT_CONTENTS || action == Range.CLONE_CONTENTS) {
1234
+ fragment = range.ownerDocument.createDocumentFragment();
1235
+ }
1236
+
1237
+ if (range.startContainer == range.endContainer) {
1238
+ switch (range.startContainer.nodeType) {
1239
+ case Node.CDATA_SECTION_NODE:
1240
+ case Node.COMMENT_NODE:
1241
+ case Node.TEXT_NODE:
1242
+ if (action == Range.EXTRACT_CONTENTS || action == Range.CLONE_CONTENTS) {
1243
+ c = range.startContainer.cloneNode();
1244
+ c.deleteData(range.endOffset, range.startContainer.data.length - range.endOffset);
1245
+ c.deleteData(0, range.startOffset);
1246
+ fragment.appendChild(c);
1247
+ }
1248
+ if (action == Range.EXTRACT_CONTENTS || action == Range.DELETE_CONTENTS) {
1249
+ range.startContainer.deleteData(range.startOffset, range.endOffset - range.startOffset);
1250
+ }
1251
+ break;
1252
+ case Node.PROCESSING_INSTRUCTION_NODE:
1253
+ break;
1254
+ default:
1255
+ n = range.startContainer.firstChild;
1256
+ for (i = 0; i < range.startOffset; i++) {
1257
+ n = n.nextSibling;
1258
+ }
1259
+ while (n && i < range.endOffset) {
1260
+ next = n.nextSibling;
1261
+ if (action == Range.EXTRACT_CONTENTS) {
1262
+ fragment.appendChild(n);
1263
+ } else if (action == Range.CLONE_CONTENTS) {
1264
+ fragment.appendChild(n.cloneNode());
1265
+ } else {
1266
+ range.startContainer.removeChild(n);
1267
+ }
1268
+ n = next;
1269
+ i++;
1270
+ }
1271
+ }
1272
+ range.collapse(true);
1273
+ return fragment;
1274
+ }
1275
+
1276
+
1277
+ if (range.startContainer != cmnRoot) {
1278
+ switch (range.startContainer.nodeType) {
1279
+ case Node.CDATA_SECTION_NODE:
1280
+ case Node.COMMENT_NODE:
1281
+ case Node.TEXT_NODE:
1282
+ if (action == Range.EXTRACT_CONTENTS || action == Range.CLONE_CONTENTS) {
1283
+ c = range.startContainer.cloneNode(true);
1284
+ c.deleteData(0, range.startOffset);
1285
+ leftContents = c;
1286
+ }
1287
+ if (action == Range.EXTRACT_CONTENTS || action == Range.DELETE_CONTENTS) {
1288
+ range.startContainer.deleteData(range.startOffset, range.startContainer.data.length - range.startOffset);
1289
+ }
1290
+ break;
1291
+ case Node.PROCESSING_INSTRUCTION_NODE:
1292
+ break;
1293
+ default:
1294
+ if (action == Range.EXTRACT_CONTENTS || action == Range.CLONE_CONTENTS) {
1295
+ leftContents = range.startContainer.cloneNode(false);
1296
+ }
1297
+ n = range.startContainer.firstChild;
1298
+ for (i = 0; i < range.startOffset; i++) {
1299
+ n = n.nextSibling;
1300
+ }
1301
+ while (n && i < range.endOffset) {
1302
+ next = n.nextSibling;
1303
+ if (action == Range.EXTRACT_CONTENTS) {
1304
+ fragment.appendChild(n);
1305
+ } else if (action == Range.CLONE_CONTENTS) {
1306
+ fragment.appendChild(n.cloneNode());
1307
+ } else {
1308
+ range.startContainer.removeChild(n);
1309
+ }
1310
+ n = next;
1311
+ i++;
1312
+ }
1313
+ }
1314
+
1315
+ leftParent = range.startContainer.parentNode;
1316
+ n = range.startContainer.nextSibling;
1317
+ for(; leftParent != cmnRoot; leftParent = leftParent.parentNode) {
1318
+ if (action == Range.EXTRACT_CONTENTS || action == Range.CLONE_CONTENTS) {
1319
+ leftContentsParent = leftParent.cloneNode(false);
1320
+ leftContentsParent.appendChild(leftContents);
1321
+ leftContents = leftContentsParent;
1322
+ }
1323
+
1324
+ for (; n; n = next) {
1325
+ next = n.nextSibling;
1326
+ if (action == Range.EXTRACT_CONTENTS) {
1327
+ leftContents.appendChild(n);
1328
+ } else if (action == Range.CLONE_CONTENTS) {
1329
+ leftContents.appendChild(n.cloneNode(true));
1330
+ } else {
1331
+ leftParent.removeChild(n);
1332
+ }
1333
+ }
1334
+ n = leftParent.nextSibling;
1335
+ }
1336
+ }
1337
+
1338
+ if (range.endContainer != cmnRoot) {
1339
+ switch (range.endContainer.nodeType) {
1340
+ case Node.CDATA_SECTION_NODE:
1341
+ case Node.COMMENT_NODE:
1342
+ case Node.TEXT_NODE:
1343
+ if (action == Range.EXTRACT_CONTENTS || action == Range.CLONE_CONTENTS) {
1344
+ c = range.endContainer.cloneNode(true);
1345
+ c.deleteData(range.endOffset, range.endContainer.data.length - range.endOffset);
1346
+ rightContents = c;
1347
+ }
1348
+ if (action == Range.EXTRACT_CONTENTS || action == Range.DELETE_CONTENTS) {
1349
+ range.endContainer.deleteData(0, range.endOffset);
1350
+ }
1351
+ break;
1352
+ case Node.PROCESSING_INSTRUCTION_NODE:
1353
+ break;
1354
+ default:
1355
+ if (action == Range.EXTRACT_CONTENTS || action == Range.CLONE_CONTENTS) {
1356
+ rightContents = range.endContainer.cloneNode(false);
1357
+ }
1358
+ n = range.endContainer.firstChild;
1359
+ if (n && range.endOffset) {
1360
+ for (i = 0; i+1 < range.endOffset; i++) {
1361
+ next = n.nextSibling;
1362
+ if (!next) {
1363
+ break;
1364
+ }
1365
+ n = next;
1366
+ }
1367
+ for (; n; n = prev) {
1368
+ prev = n.previousSibling;
1369
+ if (action == Range.EXTRACT_CONTENTS) {
1370
+ rightContents.insertBefore(n, rightContents.firstChild);
1371
+ } else if (action == Range.CLONE_CONTENTS) {
1372
+ rightContents.insertBefore(n.cloneNode(True), rightContents.firstChild);
1373
+ } else {
1374
+ range.endContainer.removeChild(n);
1375
+ }
1376
+ }
1377
+ }
1378
+ }
1379
+
1380
+ rightParent = range.endContainer.parentNode;
1381
+ n = range.endContainer.previousSibling;
1382
+ for(; rightParent != cmnRoot; rightParent = rightParent.parentNode) {
1383
+ if (action == Range.EXTRACT_CONTENTS || action == Range.CLONE_CONTENTS) {
1384
+ rightContentsParent = rightContents.cloneNode(false);
1385
+ rightContentsParent.appendChild(rightContents);
1386
+ rightContents = rightContentsParent;
1387
+ }
1388
+
1389
+ for (; n; n = prev) {
1390
+ prev = n.previousSibling;
1391
+ if (action == Range.EXTRACT_CONTENTS) {
1392
+ rightContents.insertBefore(n, rightContents.firstChild);
1393
+ } else if (action == Range.CLONE_CONTENTS) {
1394
+ rightContents.appendChild(n.cloneNode(true), rightContents.firstChild);
1395
+ } else {
1396
+ rightParent.removeChild(n);
1397
+ }
1398
+ }
1399
+ n = rightParent.previousSibling;
1400
+ }
1401
+ }
1402
+
1403
+ if (range.startContainer == cmnRoot) {
1404
+ processStart = range.startContainer.firstChild;
1405
+ for (i = 0; i < range.startOffset; i++) {
1406
+ processStart = processStart.nextSibling;
1407
+ }
1408
+ } else {
1409
+ processStart = range.startContainer;
1410
+ while (processStart.parentNode != cmnRoot) {
1411
+ processStart = processStart.parentNode;
1412
+ }
1413
+ processStart = processStart.nextSibling;
1414
+ }
1415
+ if (range.endContainer == cmnRoot) {
1416
+ processEnd = range.endContainer.firstChild;
1417
+ for (i = 0; i < range.endOffset; i++) {
1418
+ processEnd = processEnd.nextSibling;
1419
+ }
1420
+ } else {
1421
+ processEnd = range.endContainer;
1422
+ while (processEnd.parentNode != cmnRoot) {
1423
+ processEnd = processEnd.parentNode;
1424
+ }
1425
+ }
1426
+
1427
+ if ((action == Range.EXTRACT_CONTENTS || action == Range.CLONE_CONTENTS) && leftContents) {
1428
+ fragment.appendChild(leftContents);
1429
+ }
1430
+
1431
+ if (processStart) {
1432
+ for (n = processStart; n && n != processEnd; n = next) {
1433
+ next = n.nextSibling;
1434
+ if (action == Range.EXTRACT_CONTENTS) {
1435
+ fragment.appendChild(n);
1436
+ } else if (action == Range.CLONE_CONTENTS) {
1437
+ fragment.appendChild(n.cloneNode(true));
1438
+ } else {
1439
+ cmnRoot.removeChild(n);
1440
+ }
1441
+ }
1442
+ }
1443
+
1444
+ if ((action == Range.EXTRACT_CONTENTS || action == Range.CLONE_CONTENTS) && rightContents) {
1445
+ fragment.appendChild(rightContents);
1446
+ }
1447
+
1448
+ if (action == Range.EXTRACT_CONTENTS || action == Range.DELETE_CONTENTS) {
1449
+ if (!partialStart && !partialEnd) {
1450
+ range.collapse(true);
1451
+ } else if (partialStart) {
1452
+ range.startContainer = partialStart.parentNode;
1453
+ range.endContainer = partialStart.parentNode;
1454
+ range.startOffset = range.endOffset = range._nodeIndex(partialStart) + 1;
1455
+ } else if (partialEnd) {
1456
+ range.startContainer = partialEnd.parentNode;
1457
+ range.endContainer = partialEnd.parentNode;
1458
+ range.startOffset = range.endOffset = range._nodeIndex(partialEnd);
1459
+ }
1460
+ }
1461
+
1462
+ return fragment;
1463
+
1464
+ } catch (e) {
1465
+ return null;
1466
+ };
1467
+ }
1468
+
1469
+ function _nodeIndex(refNode) {
1470
+ var nodeIndex = 0;
1471
+ while (refNode.previousSibling) {
1472
+ nodeIndex++;
1473
+ refNode = refNode.previousSibling;
1474
+ }
1475
+ return nodeIndex;
1476
+ }
1477
+
1478
+ return {
1479
+ setStart: setStart,
1480
+ setEnd: setEnd,
1481
+ setStartBefore: setStartBefore,
1482
+ setStartAfter: setStartAfter,
1483
+ setEndBefore: setEndBefore,
1484
+ setEndAfter: setEndAfter,
1485
+
1486
+ collapse: collapse,
1487
+
1488
+ selectNode: selectNode,
1489
+ selectNodeContents: selectNodeContents,
1490
+
1491
+ compareBoundaryPoints: compareBoundaryPoints,
1492
+
1493
+ deleteContents: deleteContents,
1494
+ extractContents: extractContents,
1495
+ cloneContents: cloneContents,
1496
+
1497
+ insertNode: insertNode,
1498
+ surroundContents: surroundContents,
1499
+
1500
+ cloneRange: cloneRange,
1501
+ toString: toString,
1502
+ detach: detach,
1503
+
1504
+ _commonAncestorContainer: _commonAncestorContainer
1505
+ };
1506
+ })());
1507
+ }
1508
+
1509
+ if (!window.getSelection) {
1510
+ window.getSelection = function() {
1511
+ return Selection.getInstance();
1512
+ };
1513
+
1514
+ SelectionImpl = function() {
1515
+ this.anchorNode = null;
1516
+ this.anchorOffset = 0;
1517
+ this.focusNode = null;
1518
+ this.focusOffset = 0;
1519
+ this.isCollapsed = true;
1520
+ this.rangeCount = 0;
1521
+ this.ranges = [];
1522
+ }
1523
+
1524
+ Object.extend(SelectionImpl.prototype, (function() {
1525
+ function addRange(r) {
1526
+ return true;
1527
+ }
1528
+
1529
+ function collapse() {
1530
+ return true;
1531
+ }
1532
+
1533
+ function collapseToStart() {
1534
+ return true;
1535
+ }
1536
+
1537
+ function collapseToEnd() {
1538
+ return true;
1539
+ }
1540
+
1541
+ function getRangeAt() {
1542
+ return true;
1543
+ }
1544
+
1545
+ function removeAllRanges() {
1546
+ this.anchorNode = null;
1547
+ this.anchorOffset = 0;
1548
+ this.focusNode = null;
1549
+ this.focusOffset = 0;
1550
+ this.isCollapsed = true;
1551
+ this.rangeCount = 0;
1552
+ this.ranges = [];
1553
+ }
1554
+
1555
+ function _addRange(r) {
1556
+ if (r.startContainer.nodeType != Node.TEXT_NODE) {
1557
+ var start = this._getRightStart(r.startContainer);
1558
+ var startOffset = 0;
1559
+ } else {
1560
+ var start = r.startContainer;
1561
+ var startOffset = r.startOffset;
1562
+ }
1563
+ if (r.endContainer.nodeType != Node.TEXT_NODE) {
1564
+ var end = this._getRightEnd(r.endContainer);
1565
+ var endOffset = end.data.length;
1566
+ } else {
1567
+ var end = r.endContainer;
1568
+ var endOffset = r.endOffset;
1569
+ }
1570
+
1571
+ var rStart = this._selectStart(start, startOffset);
1572
+ var rEnd = this._selectEnd(end,endOffset);
1573
+ rStart.setEndPoint('EndToStart', rEnd);
1574
+ rStart.select();
1575
+ document.selection._selectedRange = r;
1576
+ }
1577
+
1578
+ function _getRightStart(start, offset) {
1579
+ if (start.nodeType != Node.TEXT_NODE) {
1580
+ if (start.nodeType == Node.ELEMENT_NODE) {
1581
+ start = start.childNodes(offset);
1582
+ }
1583
+ return getNextTextNode(start);
1584
+ } else {
1585
+ return null;
1586
+ }
1587
+ }
1588
+
1589
+ function _getRightEnd(end, offset) {
1590
+ if (end.nodeType != Node.TEXT_NODE) {
1591
+ if (end.nodeType == Node.ELEMENT_NODE) {
1592
+ end = end.childNodes(offset);
1593
+ }
1594
+ return getPreviousTextNode(end);
1595
+ } else {
1596
+ return null;
1597
+ }
1598
+ }
1599
+
1600
+ function _selectStart(start, offset) {
1601
+ var r = document.body.createTextRange();
1602
+ if (start.nodeType == Node.TEXT_NODE) {
1603
+ var moveCharacters = offset, node = start;
1604
+ var moveToNode = null, collapse = true;
1605
+ while (node.previousSibling) {
1606
+ switch (node.previousSibling.nodeType) {
1607
+ case Node.ELEMENT_NODE:
1608
+ moveToNode = node.previousSibling;
1609
+ collapse = false;
1610
+ break;
1611
+ case Node.TEXT_NODE:
1612
+ moveCharacters += node.previousSibling.data.length;
1613
+ }
1614
+ if (moveToNode != null) {
1615
+ break;
1616
+ }
1617
+ node = node.previousSibling;
1618
+ }
1619
+ if (moveToNode == null) {
1620
+ moveToNode = start.parentNode;
1621
+ }
1622
+
1623
+ r.moveToElementText(moveToNode);
1624
+ r.collapse(collapse);
1625
+ r.move('Character', moveCharacters);
1626
+ return r;
1627
+ } else {
1628
+ return null;
1629
+ }
1630
+ }
1631
+
1632
+ function _selectEnd(end, offset) {
1633
+ var r = document.body.createTextRange(), node = end;
1634
+ if (end.nodeType == 3) {
1635
+ var moveCharacters = end.data.length - offset;
1636
+ var moveToNode = null, collapse = false;
1637
+ while (node.nextSibling) {
1638
+ switch (node.nextSibling.nodeType) {
1639
+ case Node.ELEMENT_NODE:
1640
+ moveToNode = node.nextSibling;
1641
+ collapse = true;
1642
+ break;
1643
+ case Node.TEXT_NODE:
1644
+ moveCharacters += node.nextSibling.data.length;
1645
+ break;
1646
+ }
1647
+ if (moveToNode != null) {
1648
+ break;
1649
+ }
1650
+ node = node.nextSibling;
1651
+ }
1652
+ if (moveToNode == null) {
1653
+ moveToNode = end.parentNode;
1654
+ collapse = false;
1655
+ }
1656
+
1657
+ switch (moveToNode.nodeName.toLowerCase()) {
1658
+ case 'p':
1659
+ case 'div':
1660
+ case 'h1':
1661
+ case 'h2':
1662
+ case 'h3':
1663
+ case 'h4':
1664
+ case 'h5':
1665
+ case 'h6':
1666
+ moveCharacters++;
1667
+ }
1668
+
1669
+ r.moveToElementText(moveToNode);
1670
+ r.collapse(collapse);
1671
+
1672
+ r.move('Character', -moveCharacters);
1673
+ return r;
1674
+ }
1675
+
1676
+ return null;
1677
+ }
1678
+
1679
+ function getPreviousTextNode(node) {
1680
+ var stack = [];
1681
+ var current = null;
1682
+ while (node) {
1683
+ stack = [];
1684
+ current = node;
1685
+ while (current) {
1686
+ while (current) {
1687
+ if (current.nodeType == 3 && current.data.replace(/^\s+|\s+$/, '').length) {
1688
+ return current;
1689
+ }
1690
+ if (current.previousSibling) {
1691
+ stack.push (current.previousSibling);
1692
+ }
1693
+ current = current.lastChild;
1694
+ }
1695
+ current = stack.pop();
1696
+ }
1697
+ node = node.previousSibling;
1698
+ }
1699
+ return null;
1700
+ }
1701
+
1702
+ function getNextTextNode(node) {
1703
+ var stack = [];
1704
+ var current = null;
1705
+ while (node) {
1706
+ stack = [];
1707
+ current = node;
1708
+ while (current) {
1709
+ while (current) {
1710
+ if (current.nodeType == 3 && current.data.replace(/^\s+|\s+$/, '').length) {
1711
+ return current;
1712
+ }
1713
+ if (current.nextSibling) {
1714
+ stack.push (current.nextSibling);
1715
+ }
1716
+ current = current.firstChild;
1717
+ }
1718
+ current = stack.pop();
1719
+ }
1720
+ node = node.nextSibling;
1721
+ }
1722
+ return null;
1723
+ }
1724
+
1725
+ return {
1726
+ removeAllRanges: removeAllRanges,
1727
+
1728
+ _addRange: _addRange,
1729
+ _getRightStart: _getRightStart,
1730
+ _getRightEnd: _getRightEnd,
1731
+ _selectStart: _selectStart,
1732
+ _selectEnd: _selectEnd
1733
+ };
1734
+ })());
1735
+
1736
+ Selection = new function() {
1737
+ var instance = null;
1738
+ this.getInstance = function() {
1739
+ if (instance == null) {
1740
+ return (instance = new SelectionImpl());
1741
+ } else {
1742
+ return instance;
1743
+ }
1744
+ };
1745
+ };
1746
+ }
1747
+
1748
+ Object.extend(Range.prototype, (function() {
1749
+ function getNode() {
1750
+ var node = this.commonAncestorContainer;
1751
+
1752
+ if (this.startContainer == this.endContainer)
1753
+ if (this.startOffset - this.endOffset < 2)
1754
+ node = this.startContainer.childNodes[this.startOffset];
1755
+
1756
+ while (node.nodeType == Node.TEXT_NODE)
1757
+ node = node.parentNode;
1758
+
1759
+ return node;
1760
+ }
1761
+
1762
+ return {
1763
+ getNode: getNode
1764
+ };
1765
+ })());
1766
+ WysiHat.Selection = Class.create((function() {
1767
+ function initialize(editor) {
1768
+ this.window = editor.getWindow();
1769
+ this.document = editor.getDocument();
1770
+
1771
+ if (Prototype.Browser.IE) {
1772
+ editor.observe('wysihat:cursormove', saveRange.bind(this));
1773
+ editor.observe('wysihat:focus', restoreRange);
1774
+ }
1775
+ }
1776
+
1777
+ function getSelection() {
1778
+ return this.window.getSelection ? this.window.getSelection() : this.document.selection;
1779
+ }
1780
+
1781
+ function getRange() {
1782
+ var range = null, selection = this.getSelection();
1783
+
1784
+ try {
1785
+ if (selection.getRangeAt)
1786
+ range = selection.getRangeAt(0);
1787
+ else
1788
+ range = selection.createRange();
1789
+ } catch(e) { return null; }
1790
+
1791
+ if (Prototype.Browser.WebKit) {
1792
+ range.setStart(selection.baseNode, selection.baseOffset);
1793
+ range.setEnd(selection.extentNode, selection.extentOffset);
1794
+ }
1795
+
1796
+ return range;
1797
+ }
1798
+
1799
+ function selectNode(node) {
1800
+ var selection = this.getSelection();
1801
+
1802
+ if (Prototype.Browser.IE) {
1803
+ var range = createRangeFromElement(this.document, node);
1804
+ range.select();
1805
+ } else if (Prototype.Browser.WebKit) {
1806
+ selection.setBaseAndExtent(node, 0, node, node.innerText.length);
1807
+ } else if (Prototype.Browser.Opera) {
1808
+ range = this.document.createRange();
1809
+ range.selectNode(node);
1810
+ selection.removeAllRanges();
1811
+ selection.addRange(range);
1812
+ } else {
1813
+ var range = createRangeFromElement(this.document, node);
1814
+ selection.removeAllRanges();
1815
+ selection.addRange(range);
1816
+ }
1817
+ }
1818
+
1819
+ function getNode() {
1820
+ var nodes = null, candidates = [], children, el;
1821
+ var range = this.getRange();
1822
+
1823
+ if (!range)
1824
+ return null;
1825
+
1826
+ var parent;
1827
+ if (range.parentElement)
1828
+ parent = range.parentElement();
1829
+ else
1830
+ parent = range.commonAncestorContainer;
1831
+
1832
+ if (parent) {
1833
+ while (parent.nodeType != 1) parent = parent.parentNode;
1834
+ if (parent.nodeName.toLowerCase() != "body") {
1835
+ el = parent;
1836
+ do {
1837
+ el = el.parentNode;
1838
+ candidates[candidates.length] = el;
1839
+ } while (el.nodeName.toLowerCase() != "body");
1840
+ }
1841
+ children = parent.all || parent.getElementsByTagName("*");
1842
+ for (var j = 0; j < children.length; j++)
1843
+ candidates[candidates.length] = children[j];
1844
+ nodes = [parent];
1845
+ for (var ii = 0, r2; ii < candidates.length; ii++) {
1846
+ r2 = createRangeFromElement(this.document, candidates[ii]);
1847
+ if (r2 && compareRanges(range, r2))
1848
+ nodes[nodes.length] = candidates[ii];
1849
+ }
1850
+ }
1851
+
1852
+ return nodes.first();
1853
+ }
1854
+
1855
+ function createRangeFromElement(document, node) {
1856
+ if (document.body.createTextRange) {
1857
+ var range = document.body.createTextRange();
1858
+ range.moveToElementText(node);
1859
+ } else if (document.createRange) {
1860
+ var range = document.createRange();
1861
+ range.selectNodeContents(node);
1862
+ }
1863
+ return range;
1864
+ }
1865
+
1866
+ function compareRanges(r1, r2) {
1867
+ if (r1.compareEndPoints) {
1868
+ return !(
1869
+ r2.compareEndPoints('StartToStart', r1) == 1 &&
1870
+ r2.compareEndPoints('EndToEnd', r1) == 1 &&
1871
+ r2.compareEndPoints('StartToEnd', r1) == 1 &&
1872
+ r2.compareEndPoints('EndToStart', r1) == 1
1873
+ ||
1874
+ r2.compareEndPoints('StartToStart', r1) == -1 &&
1875
+ r2.compareEndPoints('EndToEnd', r1) == -1 &&
1876
+ r2.compareEndPoints('StartToEnd', r1) == -1 &&
1877
+ r2.compareEndPoints('EndToStart', r1) == -1
1878
+ );
1879
+ } else if (r1.compareBoundaryPoints) {
1880
+ return !(
1881
+ r2.compareBoundaryPoints(0, r1) == 1 &&
1882
+ r2.compareBoundaryPoints(2, r1) == 1 &&
1883
+ r2.compareBoundaryPoints(1, r1) == 1 &&
1884
+ r2.compareBoundaryPoints(3, r1) == 1
1885
+ ||
1886
+ r2.compareBoundaryPoints(0, r1) == -1 &&
1887
+ r2.compareBoundaryPoints(2, r1) == -1 &&
1888
+ r2.compareBoundaryPoints(1, r1) == -1 &&
1889
+ r2.compareBoundaryPoints(3, r1) == -1
1890
+ );
1891
+ }
1892
+
1893
+ return null;
1894
+ };
1895
+
1896
+ function setBookmark() {
1897
+ var bookmark = this.document.getElementById('bookmark');
1898
+ if (bookmark)
1899
+ bookmark.parentNode.removeChild(bookmark);
1900
+
1901
+ bookmark = this.document.createElement('span');
1902
+ bookmark.id = 'bookmark';
1903
+ bookmark.innerHTML = '&nbsp;';
1904
+
1905
+ if (Prototype.Browser.IE) {
1906
+ var range = this.document.selection.createRange();
1907
+ var parent = this.document.createElement('div');
1908
+ parent.appendChild(bookmark);
1909
+ range.collapse();
1910
+ range.pasteHTML(parent.innerHTML);
1911
+ }
1912
+ else {
1913
+ var range = this.getRange();
1914
+ range.insertNode(bookmark);
1915
+ }
1916
+ }
1917
+
1918
+ function moveToBookmark() {
1919
+ var bookmark = this.document.getElementById('bookmark');
1920
+ if (!bookmark)
1921
+ return;
1922
+
1923
+ if (Prototype.Browser.IE) {
1924
+ var range = this.getRange();
1925
+ range.moveToElementText(bookmark);
1926
+ range.collapse();
1927
+ range.select();
1928
+ } else if (Prototype.Browser.WebKit) {
1929
+ var selection = this.getSelection();
1930
+ selection.setBaseAndExtent(bookmark, 0, bookmark, 0);
1931
+ } else {
1932
+ var range = this.getRange();
1933
+ range.setStartBefore(bookmark);
1934
+ }
1935
+
1936
+ bookmark.parentNode.removeChild(bookmark);
1937
+ }
1938
+
1939
+ var savedRange = null;
1940
+ function saveRange() {
1941
+ savedRange = this.getRange();
1942
+ }
1943
+
1944
+ function restoreRange() {
1945
+ if (savedRange) savedRange.select();
1946
+ }
1947
+
1948
+ return {
1949
+ initialize: initialize,
1950
+ getSelection: getSelection,
1951
+ getRange: getRange,
1952
+ getNode: getNode,
1953
+ selectNode: selectNode,
1954
+ setBookmark: setBookmark,
1955
+ moveToBookmark: moveToBookmark,
1956
+ restore: restoreRange
1957
+ };
1958
+ })());
1959
+ WysiHat.Toolbar = Class.create((function() {
1960
+ function initialize(editor) {
1961
+ this.editor = editor;
1962
+ this.element = this.createToolbarElement();
1963
+ }
1964
+
1965
+ function createToolbarElement() {
1966
+ var toolbar = new Element('div', { 'class': 'editor_toolbar' });
1967
+ this.editor.insert({before: toolbar});
1968
+ return toolbar;
1969
+ }
1970
+
1971
+ function addButtonSet(set) {
1972
+ var toolbar = this;
1973
+ $A(set).each(function(button){
1974
+ toolbar.addButton(button);
1975
+ });
1976
+ }
1977
+
1978
+ function addButton(options, handler) {
1979
+ options = $H(options);
1980
+
1981
+ if (!options.get('name'))
1982
+ options.set('name', options.get('label').toLowerCase());
1983
+ var name = options.get('name');
1984
+
1985
+ var button = this.createButtonElement(this.element, options);
1986
+
1987
+ var handler = this.buttonHandler(name, options);
1988
+ this.observeButtonClick(button, handler);
1989
+
1990
+ var handler = this.buttonStateHandler(name, options);
1991
+ this.observeStateChanges(button, name, handler)
1992
+ }
1993
+
1994
+ function createButtonElement(toolbar, options) {
1995
+ var button = new Element('a', {
1996
+ 'class': 'button', 'href': '#'
1997
+ });
1998
+ button.update('<span>' + options.get('label') + '</span>');
1999
+ button.addClassName(options.get('name'));
2000
+
2001
+ toolbar.appendChild(button);
2002
+
2003
+ return button;
2004
+ }
2005
+
2006
+ function buttonHandler(name, options) {
2007
+ if (options.handler)
2008
+ return options.handler;
2009
+ else if (options.get('handler'))
2010
+ return options.get('handler');
2011
+ else
2012
+ return function(editor) { editor.execCommand(name); };
2013
+ }
2014
+
2015
+ function observeButtonClick(element, handler) {
2016
+ var toolbar = this;
2017
+ element.observe('click', function(event) {
2018
+ handler(toolbar.editor);
2019
+ toolbar.editor.fire("wysihat:change");
2020
+ toolbar.editor.fire("wysihat:cursormove");
2021
+ Event.stop(event);
2022
+ });
2023
+ }
2024
+
2025
+ function buttonStateHandler(name, options) {
2026
+ if (options.query)
2027
+ return options.query;
2028
+ else if (options.get('query'))
2029
+ return options.get('query');
2030
+ else
2031
+ return function(editor) { return editor.queryCommandState(name); };
2032
+ }
2033
+
2034
+ function observeStateChanges(element, name, handler) {
2035
+ var toolbar = this;
2036
+ var previousState = false;
2037
+ toolbar.editor.observe("wysihat:cursormove", function(event) {
2038
+ var state = handler(toolbar.editor);
2039
+ if (state != previousState) {
2040
+ previousState = state;
2041
+ toolbar.updateButtonState(element, name, state);
2042
+ }
2043
+ });
2044
+ }
2045
+
2046
+ function updateButtonState(element, name, state) {
2047
+ if (state)
2048
+ element.addClassName('selected');
2049
+ else
2050
+ element.removeClassName('selected');
2051
+ }
2052
+
2053
+ return {
2054
+ initialize: initialize,
2055
+ createToolbarElement: createToolbarElement,
2056
+ addButtonSet: addButtonSet,
2057
+ addButton: addButton,
2058
+ createButtonElement: createButtonElement,
2059
+ buttonHandler: buttonHandler,
2060
+ observeButtonClick: observeButtonClick,
2061
+ buttonStateHandler: buttonStateHandler,
2062
+ observeStateChanges: observeStateChanges,
2063
+ updateButtonState: updateButtonState
2064
+ };
2065
+ })());
2066
+
2067
+ WysiHat.Toolbar.ButtonSets = {};
2068
+
2069
+ WysiHat.Toolbar.ButtonSets.Basic = $A([
2070
+ { label: "Bold" },
2071
+ { label: "Underline" },
2072
+ { label: "Italic" }
2073
+ ]);