bcalloway-wysihat-engine 0.1.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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
+ ]);