right-rails 1.2.0 → 1.2.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,2685 @@
1
+ /**
2
+ * RightJS-UI RTE v2.2.0
3
+ * http://rightjs.org/ui/rte
4
+ *
5
+ * Copyright (C) 2010-2011 Nikolay Nemshilov
6
+ */
7
+ var Rte = RightJS.Rte = (function(RightJS, document, window) {
8
+ /**
9
+ * This module defines the basic widgets constructor
10
+ * it creates an abstract proxy with the common functionality
11
+ * which then we reuse and override in the actual widgets
12
+ *
13
+ * Copyright (C) 2010-2011 Nikolay Nemshilov
14
+ */
15
+
16
+ /**
17
+ * The widget units constructor
18
+ *
19
+ * @param String tag-name or Object methods
20
+ * @param Object methods
21
+ * @return Widget wrapper
22
+ */
23
+ function Widget(tag_name, methods) {
24
+ if (!methods) {
25
+ methods = tag_name;
26
+ tag_name = 'DIV';
27
+ }
28
+
29
+ /**
30
+ * An Abstract Widget Unit
31
+ *
32
+ * Copyright (C) 2010 Nikolay Nemshilov
33
+ */
34
+ var AbstractWidget = new RightJS.Class(RightJS.Element.Wrappers[tag_name] || RightJS.Element, {
35
+ /**
36
+ * The common constructor
37
+ *
38
+ * @param Object options
39
+ * @param String optional tag name
40
+ * @return void
41
+ */
42
+ initialize: function(key, options) {
43
+ this.key = key;
44
+ var args = [{'class': 'rui-' + key}];
45
+
46
+ // those two have different constructors
47
+ if (!(this instanceof RightJS.Input || this instanceof RightJS.Form)) {
48
+ args.unshift(tag_name);
49
+ }
50
+ this.$super.apply(this, args);
51
+
52
+ if (RightJS.isString(options)) {
53
+ options = RightJS.$(options);
54
+ }
55
+
56
+ // if the options is another element then
57
+ // try to dynamically rewrap it with our widget
58
+ if (options instanceof RightJS.Element) {
59
+ this._ = options._;
60
+ if ('$listeners' in options) {
61
+ options.$listeners = options.$listeners;
62
+ }
63
+ options = {};
64
+ }
65
+ this.setOptions(options, this);
66
+
67
+ return (RightJS.Wrapper.Cache[RightJS.$uid(this._)] = this);
68
+ },
69
+
70
+ // protected
71
+
72
+ /**
73
+ * Catches the options
74
+ *
75
+ * @param Object user-options
76
+ * @param Element element with contextual options
77
+ * @return void
78
+ */
79
+ setOptions: function(options, element) {
80
+ if (element) {
81
+ options = RightJS.Object.merge(options, new Function("return "+(
82
+ element.get('data-'+ this.key) || '{}'
83
+ ))());
84
+ }
85
+
86
+ if (options) {
87
+ RightJS.Options.setOptions.call(this, RightJS.Object.merge(this.options, options));
88
+ }
89
+
90
+ return this;
91
+ }
92
+ });
93
+
94
+ /**
95
+ * Creating the actual widget class
96
+ *
97
+ */
98
+ var Klass = new RightJS.Class(AbstractWidget, methods);
99
+
100
+ // creating the widget related shortcuts
101
+ RightJS.Observer.createShortcuts(Klass.prototype, Klass.EVENTS || RightJS([]));
102
+
103
+ return Klass;
104
+ }
105
+
106
+
107
+ /**
108
+ * RTE's initialization script
109
+ *
110
+ * Copyright (C) 2010-2011 Nikolay Nemshilov
111
+ */
112
+
113
+ var R = RightJS,
114
+ $ = RightJS.$,
115
+ $$ = RightJS.$$,
116
+ $w = RightJS.$w,
117
+ $E = RightJS.$E,
118
+ $A = RightJS.$A,
119
+ isArray = RightJS.isArray,
120
+ RegExp = RightJS.RegExp,
121
+ Class = RightJS.Class,
122
+ Element = RightJS.Element,
123
+ Input = RightJS.Input;
124
+
125
+
126
+
127
+
128
+ /**
129
+ * The Right Text Editor
130
+ *
131
+ * Copyright (C) 2010-2011 Nikolay Nemshilov
132
+ */
133
+ var Rte = new Widget({
134
+
135
+ extend: {
136
+ version: '2.2.0',
137
+
138
+ EVENTS: $w('change focus blur'),
139
+
140
+ // checking if the 'contentEditable' feature is supported at all
141
+ supported: 'contentEditable' in document.createElement('div'),
142
+
143
+ Options: {
144
+ toolbar: 'small', // toolbar, the name or an array of your own
145
+
146
+ autoresize: true, // automatically resize the editor's height to fit the text
147
+
148
+ showToolbar: true, // show the toolbar
149
+ showStatus: true, // show the status bar
150
+
151
+ videoSize: '425x344', // flash-video blocks default size
152
+
153
+ cssRule: 'textarea[data-rte]'
154
+ },
155
+
156
+ // predefined toolbars set
157
+ Toolbars: {
158
+ small: ['Bold Italic Underline Strike Ttext|Cut Copy Paste|Header Code Quote|Link Image Video|Source'],
159
+ basic: [
160
+ 'Save Clear|Cut Copy Paste|Bold Italic Underline Strike Ttext|Left Center Right Justify',
161
+ 'Undo Redo|Header Code Quote|Link Image Video|Dotlist Numlist|Indent Outdent|Source'
162
+ ],
163
+ extra: [
164
+ 'Save Clear|Cut Copy Paste|Bold Italic Underline Strike Ttext|Left Center Right Justify',
165
+ 'Undo Redo|Header Code Quote|Link Image Video|Subscript Superscript|Dotlist Numlist|Indent Outdent',
166
+ 'Format|Fontname Fontsize|Forecolor Backcolor|Source'
167
+ ]
168
+ },
169
+
170
+ Tools: {}, // the index of available tools will be here
171
+
172
+ // the keyboard bindings
173
+ Shortcuts: {
174
+ Bold: 'b',
175
+ Italic: 'i',
176
+ Underline: 'u',
177
+ // Ttext: 't',
178
+ Header: 'h',
179
+ Link: 'l',
180
+ Cut: 'x',
181
+ Copy: 'c',
182
+ Paste: 'v',
183
+ Undo: 'z',
184
+ Redo: 'shift+z',
185
+ Source: 'e',
186
+ // Quote: 'q',
187
+ Code: 'p',
188
+ Save: 's'
189
+ },
190
+
191
+ // tags used by default with formatting tools
192
+ Tags: {
193
+ Bold: 'b',
194
+ Italic: 'i',
195
+ Underline: 'u',
196
+ Strike: 's',
197
+ Ttext: 'tt',
198
+ Code: 'pre',
199
+ Quote: 'blockquote',
200
+ Header: 'h2'
201
+ },
202
+
203
+ // the formatting options, you can use simply tag names
204
+ // or you can also specify tag + class like 'div.blue'
205
+ Formats: {
206
+ 'h1': 'Header 1',
207
+ 'h2': 'Header 2',
208
+ 'h3': 'Header 3',
209
+ 'h4': 'Header 4',
210
+ 'p': 'Paragraph',
211
+ 'pre': 'Preformatted',
212
+ 'blockquote': 'Blockquote',
213
+ 'tt': 'Typetext',
214
+ 'address': 'Address'
215
+ },
216
+
217
+ // the font-name options
218
+ FontNames: {
219
+ 'Andale Mono': 'andale mono,times',
220
+ 'Arial': 'arial,helvetica,sans-serif',
221
+ 'Arial Black': 'arial black,avant garde',
222
+ 'Book Antiqua': 'book antiqua,palatino',
223
+ 'Comic Sans MS': 'comic sans ms,sans-serif',
224
+ 'Courier New': 'courier new,courier',
225
+ 'Georgia': 'georgia,palatino',
226
+ 'Helvetica': 'helvetica',
227
+ 'Impact': 'impact,chicago',
228
+ 'Symbol': 'symbol',
229
+ 'Tahoma': 'tahoma,arial,helvetica,sans-serif',
230
+ 'Terminal': 'terminal,monaco',
231
+ 'Times New Roman': 'times new roman,times',
232
+ 'Trebuchet MS': 'trebuchet ms,geneva',
233
+ 'Verdana': 'verdana,geneva',
234
+ 'Webdings': 'webdings',
235
+ 'Wingdings': 'wingdings,zapf dingbats'
236
+ },
237
+
238
+ // the font-size options
239
+ FontSizes: '6pt 7pt 8pt 9pt 10pt 11pt 12pt 14pt 18pt 24pt 36pt',
240
+
241
+ Videos: [
242
+ // supported swf video resources
243
+ [/(http:\/\/.*?youtube\.[a-z]+)\/watch\?v=([^&]+)/, '$1/v/$2'],
244
+ [/(http:\/\/video.google.com)\/videoplay\?docid=([^&]+)/, '$1/googleplayer.swf?docId=$2'],
245
+ [/(http:\/\/vimeo\.[a-z]+)\/([0-9]+).*?/, '$1/moogaloop.swf?clip_id=$2']
246
+ ],
247
+
248
+ i18n: {
249
+ Clear: 'Clear',
250
+ Save: 'Save',
251
+ Source: 'Source',
252
+ Bold: 'Bold',
253
+ Italic: 'Italic',
254
+ Underline: 'Underline',
255
+ Strike: 'Strike through',
256
+ Ttext: 'Typetext',
257
+ Header: 'Header',
258
+ Cut: 'Cut',
259
+ Copy: 'Copy',
260
+ Paste: 'Paste',
261
+ Left: 'Left',
262
+ Center: 'Center',
263
+ Right: 'Right',
264
+ Justify: 'Justify',
265
+ Undo: 'Undo',
266
+ Redo: 'Redo',
267
+ Code: 'Code block',
268
+ Quote: 'Block quote',
269
+ Link: 'Add link',
270
+ Image: 'Insert image',
271
+ Video: 'Insert video',
272
+ Dotlist: 'List with dots',
273
+ Numlist: 'List with numbers',
274
+ Indent: 'Indent',
275
+ Outdent: 'Outdent',
276
+ Forecolor: 'Text color',
277
+ Backcolor: 'Background color',
278
+ Select: 'Select',
279
+ Remove: 'Remove',
280
+ Format: 'Format',
281
+ Fontname: 'Font name',
282
+ Fontsize: 'Size',
283
+ Subscript: 'Subscript',
284
+ Superscript: 'Superscript',
285
+ UrlAddress: 'URL Address'
286
+ },
287
+
288
+ current: null
289
+ },
290
+
291
+ /**
292
+ * Basic constructor
293
+ *
294
+ * @param Input textarea reference
295
+ * @param Object additional options
296
+ * @return void
297
+ */
298
+ initialize: function(textarea, options) {
299
+ this
300
+ .$super('rte', {})
301
+ .setOptions(options, textarea)
302
+ .append(
303
+ this.toolbar = new Rte.Toolbar(this),
304
+ this.editor = new Rte.Editor(this),
305
+ this.status = new Rte.Status(this)
306
+ );
307
+
308
+ if (!this.options.showToolbar) {
309
+ this.toolbar.hide();
310
+ }
311
+
312
+ if (!this.options.showStatus) {
313
+ this.status.hide();
314
+ }
315
+
316
+ if (textarea) {
317
+ this.assignTo(textarea);
318
+ }
319
+
320
+ this.undoer = new Rte.Undoer(this);
321
+ this.selection = new Rte.Selection(this);
322
+
323
+ // updating the initial state
324
+ this.selection.exec('styleWithCss', false);
325
+ this.status.update();
326
+ },
327
+
328
+ /**
329
+ * Sets the value
330
+ *
331
+ * @param String value
332
+ * @return Rte this
333
+ */
334
+ setValue: function(value) {
335
+ if (this.textarea) {
336
+ this.textarea.value(value);
337
+ }
338
+ this.editor.update(value);
339
+ return this;
340
+ },
341
+
342
+ /**
343
+ * Returns the current value
344
+ *
345
+ * @return String current value
346
+ */
347
+ getValue: function() {
348
+ return this.editor._.innerHTML;
349
+ },
350
+
351
+ /**
352
+ * Bidirectional method to set/get the value
353
+ *
354
+ * @param String value
355
+ * @return Rte this or String value
356
+ */
357
+ value: function(value) {
358
+ return this[value === undefined ? 'getValue' : 'setValue'](value);
359
+ },
360
+
361
+ /**
362
+ * Disables the editor
363
+ *
364
+ * @return Rte this
365
+ */
366
+ disable: function() {
367
+ this.disabled = true;
368
+ return this.addClass('rui-rte-disabled');
369
+ },
370
+
371
+ /**
372
+ * Enables the editor
373
+ *
374
+ * @return Rte this
375
+ */
376
+ enable: function() {
377
+ this.disabled = false;
378
+ return this.removeClass('rui-rte-disabled');
379
+ },
380
+
381
+ /**
382
+ * Puts the focus into the editor
383
+ *
384
+ * @return Rte this
385
+ */
386
+ focus: function() {
387
+ if (Rte.current !== this) {
388
+ Rte.current = this;
389
+ this.editor.focus();
390
+ }
391
+
392
+ return this;
393
+ },
394
+
395
+ /**
396
+ * Looses focus from the editor
397
+ *
398
+ * @return Rte this
399
+ */
400
+ blur: function() {
401
+ Rte.current = null;
402
+
403
+ this.editor.blur();
404
+
405
+ return this;
406
+ },
407
+
408
+ /**
409
+ * Assigns this Rte to work with this textarea
410
+ *
411
+ * @param mixed textarea reference
412
+ * @return Rte this
413
+ */
414
+ assignTo: function(element) {
415
+ var textarea = $(element),
416
+ size = textarea.size();
417
+
418
+ // displaying self only if the 'contentEditable' feature is supported
419
+ // otherwise keeping original textarea where it is
420
+ if (Rte.supported) {
421
+ this.insertTo(textarea.setStyle(
422
+ 'position:absolute;left:-9999em;'
423
+ ), 'before');
424
+
425
+ this.editor.resize(size);
426
+ this.setWidth(size.x);
427
+
428
+ if (this.options.autoresize) {
429
+ this.editor.setStyle({
430
+ minHeight: size.y + 'px',
431
+ height: 'auto'
432
+ });
433
+ }
434
+ } else {
435
+ textarea.setStyle('visibility:visible');
436
+ }
437
+
438
+ this.setValue(textarea.value());
439
+ this.onChange(function() {
440
+ textarea._.value = this.editor._.innerHTML;
441
+ });
442
+
443
+ this.textarea = textarea;
444
+
445
+ return this;
446
+ }
447
+
448
+ });
449
+
450
+ /**
451
+ * Rte's toolbar unit
452
+ *
453
+ * Copyright (C) 2010 Nikolay Nemshilov
454
+ */
455
+ Rte.Toolbar = new Class(Element, {
456
+
457
+ initialize: function(rte) {
458
+ this.$super('div', {'class': 'rui-rte-toolbar'});
459
+
460
+ this.rte = rte;
461
+ rte.tools = {};
462
+
463
+ var options = rte.options, toolbar = options.toolbar;
464
+
465
+ R(Rte.Toolbars[toolbar] || (isArray(toolbar) ? toolbar : [toolbar])).each(function(line_s) {
466
+ var line = $E('div', {'class': 'line'}).insertTo(this);
467
+
468
+ R(line_s.split('|')).each(function(bar_s) {
469
+ if (!R(bar_s).blank()) {
470
+ var bar = $E('div', {'class': 'bar'}).insertTo(line);
471
+
472
+ R(bar_s.split(' ')).each(function(tool) {
473
+ tool = R(tool).capitalize();
474
+ bar.insert(new Rte.Tools[tool](rte));
475
+ });
476
+ }
477
+ });
478
+ }, this);
479
+
480
+ // adding hidden undo/redo tools if they are not on the toolbar
481
+ // so that the undoer kicked in on the keybindings
482
+ rte.tools.Undo || new Rte.Tools.Undo(rte);
483
+ rte.tools.Redo || new Rte.Tools.Redo(rte);
484
+ },
485
+
486
+ /**
487
+ * Finds a tool for the keyboard event
488
+ *
489
+ * @param {Event} event
490
+ * @return {Rte.Tool} wired tool or false
491
+ */
492
+ shortcut: function(event) {
493
+ var raw = event._, key, tool;
494
+
495
+ for (key in this.rte.tools) {
496
+ tool = this.rte.tools[key];
497
+
498
+ if (tool.shortcut === raw.keyCode && tool.shiftKey === raw.shiftKey) {
499
+ return tool;
500
+ }
501
+ }
502
+
503
+ return null;
504
+ }
505
+
506
+ });
507
+
508
+ /**
509
+ * The actual Editor unit for the Rte
510
+ *
511
+ * Copyright (C) 2010 Nikolay Nemshilov
512
+ */
513
+ Rte.Editor = new Class(Element, {
514
+
515
+ /**
516
+ * Basic constructor
517
+ *
518
+ * @param Rte rte
519
+ * @return void
520
+ */
521
+ initialize: function(rte) {
522
+ // IE won't allow us to set 'contenteditable' progarmatically
523
+ // so we put it as a textual content and then find it manually
524
+ this.$super(rte
525
+ .append('<div contenteditable="true" class="rui-rte-editor"></div>')
526
+ .first('div.rui-rte-editor')._
527
+ );
528
+
529
+ this.rte = rte;
530
+
531
+ this.on({
532
+ focus: this._focus,
533
+ blur: this._blur,
534
+ mouseup: this._mouseup,
535
+ keypress: this._keypress,
536
+ keydown: this._keydown,
537
+ keyup: this._keyup
538
+ });
539
+ },
540
+
541
+ /**
542
+ * Updates the editor's content
543
+ *
544
+ * @param String text
545
+ * @return Rte.Editor this
546
+ */
547
+ update: function(text) {
548
+ this.$super(text);
549
+ return this;
550
+ },
551
+
552
+ /**
553
+ * puts focus on the editing area
554
+ *
555
+ * @return Rte.Editor this
556
+ */
557
+ focus: function() {
558
+ this._.focus();
559
+ return this;
560
+ },
561
+
562
+ /**
563
+ * removes focus out of the editing area
564
+ *
565
+ * @return Rte.Editor this
566
+ */
567
+ blur: function() {
568
+ this._.blur();
569
+ return this;
570
+ },
571
+
572
+ /**
573
+ * Removes the element from the editor, placing all its content
574
+ * in its place
575
+ *
576
+ * @param raw dom element
577
+ * @return void
578
+ */
579
+ removeElement: function(element) {
580
+ if (element !== null) {
581
+ var parent = element.parentNode;
582
+ while (element.firstChild) {
583
+ parent.insertBefore(element.firstChild, element);
584
+ }
585
+ parent.removeChild(element);
586
+ }
587
+ },
588
+
589
+ // protected
590
+
591
+ _focus: function() {
592
+ this.rte.selection.restore();
593
+ this.rte.status.update();
594
+ this.rte.focused = true;
595
+ },
596
+
597
+ _blur: function() {
598
+ this.rte.focused = false;
599
+ this.rte.status.update();
600
+ },
601
+
602
+ _mouseup: function() {
603
+ this._focus();
604
+ },
605
+
606
+ _keypress: function(event) {
607
+ if (this.__stopped) {
608
+ event.stop();
609
+ }
610
+ },
611
+
612
+ _keydown: function(event) {
613
+ var raw = event._, stopped = false, tool;
614
+
615
+ if (raw.metaKey || raw.ctrlKey) {
616
+ if ((tool = this.rte.toolbar.shortcut(event))) {
617
+ tool.call(event);
618
+ }
619
+
620
+ stopped = event.stopped;
621
+ }
622
+
623
+ // an internal marker to lock the 'keypress' event later on
624
+ this.__stopped = stopped;
625
+ },
626
+
627
+ _keyup: function(event) {
628
+ switch (event.keyCode) {
629
+ case 37: // arrow
630
+ case 38: // arrow
631
+ case 39: // arrow
632
+ case 40: // arrow
633
+ this.rte.status.update();
634
+ break;
635
+
636
+ default:
637
+ // watching the typing pauses to fire 'change' events
638
+ var rte = this.rte, editor = this._;
639
+
640
+ if (this._timer !== false) { window.clearTimeout(this._timer); }
641
+
642
+ this._timer = window.setTimeout(function() {
643
+ if (rte.__old_value !== editor.innerHTML) {
644
+ rte.__old_value = editor.innerHTML;
645
+ rte.fire('change');
646
+ }
647
+ }, this._delay);
648
+ }
649
+ },
650
+
651
+ _timer: false,
652
+ _delay: 400
653
+
654
+ });
655
+
656
+ /**
657
+ * The Rte's status bar block
658
+ *
659
+ * Copyright (C) 2010-2011 Nikolay Nemshilov
660
+ */
661
+ Rte.Status = new Class(Element, {
662
+
663
+ /**
664
+ * Basic constructor
665
+ *
666
+ * @param Rte
667
+ * @return void
668
+ */
669
+ initialize: function(rte) {
670
+ this.$super('div', {'class': 'rui-rte-status'});
671
+ this.rte = rte;
672
+ this.nodes = [];
673
+ this.tags = [];
674
+
675
+ this.onMousedown(this._mousedown);
676
+ },
677
+
678
+ /**
679
+ * Updates the current status
680
+ *
681
+ * @return Rte.Status this
682
+ */
683
+ update: function() {
684
+ this._findNodes();
685
+ this._checkTools();
686
+
687
+ return this.$super(this.nodes.map(function(node, index) {
688
+ var name = node.tagName.toLowerCase();
689
+
690
+ if (node.id) {
691
+ name += "#"+ node.id;
692
+ }
693
+
694
+ if (node.className) {
695
+ name += "."+ node.className;
696
+ }
697
+
698
+ return '<a href="" data-index="'+ index +
699
+ '" onclick="return false;" title="'+
700
+ Rte.i18n.Select +
701
+ '">'+ name +'</a>';
702
+
703
+ }).join(' &rsaquo; '));
704
+ },
705
+
706
+ /**
707
+ * Finds an element in the current status stack
708
+ *
709
+ * @param String tag name
710
+ * @param Object optional attributes
711
+ * @return raw element or null if nothing found
712
+ */
713
+ findElement: function(tag, attributes) {
714
+ if (tag) {
715
+ for (var i = this.nodes.length - 1, key, match; i > -1; i--) {
716
+ if (this.nodes[i].tagName === tag) {
717
+ match = true;
718
+
719
+ for (key in attributes) {
720
+ if (attributes[key] instanceof RegExp) {
721
+ match &= attributes[key].test(this.nodes[i].getAttribute(key));
722
+ } else {
723
+ match &= this.nodes[i].getAttribute(key) == attributes[key];
724
+ }
725
+ }
726
+
727
+ if (match) {
728
+ return this.nodes[i];
729
+ }
730
+ }
731
+ }
732
+ }
733
+
734
+ return null;
735
+ },
736
+
737
+ // protected
738
+
739
+ // runs the tools check
740
+ _checkTools: function() {
741
+ var tools = this.rte.tools, key;
742
+ for (key in tools) {
743
+ tools[key].check();
744
+ }
745
+ },
746
+
747
+ // finds the nodes from the current selection to the bottom
748
+ _findNodes: function() {
749
+ var node = this.rte.selection.element(),
750
+ editor = this.rte.editor._,
751
+ rte = this.rte._,
752
+ nodes = [],
753
+ tags = [];
754
+
755
+ this.nodes = [];
756
+ this.tags = [];
757
+
758
+ while (node && node !== rte) {
759
+ if (node.tagName) { // skipping the textual nodes
760
+ nodes.unshift(node);
761
+ tags.unshift(node.tagName);
762
+ }
763
+
764
+ node = node.parentNode;
765
+
766
+ if (node === editor) {
767
+ this.nodes = nodes;
768
+ this.tags = tags;
769
+ break;
770
+ }
771
+ }
772
+ },
773
+
774
+ // catches the mousedown on the links
775
+ _mousedown: function(event) {
776
+ var link = event.target;
777
+
778
+ if (link._.tagName === 'A') {
779
+ event.stop();
780
+ var index = link.get('data-index').toInt(),
781
+ node = this.nodes[index];
782
+
783
+ this.rte.selection.wrap(node);
784
+ }
785
+ }
786
+
787
+ });
788
+
789
+ /**
790
+ * Custom undo/redo manager
791
+ *
792
+ * The basic trouble is that the native undo manager
793
+ * dosn't support manual changes in the editable block
794
+ * plus it lacks an ability to save some things under IE
795
+ *
796
+ * So we manage our undo/redo states manually by whatching
797
+ * the 'change' event in the RTE instance
798
+ *
799
+ * Copyright (C) 2010-2011 Nikolay Nemshilov
800
+ */
801
+ Rte.Undoer = new Class({
802
+
803
+ /**
804
+ * Basic constructor
805
+ *
806
+ * @param Rte rte
807
+ * @return void
808
+ */
809
+ initialize: function(rte) {
810
+ this.rte = rte;
811
+
812
+ function save() { this.undoer.save(); }
813
+ this.rte.on({ focus: save, change: save });
814
+
815
+ this.clear();
816
+ },
817
+
818
+ /**
819
+ * Clears up the undo history
820
+ *
821
+ * @return void
822
+ */
823
+ clear: function() {
824
+ this.stash = [];
825
+ this.index = -1;
826
+ },
827
+
828
+ /**
829
+ * Checks if there are undo steps
830
+ *
831
+ * @return boolean check result
832
+ */
833
+ hasUndo: function() {
834
+ return this.stash.length > 0 && this.index > 0;
835
+ },
836
+
837
+ /**
838
+ * Checks if there are redo steps
839
+ *
840
+ * @return boolean check result
841
+ */
842
+ hasRedo: function() {
843
+ return (this.stash.length - this.index) > 1;
844
+ },
845
+
846
+ /**
847
+ * Moves the history one step back
848
+ *
849
+ * @return void
850
+ */
851
+ undo: function() {
852
+ if (this.hasUndo()) {
853
+ this.set(-- this.index);
854
+ }
855
+ },
856
+
857
+ /**
858
+ * Moves the histor one step forward
859
+ *
860
+ * @return void
861
+ */
862
+ redo: function() {
863
+ if (this.hasRedo()) {
864
+ this.set(++ this.index);
865
+ }
866
+ },
867
+
868
+ /**
869
+ * Sets the history to the given step
870
+ *
871
+ * @param Integer step index
872
+ */
873
+ set: function(index) {
874
+ if (this.stash[this.index]) {
875
+ this.rte.editor.update(this.stash[this.index]);
876
+ this.rte.selection.restore();
877
+ }
878
+ },
879
+
880
+ /**
881
+ * Saves the current state of the editor
882
+ *
883
+ * @param Event the RTE's 'change' event with the 'tool' reference
884
+ * @return void
885
+ */
886
+ save: function(event) {
887
+ var tool = event && event.tool,
888
+ tools = this.rte.tools,
889
+ html, html1, html2;
890
+
891
+ if (!tool || (tool !== tools.Undo && tool !== tools.Redo)) {
892
+ this.rte.selection.store();
893
+
894
+ html = this.rte.editor._.innerHTML;
895
+
896
+ // stripping off the selection markers
897
+ html1 = html
898
+ .replace(SELECTION_START_RE, '')
899
+ .replace(SELECTION_END_RE, '');
900
+
901
+ html2 = (this.stash[this.index]||'')
902
+ .replace(SELECTION_START_RE, '')
903
+ .replace(SELECTION_END_RE, '');
904
+
905
+ if (html1 !== html2) {
906
+ // cutting loose possible redo steps
907
+ this.stash.length = this.index + 1;
908
+ this.stash.push(html);
909
+ this.index = this.stash.length - 1;
910
+
911
+ tools.Undo.check();
912
+ tools.Redo.check();
913
+ }
914
+
915
+ this.rte.selection.restore();
916
+ }
917
+ }
918
+
919
+ });
920
+
921
+ /**
922
+ * This class handles the selection ranges
923
+ *
924
+ * Copyright (C) 2010-2011 Nikolay Nemshilov
925
+ */
926
+ Rte.Selection = new Class({
927
+
928
+ /**
929
+ * Basic constructor
930
+ *
931
+ * @param Rte rte
932
+ * @return void
933
+ */
934
+ initialize: function(rte) {
935
+ this.rte = rte;
936
+ },
937
+
938
+ /**
939
+ * gets/sets the current range object
940
+ *
941
+ * @param {Range} to set
942
+ * @return TextRange range
943
+ */
944
+ range: function(range) {
945
+ var selection = window.getSelection && window.getSelection();
946
+
947
+ if (range) {
948
+ if (selection) { // w3c
949
+ selection.removeAllRanges();
950
+ selection.addRange(range);
951
+ } else if (range._) { // IE
952
+ range._.select();
953
+ }
954
+
955
+ } else {
956
+ try {
957
+ range = selection.getRangeAt(0); // FF, Opera, IE9
958
+ } catch (e) {
959
+ try {
960
+ range = document.createRange(); // WebKit
961
+ } catch (ee) {
962
+ range = new IERangeEmulator(); // Old IE
963
+ }
964
+ }
965
+
966
+ return range;
967
+ }
968
+ },
969
+
970
+ /**
971
+ * Returns the dom-node that's currently in focus
972
+ *
973
+ * @return raw dom-element
974
+ */
975
+ element: function() {
976
+ var range = this.range(), node = range.commonAncestorContainer;
977
+
978
+ // if there is a selection, trying those
979
+ if (!range.collapsed) {
980
+ if (
981
+ range.startContainer === node &&
982
+ range.startOffset - range.endOffset < 2 &&
983
+ range.startContainer.hasChildNodes()
984
+ ) {
985
+ node = range.startContainer.childNodes[range.startOffset];
986
+ }
987
+ }
988
+
989
+ node = node && node.nodeType === 3 ? node.parentNode : node;
990
+
991
+ return node || null;
992
+ },
993
+
994
+ /**
995
+ * Puts current selection over the given raw dom-node
996
+ *
997
+ * @param raw dom-node
998
+ * @return void
999
+ */
1000
+ wrap: function(node) {
1001
+ var range = this.range();
1002
+ range.selectNode(node);
1003
+ this.range(range);
1004
+ },
1005
+
1006
+ /**
1007
+ * Returns the selection text
1008
+ *
1009
+ * @return String selection text
1010
+ */
1011
+ text: function() {
1012
+ return this.range().toString();
1013
+ },
1014
+
1015
+ /**
1016
+ * Cheks if the selection is empty
1017
+ *
1018
+ * @return boolean check result
1019
+ */
1020
+ empty: function() {
1021
+ return this.text() === '';
1022
+ },
1023
+
1024
+ /**
1025
+ * Returns the HTML content of the selection
1026
+ *
1027
+ * @return String html content
1028
+ */
1029
+ html: function() {
1030
+ var range = this.range(), tmp, fragment;
1031
+
1032
+ if (range._) {
1033
+ return range._.htmlText;
1034
+ } else {
1035
+ tmp = document.createElement('div');
1036
+ fragment = range.cloneContents();
1037
+
1038
+ while (fragment.firstChild) {
1039
+ tmp.appendChild(fragment.firstChild);
1040
+ }
1041
+
1042
+ return tmp.innerHTML;
1043
+ }
1044
+ },
1045
+
1046
+ /**
1047
+ * executes a command on this editing area
1048
+ *
1049
+ * @param String command name
1050
+ * @param mixed command value
1051
+ * @return void
1052
+ */
1053
+ exec: function(command, value) {
1054
+ try {
1055
+ // it throws errors in some cases in the non-design mode
1056
+ document.execCommand(command, false, value);
1057
+ } catch(e) {
1058
+ // emulating insert html under IE
1059
+ if (command === 'inserthtml') {
1060
+ this.range()._.pasteHTML(value);
1061
+ }
1062
+ }
1063
+ },
1064
+
1065
+ /**
1066
+ * Saves the selection by inserting special SPAN elements
1067
+ * in places where the selection starts and ends so that
1068
+ * it could be restored later, even if the editor innerHTML
1069
+ * property was manipulated directly
1070
+ *
1071
+ * @return {Rte.Selection} this
1072
+ */
1073
+ store: function() {
1074
+ var range = this.range();
1075
+
1076
+ // reclonning the data so it wasn't lost on changes
1077
+ range = {
1078
+ startContainer: range.startContainer,
1079
+ startOffset: range.startOffset,
1080
+ endContainer: range.endContainer,
1081
+ endOffset: range.endOffset,
1082
+ collapsed: range.collapsed
1083
+ };
1084
+
1085
+ /**
1086
+ * Places the selection markers into the editor's structure
1087
+ *
1088
+ * @param String name 'end' or 'start'
1089
+ * @return void
1090
+ */
1091
+ function place_marker(name) {
1092
+ var node = range[name + 'Container'],
1093
+ offset = range[name + 'Offset'],
1094
+ marker = document.createElement('span'),
1095
+ parent = node.parentNode,
1096
+ text = node.nodeValue,
1097
+ ending = document.createTextNode((''+text).substr(offset));
1098
+
1099
+ marker.setAttribute('rrte-'+name, '1');
1100
+
1101
+ function insert_after(content, anchor) {
1102
+ if (anchor.nextSibling) {
1103
+ anchor.parentNode.insertBefore(content, anchor.nextSibling);
1104
+ } else {
1105
+ anchor.parentNode.appendChild(content);
1106
+ }
1107
+ }
1108
+
1109
+ if (node.nodeType === 3) { // text-node
1110
+ if (offset === 0) {
1111
+ parent.insertBefore(marker,
1112
+ // in case both of the markers are at the beginning of
1113
+ // the same node, the 'end' marker will be already there
1114
+ // and we will need to insert the 'start' one before it.
1115
+ name === 'start' && range.collapsed ? node.previousSibling : node
1116
+ );
1117
+ } else if (offset === text.length) {
1118
+ insert_after(marker, node);
1119
+ } else { // splitting the text node in two
1120
+ node.nodeValue = text.substr(0, offset);
1121
+ insert_after(ending, node);
1122
+ parent.insertBefore(marker, ending);
1123
+ }
1124
+
1125
+ } else if (node.nodeType === 1) { // elements
1126
+ if (offset === 0) {
1127
+ if (node.firstChild) {
1128
+ node.insertBefore(marker, node.firstChild);
1129
+ } else if (node.hasChildNodes()) {
1130
+ node.appendChild(marker);
1131
+ }
1132
+ } else if (offset === node.childNodes.length) {
1133
+ node.appendChild(marker);
1134
+ } else {
1135
+ node.insertBefore(marker, node.childNodes[offset]);
1136
+ }
1137
+ }
1138
+ }
1139
+
1140
+ // NOTE: the 'end' should be before the 'start' in case both of them
1141
+ // are in the same node, so that the offest didn't shift after
1142
+ // we insert the marker nodes
1143
+ place_marker('end');
1144
+ place_marker('start');
1145
+ },
1146
+
1147
+ /**
1148
+ * Restores the selection by previously placed
1149
+ * special SPAN elements and removes them afterwards
1150
+ *
1151
+ * @return {Rte.Selection} this
1152
+ */
1153
+ restore: function() {
1154
+ var elements = $A(this.rte.editor._.getElementsByTagName('span')),
1155
+ i=0, method, parent, offset, range = this.range();
1156
+
1157
+ for (; i < elements.length; i++) {
1158
+ method = elements[i].getAttribute('rrte-start') ? 'setStart' :
1159
+ elements[i].getAttribute('rrte-end') ? 'setEnd' : false;
1160
+
1161
+ if (method) {
1162
+ parent = elements[i].parentNode;
1163
+
1164
+ if (range._) {
1165
+ range[method](elements[i]);
1166
+ } else {
1167
+ offset = IER_getIndex(elements[i]);
1168
+ range[method](parent, offset);
1169
+ }
1170
+
1171
+ parent.removeChild(elements[i]);
1172
+ }
1173
+ }
1174
+
1175
+ this.range(range);
1176
+ }
1177
+ });
1178
+
1179
+ var
1180
+
1181
+ SELECTION_START_MARKER = '<span rrte-start="1"></span>',
1182
+ SELECTION_END_MARKER = '<span rrte-end="1"></span>',
1183
+ SELECTION_START_RE = new RegExp(RegExp.escape(SELECTION_START_MARKER), 'i'),
1184
+ SELECTION_END_RE = new RegExp(RegExp.escape(SELECTION_END_MARKER), 'i');
1185
+
1186
+
1187
+ /**
1188
+ * W3C ranges API emulator for the old IE browsers
1189
+ *
1190
+ * Based on the `InternetExplorerRange` implementation
1191
+ * from the http://mozile.mozdev.org project
1192
+ * by James A. Overton <james@overton.ca>
1193
+ *
1194
+ * Originally licensed under MPL/GPL2/LGPL2 licenses
1195
+ *
1196
+ * Copyrgiht (C) 2011 Nikolay Nemshilov
1197
+ */
1198
+ var IERangeEmulator = new Class({
1199
+
1200
+ // standard w3c attributes
1201
+ collapsed: null,
1202
+ startContainer: null,
1203
+ startOffset: null,
1204
+ endContainer: null,
1205
+ endOffset: null,
1206
+ commonAncestorContainer: null,
1207
+
1208
+ /**
1209
+ * Basic constructor
1210
+ *
1211
+ * @return void
1212
+ */
1213
+ initialize: function() {
1214
+ this._ = document.selection.createRange();
1215
+
1216
+ if (document.selection.type === 'Control') {
1217
+ this.startContainer = this.endContainer = this.commonAncestorContainer = this._(0);
1218
+ this.startOffset = this.endOffset = 0;
1219
+ } else {
1220
+ //startPoint
1221
+ var range = this._.duplicate();
1222
+ range.collapse(true);
1223
+ range = IER_getPosition(range);
1224
+
1225
+ this.startContainer = range.node;
1226
+ this.startOffset = range.offset;
1227
+
1228
+ //endPoint
1229
+ range = this._.duplicate();
1230
+ range.collapse(false);
1231
+ range = IER_getPosition(range);
1232
+
1233
+ this.endContainer = range.node;
1234
+ this.endOffset = range.offset;
1235
+
1236
+ // the rest of the properties
1237
+ IER_commonAncestorContainer(this);
1238
+ }
1239
+
1240
+ IER_collapsed(this);
1241
+ },
1242
+
1243
+ /**
1244
+ * Sets the starting point for the range
1245
+ *
1246
+ * @param {Node} node
1247
+ * @param {Number} offset
1248
+ * @return void
1249
+ */
1250
+ setStart: function(node, offset) {
1251
+ var range = this._.duplicate();
1252
+
1253
+ range.moveToElementText(node);
1254
+ range.collapse(true);
1255
+
1256
+ this._.setEndPoint('StartToStart', range);
1257
+
1258
+ this.startContainer = node;
1259
+ this.startOffset = offset;
1260
+
1261
+ if (this.endContainer === null && this.endOffset === null) {
1262
+ this.endContainer = node;
1263
+ this.endOffset = offset;
1264
+ }
1265
+
1266
+ IER_commonAncestorContainer(this);
1267
+ IER_collapsed(this);
1268
+ },
1269
+
1270
+ /**
1271
+ * Setting the end point for the range
1272
+ *
1273
+ * @param {Node} node
1274
+ * @param {Number} offset
1275
+ * @return void
1276
+ */
1277
+ setEnd: function (node, offset) {
1278
+ var range = this._.duplicate();
1279
+
1280
+ range.moveToElementText(node);
1281
+ range.collapse(true);
1282
+
1283
+ this._.setEndPoint('EndToEnd', range);
1284
+
1285
+ this.endContainer = node;
1286
+ this.endOffset = offset;
1287
+
1288
+ if (this.startContainer === null && this.startOffset === null) {
1289
+ this.startContainer = node;
1290
+ this.startOffset = offset;
1291
+ }
1292
+
1293
+ IER_commonAncestorContainer(this);
1294
+ IER_collapsed(this);
1295
+ },
1296
+
1297
+ /**
1298
+ * Wrapps the range around the given node
1299
+ *
1300
+ * @param {Node} node
1301
+ * @return void
1302
+ */
1303
+ selectNode: function (node) {
1304
+ this._.moveToElementText(node);
1305
+ },
1306
+
1307
+ /**
1308
+ * Selection text emulation
1309
+ *
1310
+ * @return {String} text
1311
+ */
1312
+ toString: function() {
1313
+ return ''+ this._.text;
1314
+ }
1315
+ });
1316
+
1317
+
1318
+ //////////////////////////////////////////////////////////////////////////////
1319
+ // Some private methods for the IERangeEmaulator
1320
+ //////////////////////////////////////////////////////////////////////////////
1321
+
1322
+
1323
+ /**
1324
+ * Finds the standard node/offset pair out of the
1325
+ * IE range object
1326
+ *
1327
+ * @param {TextRange} ie text range object
1328
+ * @return {Object} 'node' and 'offset' pairs
1329
+ */
1330
+ function IER_getPosition(original_range) {
1331
+ var element = original_range.parentElement(),
1332
+ range, range_size, direction, node, node_size;
1333
+
1334
+ range = document.body.createTextRange();
1335
+ range.moveToElementText(element);
1336
+ range.setEndPoint("EndToStart", original_range);
1337
+
1338
+ range_size = range.text.length;
1339
+
1340
+ // Choose Direction
1341
+ if (range_size < element.innerText.length / 2) {
1342
+ direction = 1;
1343
+ node = element.firstChild;
1344
+ } else {
1345
+ direction = -1;
1346
+ node = element.lastChild;
1347
+ range.moveToElementText(element);
1348
+ range.setEndPoint("StartToStart", original_range);
1349
+ range_size = range.text.length;
1350
+ }
1351
+
1352
+ // Loop through child nodes
1353
+ while (node) {
1354
+ switch (node.nodeType) {
1355
+ case 3: // text-node
1356
+ node_size = node.data.length;
1357
+ if(node_size < range_size) {
1358
+ range_size -= node_size;
1359
+
1360
+ if (direction === 1) {
1361
+ range.moveStart("character", range_size);
1362
+ } else {
1363
+ range.moveEnd("character", -range_size);
1364
+ }
1365
+
1366
+ } else {
1367
+ return direction === 1 ?
1368
+ {node: node, offset: range_size} :
1369
+ {node: node, offset: node_size - range_size};
1370
+ }
1371
+ break;
1372
+
1373
+ case 1: // element-node
1374
+ node_size = node.innerText.length;
1375
+
1376
+ if (direction === 1) {
1377
+ range.moveStart("character", node_size);
1378
+ } else {
1379
+ range.moveEnd("character", -node_size);
1380
+ }
1381
+
1382
+ range_size = range_size - node_size;
1383
+ break;
1384
+ }
1385
+
1386
+ node = direction === 1 ? node.nextSibling : node.previousSibling;
1387
+ }
1388
+
1389
+ // The TextRange was not found. Return a reasonable value instead.
1390
+ return {node: element, offset: 0};
1391
+ }
1392
+
1393
+ /**
1394
+ * Assigns a suitable `commonAncestorContainer` property for the range
1395
+ *
1396
+ * @param {IERangeEmulator} range
1397
+ * @return void
1398
+ */
1399
+ function IER_commonAncestorContainer(range) {
1400
+ range.commonAncestorContainer = range._.parentElement();
1401
+ }
1402
+
1403
+ /**
1404
+ * Sets the `collapsed` property for the range
1405
+ *
1406
+ * @param {IERangeEmulator} range
1407
+ * return void
1408
+ */
1409
+ function IER_collapsed(range) {
1410
+ range.collapsed =
1411
+ range.startContainer === range.endContainer &&
1412
+ range.startOffset === range.endOffset;
1413
+ }
1414
+
1415
+ /**
1416
+ * Finds out the node's index among it's siblings
1417
+ *
1418
+ * @param {Node} node
1419
+ * @return {Number} index
1420
+ */
1421
+ function IER_getIndex(node) {
1422
+ var index = 0;
1423
+
1424
+ while ((node = node.previousSibling)) {
1425
+ index ++;
1426
+ }
1427
+
1428
+ return index;
1429
+ }
1430
+
1431
+
1432
+ /**
1433
+ * The basic tools class
1434
+ *
1435
+ * Copyright (C) 2010-2011 Nikolay Nemshilov
1436
+ */
1437
+ Rte.Tool = new Class(Element, {
1438
+
1439
+ block: true, // should the 'keypress' event be blocked
1440
+ blip: false, // whether it should be highlighted when used
1441
+ changes: true, // if this tool should fire 'change' on the rte
1442
+
1443
+ shortuct: null, // the shortuct key-code
1444
+ shiftKey: false, // if it should trigger with a shift-key only
1445
+
1446
+ /**
1447
+ * Basic constructor
1448
+ *
1449
+ * @param Rte rte reference
1450
+ * @return Rte.Tool this
1451
+ */
1452
+ initialize: function(rte) {
1453
+ var name;
1454
+
1455
+ // searching for the tool-name
1456
+ for (name in Rte.Tools) {
1457
+ if (Rte.Tools[name] === this.constructor) { break; }
1458
+ }
1459
+
1460
+ this.name = name;
1461
+ this.shortcut = this.shortcut || Rte.Shortcuts[name];
1462
+
1463
+ this.$super('div', {
1464
+ 'html': '<div class="icon"></div>', // <- icon container
1465
+ 'class': 'tool '+ name.toLowerCase(),
1466
+ 'title': (Rte.i18n[name] || name) + (
1467
+ this.shortcut ? " ("+ this.shortcut + ")" : ""
1468
+ )
1469
+ });
1470
+
1471
+ // registering the tool
1472
+ this.rte = rte;
1473
+ rte.tools[name] = this;
1474
+
1475
+ // hooking up the shortcuts
1476
+ this.shortcut = this.shortcut && this.shortcut.toLowerCase();
1477
+ this.shiftKey = this.shortcut && this.shortcut.indexOf('shift') > -1;
1478
+ this.shortcut = this.shortcut && this.shortcut.toUpperCase().charCodeAt(this.shortcut.length - 1);
1479
+
1480
+ // connecting the mousedown the way that the editor din't loose the focus
1481
+ this.onMousedown(function(e) {
1482
+ e.stop(); this.mousedown();
1483
+ });
1484
+
1485
+ // allowing some nice chains in the subclass
1486
+ return this;
1487
+ },
1488
+
1489
+ // those three methods should be implemented in subclasses
1490
+ exec: function() { }, /// the actual process
1491
+ active: function() { return false; }, // all tools not active by default
1492
+ enabled: function() { return true; }, // all tools enabled by default
1493
+
1494
+ /**
1495
+ * The entry point for all the tools, if you need to call a tool,
1496
+ * call this method. __DON'T CALL__ the #exec method directly!
1497
+ *
1498
+ * @param {Event} 'keydown' event
1499
+ * @return void
1500
+ */
1501
+ call: function(event) {
1502
+ if (!this.disabled) {
1503
+ if (event && this.block) { event.stop(); }
1504
+
1505
+ this.exec();
1506
+ this.rte.status.update();
1507
+ this.rte.fire('change', {tool: this});
1508
+
1509
+ if (this.blip) { this.highlight(); }
1510
+ }
1511
+ },
1512
+
1513
+ /**
1514
+ * Checks the command's status
1515
+ *
1516
+ * @return void
1517
+ */
1518
+ check: function() {
1519
+ this._.className = this._.className.replace(' disabled', '');
1520
+ this.disabled = false;
1521
+
1522
+ if ((this.name === 'Source' || this.rte.srcMode !== true) && this.enabled()) {
1523
+ this._.className = this._.className.replace(' active', '');
1524
+ if (this.active()) {
1525
+ this._.className += ' active';
1526
+ }
1527
+
1528
+ } else {
1529
+ this._.className += ' disabled';
1530
+ this.disabled = true;
1531
+ }
1532
+ },
1533
+
1534
+ /**
1535
+ * Replacing the highlight method with some css stuff instead of an Fx
1536
+ *
1537
+ * @return Rte.Tool this
1538
+ */
1539
+ highlight: function() {
1540
+ R(this.addClass('highlight').removeClass).bind(this, 'highlight').delay(100);
1541
+ },
1542
+
1543
+ // protected
1544
+
1545
+ // mousedown event receiver (might be replaced in subclasses)
1546
+ mousedown: function() {
1547
+ this.call();
1548
+ }
1549
+
1550
+ });
1551
+
1552
+ /**
1553
+ * Native command related abstract tool
1554
+ *
1555
+ * Copyright (C) 2010-2011 Nikolay Nemshilov
1556
+ */
1557
+ Rte.Tool.Command = new Class(Rte.Tool, {
1558
+ command: null, // execCommand name
1559
+ value: null, // execCommand value
1560
+
1561
+ /**
1562
+ * calling the command exec
1563
+ *
1564
+ * @return void
1565
+ */
1566
+ exec: function() {
1567
+ this.rte.selection.exec(this.command, this.value);
1568
+ },
1569
+
1570
+ /**
1571
+ * Checks if the command is enabled at all
1572
+ *
1573
+ * @return boolean check result
1574
+ */
1575
+ enabled: function() {
1576
+ try {
1577
+ return document.queryCommandEnabled(this.command);
1578
+ } catch(e) {
1579
+ return false;
1580
+ }
1581
+ },
1582
+
1583
+ /**
1584
+ * Queries if the command is in active state
1585
+ *
1586
+ * @return boolean check result
1587
+ */
1588
+ active: function() {
1589
+ try { // copy/paste commands cause errors under FF by default
1590
+ return this.value === null ?
1591
+ document.queryCommandState(this.command) :
1592
+ document.queryCommandValue(this.command) == this.value;
1593
+ } catch(e) {
1594
+ return false;
1595
+ }
1596
+ }
1597
+ });
1598
+
1599
+ /**
1600
+ * Text formatting specific abstract tool
1601
+ *
1602
+ * Copyright (C) 2010-2011 Nikolay Nemshilov
1603
+ */
1604
+ Rte.Tool.Format = new Class(Rte.Tool, {
1605
+ tag: null, // element's tag name
1606
+ atts: {}, // element's attributes
1607
+
1608
+ /**
1609
+ * Formatting tools basic constructor
1610
+ *
1611
+ * @param Rte rte
1612
+ * @return Rte.ToolFormat this
1613
+ */
1614
+ initialize: function(rte) {
1615
+ this.$super(rte);
1616
+ this.tag = (this.tag || Rte.Tags[this.name] || '').toUpperCase();
1617
+ return this;
1618
+ },
1619
+
1620
+ /**
1621
+ * triggering the formatting
1622
+ *
1623
+ * @return void
1624
+ */
1625
+ exec: function() {
1626
+ this[this.active() ? 'unformat' : 'format']();
1627
+ },
1628
+
1629
+ /**
1630
+ * Overloading the activity checks
1631
+ *
1632
+ * @return boolean check result
1633
+ */
1634
+ active: function() {
1635
+ return this.element() !== null;
1636
+ },
1637
+
1638
+ // protected
1639
+
1640
+ /**
1641
+ * Tries to find a currently active element
1642
+ *
1643
+ * @return raw dom element or null
1644
+ */
1645
+ element: function() {
1646
+ return this.rte.status.findElement(this.tag, this.attrs);
1647
+ },
1648
+
1649
+ /**
1650
+ * Removes the formatting
1651
+ *
1652
+ * @return void
1653
+ */
1654
+ unformat: function() {
1655
+ this._format(false);
1656
+ },
1657
+
1658
+ /**
1659
+ * Formats the block according to the settings
1660
+ *
1661
+ * @return void
1662
+ */
1663
+ format: function() {
1664
+ this._format(true);
1665
+ },
1666
+
1667
+ // private
1668
+
1669
+ /**
1670
+ * The actual formatting/unformatting process mumbo-jumbo
1671
+ *
1672
+ * @param boolean formatting direction true/false
1673
+ * @return void
1674
+ */
1675
+ _format: function(formatting) {
1676
+ var open_tag = '<'+ this.tag,
1677
+ close_tag = '</'+ this.tag + '>',
1678
+ editor = this.rte.editor,
1679
+ selection = this.rte.selection,
1680
+ range = selection.range(),
1681
+ selected = selection.text(),
1682
+ element = this.element(),
1683
+ content = element && (element.textContent || element.innerText);
1684
+
1685
+ // building the open-tag attributes
1686
+ for (var attr in this.attrs) {
1687
+ open_tag += ' '+ attr +'="'+ this.attrs[attr]+ '"';
1688
+ }
1689
+ open_tag += ">";
1690
+
1691
+ selection.store();
1692
+
1693
+ // Old IEs screw with the starting position
1694
+ // placing it before the open tag, so here we switch it back
1695
+ if (!formatting && range._) {
1696
+ editor.html(editor.html().replace(
1697
+ new RegExp(RegExp.escape(SELECTION_START_MARKER + open_tag), 'i'),
1698
+ open_tag + SELECTION_START_MARKER
1699
+ ));
1700
+ }
1701
+
1702
+
1703
+ if (formatting) {
1704
+ editor.html(editor.html()
1705
+ .replace(SELECTION_START_RE, open_tag + SELECTION_START_MARKER)
1706
+ .replace(SELECTION_END_RE, SELECTION_END_MARKER + close_tag)
1707
+ );
1708
+ } else if (element && selected === content) {
1709
+ // plainly remove the element if it was fully selected
1710
+ // in case there are several nested elements so that
1711
+ // we didn't get screwed with the regexps manipulations
1712
+ editor.removeElement(element);
1713
+ } else {
1714
+ editor.html(editor.html()
1715
+ .replace(SELECTION_START_RE, close_tag + SELECTION_START_MARKER)
1716
+ .replace(SELECTION_END_RE, SELECTION_END_MARKER + open_tag)
1717
+
1718
+ // cleaning up empty tags that might left
1719
+ .replace(/<([a-z]+)[^>]*?>\s*?<\/\1>/ig, '')
1720
+ );
1721
+ }
1722
+
1723
+ selection.restore();
1724
+ }
1725
+
1726
+ });
1727
+
1728
+ /**
1729
+ * A _shared module_ to provide the `options` list functionality
1730
+ * for the tools
1731
+ *
1732
+ * Copyright (C) 2010 Nikolay Nemshilov
1733
+ */
1734
+ Rte.Tool.Options = {
1735
+
1736
+ /**
1737
+ * Builds the options list
1738
+ *
1739
+ * @param Object key -> value hash
1740
+ */
1741
+ build: function(options) {
1742
+ this.trigger = $E('div', {'class': 'trigger', 'html': '&middot;'});
1743
+ this.display = $E('div', {'class': 'display'});
1744
+ this.options = $E('ul', {'class': 'options'});
1745
+
1746
+ this
1747
+ .addClass('with-options')
1748
+ .append(this.display, this.options)
1749
+ .insert(this.trigger, 'top');
1750
+
1751
+ this.items = {};
1752
+
1753
+ for (var value in options) {
1754
+ this.items[value] = $E('li').insert(options[value]);
1755
+ this.items[value].insertTo(this.options).value = value;
1756
+ }
1757
+
1758
+ // creating an the reset value
1759
+ this.items[''] = $E('li', {'class': 'remove', 'html': '--', 'title': Rte.i18n.Remove});
1760
+ this.items[''].insertTo(this.options, 'top').value = '';
1761
+
1762
+ // catching the clicks
1763
+ this.options.onMousedown(R(this.pick).bind(this));
1764
+
1765
+ // hidding the menu when the user interacts with the document outside of the document
1766
+ var hide = R(this.options.hide).bind(this.options, null);
1767
+ $(document).on({mousedown: hide,
1768
+ keydown: function(event) {
1769
+ if (event.keyCode === 27) { hide(); }
1770
+ }
1771
+ });
1772
+
1773
+ this.value = '';
1774
+ this.updateDisplay(null);
1775
+
1776
+ return this;
1777
+ },
1778
+
1779
+ // protected
1780
+
1781
+ // handling an option pick
1782
+ pick: function(event) {
1783
+ var target = event.stop().target;
1784
+
1785
+ if (target._.tagName !== 'LI') {
1786
+ target = target.parent('LI');
1787
+ }
1788
+
1789
+ if (target.value !== undefined) {
1790
+ this.options.hide();
1791
+ this.value = target.value;
1792
+ this.updateDisplay(this.value || null);
1793
+ this.markActive();
1794
+ this.exec();
1795
+ }
1796
+ },
1797
+
1798
+ // toggling the menu on the icon-click
1799
+ mousedown: function() {
1800
+ if (!this.disabled) {
1801
+ $$('.rui-rte-toolbar div.with-options ul.options')
1802
+ .without(this.options).each('hide');
1803
+
1804
+ // marking the current value so it could be seen
1805
+ if (this.options.hidden() && this.value !== null) {
1806
+ this.markActive();
1807
+ }
1808
+
1809
+ this.options.toggle('fade', {duration: 'short'});
1810
+ }
1811
+ },
1812
+
1813
+ // marks the currently active item
1814
+ markActive: function() {
1815
+ for (var item in this.items) {
1816
+ this.items[item][
1817
+ item === this.value ? 'addClass' : 'removeClass'
1818
+ ]('active');
1819
+ }
1820
+ },
1821
+
1822
+ // updates the display
1823
+ updateDisplay: function(value) {
1824
+ this.display.update(
1825
+ value !== null && value in this.items ?
1826
+ this.items[value].text() : this._.title
1827
+ );
1828
+ }
1829
+
1830
+ };
1831
+
1832
+ /**
1833
+ * An abstract formatting tool that works with styles
1834
+ * basically it wraps a selection with a 'span' tag and then
1835
+ * handles the 'style' attribute, adds/removes various styles, etc.
1836
+ *
1837
+ * Copyright (C) 2010 Nikolay Nemshilov
1838
+ */
1839
+ Rte.Tool.Style = new Class(Rte.Tool.Format, {
1840
+ include: Rte.Tool.Options,
1841
+
1842
+ tag: 'span', // tag name of the element to be used
1843
+ style: null, // the style property name (dashed)
1844
+
1845
+ /**
1846
+ * Joint constructor
1847
+ *
1848
+ * @param Rte rte
1849
+ * @return Rte.Tool.StyleOptions
1850
+ */
1851
+ initialize: function(rte, options) {
1852
+ // a regular expression to process the `style` property
1853
+ this.re = new RegExp("(^|;)\\s*"+ RegExp.escape(this.style + ":")+ "\\s*(.+?)\\s*(;|$)");
1854
+ this.attrs = { style: this.re };
1855
+
1856
+ this.$super(rte).build(options);
1857
+
1858
+ return this;
1859
+ },
1860
+
1861
+ /**
1862
+ * Triggers the action
1863
+ *
1864
+ * @return void
1865
+ */
1866
+ exec: function() {
1867
+ // unformatting if there is a value
1868
+ if (this.active()) {
1869
+ this.attrs = {style: this.style + ":" + this._value};
1870
+ this.unformat();
1871
+ }
1872
+
1873
+ // formatting if some value was asked
1874
+ if (this.value) {
1875
+ this.attrs = {style: this.style + ":" + this.value};
1876
+ this.format();
1877
+ }
1878
+
1879
+ // getting back the RE checker
1880
+ this.attrs = {style: this.re};
1881
+ },
1882
+
1883
+ /**
1884
+ * Overloading the original method so that we could do some additional
1885
+ * features with the actual current value
1886
+ *
1887
+ * @return void
1888
+ */
1889
+ active: function() {
1890
+ var element = this.element(), active = false, value = null;
1891
+
1892
+ if (element !== null) {
1893
+ this._value = value = this.getStyleValue();
1894
+ active = true;
1895
+ }
1896
+
1897
+ this.updateDisplay(value);
1898
+
1899
+ return active;
1900
+ },
1901
+
1902
+ // protected
1903
+
1904
+ /**
1905
+ * Finds the current style value (if any)
1906
+ *
1907
+ * @return string style value or null if nothing
1908
+ */
1909
+ getStyleValue: function() {
1910
+ var element = this.element();
1911
+ var attribute = element !== null ? element.getAttribute('style') : null;
1912
+
1913
+ if (attribute !== null) {
1914
+ if ((attribute = attribute.match(this.re)) !== null) {
1915
+ attribute = attribute[2];
1916
+ }
1917
+ }
1918
+
1919
+ return attribute;
1920
+ }
1921
+ });
1922
+
1923
+ /**
1924
+ * an abstract color-picking tool
1925
+ *
1926
+ * Copyright (C) 2010-2011 Nikolay Nemshilov
1927
+ */
1928
+ Rte.Tool.Color = new Class(Rte.Tool.Style, {
1929
+
1930
+ extend: {
1931
+ COLORS: R([
1932
+ // TODO that's ain't no cool hacker's approach!
1933
+ '000000 444444 666666 999999 cccccc eeeeee f4f4f4 ffffff',
1934
+ 'f24020 f79c33 fff84c 6af244 5ef9fd 0048f7 8950f7 ee5ff8',
1935
+ 'e39e9b f5cba1 fee3a1 bcd3ab a6c3c8 a2c6e5 b1abd3 d0aabc '+
1936
+ 'd77169 f1b374 fdd675 9cbe83 7ca4ae 74aad8 8983bf bb839f '+
1937
+ 'cc0100 e79138 f1c332 69a84f 45818e 3d85c6 674ea7 a64d79 '+
1938
+ '990000 b45f05 bf9000 38761c 134f5c 0b5394 351b75 751a47 '+
1939
+ '660000 783e03 7f6000 264e13 0b333d 063763 1f124c 4c1030'
1940
+ ])
1941
+ },
1942
+
1943
+ /**
1944
+ * Basic constructor, builds the colors picking panel
1945
+ *
1946
+ * @param Rte rte
1947
+ * @return Rte.Tool.Color this
1948
+ */
1949
+ initialize: function(rte) {
1950
+ this.$super(rte, {}).addClass('color');
1951
+ this.display.clean();
1952
+
1953
+ // building the color picker menu
1954
+ Rte.Tool.Color.COLORS.each(function(line) {
1955
+ var group = $E('li', {'class': 'group'}),
1956
+ list = $E('ul').insertTo(group),
1957
+ colors = line.split(' '), i = 0, color, forecolor;
1958
+
1959
+ for (; i < colors.length; i++) {
1960
+ color = '#' + colors[i];
1961
+ forecolor = (parseInt('ffffff', 16) - parseInt(colors[i], 16)).toString(16);
1962
+
1963
+ // IE will get screwed if the length of the color is less than 6
1964
+ while (forecolor.length < 6) {
1965
+ forecolor += '0';
1966
+ }
1967
+
1968
+ this.items[color] = $E('li', {
1969
+ html: '&bull;',
1970
+ style: {
1971
+ background: color,
1972
+ color: '#'+ forecolor
1973
+ }
1974
+ });
1975
+
1976
+ this.items[color].insertTo(list).value = color;
1977
+ }
1978
+
1979
+ this.options.append(group);
1980
+ }, this);
1981
+
1982
+ return this;
1983
+ },
1984
+
1985
+ // protected
1986
+
1987
+ /**
1988
+ * Overloading the property so that it converted any color formats
1989
+ * into a six chars hex value
1990
+ *
1991
+ * @return String current color or null
1992
+ */
1993
+ getStyleValue: function() {
1994
+ var color = this.$super(), match;
1995
+
1996
+ if (color !== null) {
1997
+ if ((match = /^#(\w)(\w)(\w)$/.exec(color))) {
1998
+ color = "#"+ match[1]+match[1]+match[2]+match[2]+match[3]+match[3];
1999
+ } else if ((match = /^\s*rgb\((\d+),\s*(\d+),\s*(\d+)\)\s*$/.exec(color))) {
2000
+ color = "#"+ R(match.slice(1)).map(function(bit) {
2001
+ bit = (bit-0).toString(16);
2002
+ return bit.length === 1 ? '0'+bit : bit;
2003
+ }).join('');
2004
+ }
2005
+ }
2006
+
2007
+ return color;
2008
+ },
2009
+
2010
+ /**
2011
+ * Replacing the original method so that
2012
+ * it changed the color of the display thing
2013
+ * instead of text
2014
+ *
2015
+ * @param String value
2016
+ * @return void
2017
+ */
2018
+ updateDisplay: function(value) {
2019
+ this.display._.style.background = value === null ?
2020
+ 'transparent' : value;
2021
+ }
2022
+ });
2023
+
2024
+ /**
2025
+ * An abstract URL tool, used with the links, images, videos, etc.
2026
+ *
2027
+ * Copyright (C) 2010-2011 Nikolay Nemshilov
2028
+ */
2029
+ Rte.Tool.Url = new Class(Rte.Tool, {
2030
+ attr: null, // the url-attribute 'src', 'href', etc.
2031
+
2032
+ exec: function(url) {
2033
+ if (url === undefined) {
2034
+ this.prompt();
2035
+ } else {
2036
+ if (url) {
2037
+ this[this.element() ? 'url' : 'create'](url);
2038
+ } else {
2039
+ this.rte.editor.removeElement(this.element());
2040
+ }
2041
+ }
2042
+ },
2043
+
2044
+ active: function() {
2045
+ return this.element() !== null;
2046
+ },
2047
+
2048
+ prompt: function() {
2049
+ var url = prompt(Rte.i18n.UrlAddress, this.url() || 'http://some.url.com');
2050
+
2051
+ if (url !== null) {
2052
+ this.exec(url);
2053
+ }
2054
+ },
2055
+
2056
+ // protected
2057
+
2058
+ url: function(url) {
2059
+ if (this.element()) {
2060
+ if (url !== undefined) {
2061
+ this.element()[this.attr] = url;
2062
+ } else {
2063
+ return this.element()[this.attr];
2064
+ }
2065
+ }
2066
+ },
2067
+
2068
+ create: function(url) {
2069
+ this.rte.selection.exec(this.command, url);
2070
+ }
2071
+ });
2072
+
2073
+ /**
2074
+ * Making the things bold tool
2075
+ *
2076
+ * Copyright (C) 2010-2011 Nikolay Nemshilov
2077
+ */
2078
+ Rte.Tools.Bold = new Class(Rte.Tool.Format, {
2079
+ });
2080
+
2081
+ /**
2082
+ * Making the things italic
2083
+ *
2084
+ * Copyright (C) 2010-2011 Nikolay Nemshilov
2085
+ */
2086
+ Rte.Tools.Italic = new Class(Rte.Tool.Format, {
2087
+ });
2088
+
2089
+ /**
2090
+ * Making the things underline tool
2091
+ *
2092
+ * Copyright (C) 2010-2011 Nikolay Nemshilov
2093
+ */
2094
+ Rte.Tools.Underline = new Class(Rte.Tool.Format, {
2095
+ });
2096
+
2097
+ /**
2098
+ * Making the things strike through
2099
+ *
2100
+ * Copyright (C) 2010 Nikolay Nemshilov
2101
+ */
2102
+ Rte.Tools.Strike = new Class(Rte.Tool.Format, {
2103
+ });
2104
+
2105
+ /**
2106
+ * The cut tool
2107
+ *
2108
+ * Copyrigth (C) 2010-2011 Nikolay Nemshilov
2109
+ */
2110
+ Rte.Tools.Cut = new Class(Rte.Tool.Command, {
2111
+ command: 'cut',
2112
+ block: false,
2113
+ blip: true
2114
+ });
2115
+
2116
+ /**
2117
+ * The copy tool
2118
+ *
2119
+ * Copyrigth (C) 2010-2011 Nikolay Nemshilov
2120
+ */
2121
+ Rte.Tools.Copy = new Class(Rte.Tools.Cut, {
2122
+ command: 'copy'
2123
+ });
2124
+
2125
+ /**
2126
+ * The paste action
2127
+ *
2128
+ * Copyrigth (C) 2010-2011 Nikolay Nemshilov
2129
+ */
2130
+ Rte.Tools.Paste = new Class(Rte.Tools.Cut, {
2131
+ command: 'paste'
2132
+ });
2133
+
2134
+ /**
2135
+ * the 'left' tool
2136
+ *
2137
+ * Copyright (C) 2010 Nikolay Nemshilov
2138
+ */
2139
+ Rte.Tools.Left = new Class(Rte.Tool.Command, {
2140
+ command: 'justifyleft'
2141
+ });
2142
+
2143
+ /**
2144
+ * the 'center' tool
2145
+ *
2146
+ * Copyright (C) 2010 Nikolay Nemshilov
2147
+ */
2148
+ Rte.Tools.Center = new Class(Rte.Tool.Command, {
2149
+ command: 'justifycenter'
2150
+ });
2151
+
2152
+ /**
2153
+ * the 'right' tool
2154
+ *
2155
+ * Copyright (C) 2010 Nikolay Nemshilov
2156
+ */
2157
+ Rte.Tools.Right = new Class(Rte.Tool.Command, {
2158
+ command: 'justifyright'
2159
+ });
2160
+
2161
+ /**
2162
+ * the 'justify' tool
2163
+ *
2164
+ * Copyright (C) 2010 Nikolay Nemshilov
2165
+ */
2166
+ Rte.Tools.Justify = new Class(Rte.Tool.Command, {
2167
+ command: 'justifyfull'
2168
+ });
2169
+
2170
+ /**
2171
+ * The undo tool
2172
+ *
2173
+ * The actual magic happens in the Rte.Undoer class
2174
+ * here we just show the status and blip it when used
2175
+ *
2176
+ * Copyright (C) 2010-2011 Nikolay Nemshilov
2177
+ */
2178
+ Rte.Tools.Undo = new Class(Rte.Tool, {
2179
+ blip: true,
2180
+
2181
+ exec: function() {
2182
+ this.rte.undoer.undo();
2183
+ },
2184
+
2185
+ enabled: function() {
2186
+ return this.rte.undoer.hasUndo();
2187
+ }
2188
+ });
2189
+
2190
+ /**
2191
+ * the 'redo' tool
2192
+ *
2193
+ * The actual magic happens in the Rte.Undoer class
2194
+ * here we just show the status and blip it when used
2195
+ *
2196
+ * Copyright (C) 2010 Nikolay Nemshilov
2197
+ */
2198
+ Rte.Tools.Redo = new Class(Rte.Tool, {
2199
+ blip: true,
2200
+
2201
+ exec: function() {
2202
+ this.rte.undoer.redo();
2203
+ },
2204
+
2205
+ enabled: function() {
2206
+ return this.rte.undoer.hasRedo();
2207
+ }
2208
+
2209
+ });
2210
+
2211
+ /**
2212
+ * The code block tool
2213
+ *
2214
+ * Copyrigth (C) 2010-2011 Nikolay Nemshilov
2215
+ */
2216
+ Rte.Tools.Code = new Class(Rte.Tool.Format, {
2217
+ });
2218
+
2219
+ /**
2220
+ * The block quote tool
2221
+ *
2222
+ * Copyrigth (C) 2010 Nikolay Nemshilov
2223
+ */
2224
+ Rte.Tools.Quote = new Class(Rte.Tool.Format, {
2225
+ });
2226
+
2227
+ /**
2228
+ * the 'ttext' tool
2229
+ *
2230
+ * Copyright (C) 2010-2011 Nikolay Nemshilov
2231
+ */
2232
+ Rte.Tools.Ttext = new Class(Rte.Tool.Format, {
2233
+ });
2234
+
2235
+ /**
2236
+ * The header block tool
2237
+ *
2238
+ * Copyrigth (C) 2010-2011 Nikolay Nemshilov
2239
+ */
2240
+ Rte.Tools.Header = new Class(Rte.Tool.Format, {
2241
+ });
2242
+
2243
+ /**
2244
+ * The the url link tool
2245
+ *
2246
+ * Copyrigth (C) 2010-2011 Nikolay Nemshilov
2247
+ */
2248
+ Rte.Tools.Link = new Class(Rte.Tool.Url, {
2249
+ command: 'createlink',
2250
+ attr: 'href',
2251
+
2252
+ enabled: function() {
2253
+ return !this.rte.selection.empty() || this.active();
2254
+ },
2255
+
2256
+ element: function() {
2257
+ return this.rte.status.findElement('A', {});
2258
+ }
2259
+ });
2260
+
2261
+ /**
2262
+ * The image tool
2263
+ *
2264
+ * Copyrigth (C) 2010-2011 Nikolay Nemshilov
2265
+ */
2266
+ Rte.Tools.Image = new Class(Rte.Tool.Url, {
2267
+ command: 'insertimage',
2268
+ attr: 'src',
2269
+
2270
+ element: function() {
2271
+ var image = this.rte.selection.element();
2272
+ return image !== null && image.tagName === "IMG" ? image : null;
2273
+ }
2274
+
2275
+ });
2276
+
2277
+ /**
2278
+ * the 'video' tool
2279
+ *
2280
+ * Copyright (C) 2010 Nikolay Nemshilov
2281
+ */
2282
+ Rte.Tools.Video = new Class(Rte.Tool.Url, {
2283
+ command: 'inserthtml',
2284
+
2285
+ enabled: function() {
2286
+ return true; // always enabled
2287
+ },
2288
+
2289
+ element: function() {
2290
+ return this.rte.status.findElement('OBJECT', {});
2291
+ },
2292
+
2293
+ // works with url address of the embedded object
2294
+ url: function(url) {
2295
+ var element = this.element() && this.element().getElementsByTagName('embed')[0];
2296
+
2297
+ if (element) {
2298
+ if (url !== undefined) {
2299
+ element.src = this.swfUrl(url);
2300
+ } else {
2301
+ return element.src;
2302
+ }
2303
+ }
2304
+ },
2305
+
2306
+ // creates the actual object block
2307
+ create: function(url) {
2308
+ var swf_url = this.swfUrl(url),
2309
+ size = 'width="'+ this.rte.options.videoSize.split('x')[0] +
2310
+ '" height="'+ this.rte.options.videoSize.split('x')[1] + '"';
2311
+
2312
+ this.$super('<object '+ size +'>'+
2313
+ '<param name="src" value="'+ swf_url +'" />'+
2314
+ '<embed src="'+ swf_url +'" type="application/x-shockwave-flash" '+ size + ' />' +
2315
+ '</object>');
2316
+ },
2317
+
2318
+ // making the actual 'swf' url
2319
+ swfUrl: function(url) {
2320
+ return R(Rte.Videos).map(function(desc) {
2321
+ return url.match(desc[0]) ?
2322
+ url.replace(desc[0], desc[1]) : null;
2323
+ }).compact()[0] || url;
2324
+ }
2325
+ });
2326
+
2327
+ /**
2328
+ * the 'dotlist' tool
2329
+ *
2330
+ * Copyright (C) 2010 Nikolay Nemshilov
2331
+ */
2332
+ Rte.Tools.Dotlist = new Class(Rte.Tool.Command, {
2333
+ command: 'insertunorderedlist'
2334
+ });
2335
+
2336
+ /**
2337
+ * the 'numlist' tool
2338
+ *
2339
+ * Copyright (C) 2010 Nikolay Nemshilov
2340
+ */
2341
+ Rte.Tools.Numlist = new Class(Rte.Tool.Command, {
2342
+ command: 'insertorderedlist'
2343
+ });
2344
+
2345
+ /**
2346
+ * the 'indent' tool
2347
+ *
2348
+ * Copyright (C) 2010 Nikolay Nemshilov
2349
+ */
2350
+ Rte.Tools.Indent = new Class(Rte.Tool.Command, {
2351
+ command: 'indent'
2352
+ });
2353
+
2354
+ /**
2355
+ * the 'Outdent' tool
2356
+ *
2357
+ * Copyright (C) 2010 Nikolay Nemshilov
2358
+ */
2359
+ Rte.Tools.Outdent = new Class(Rte.Tool.Command, {
2360
+ command: 'outdent'
2361
+ });
2362
+
2363
+ /**
2364
+ * the 'forecolor' tool
2365
+ *
2366
+ * Copyright (C) 2010 Nikolay Nemshilov
2367
+ */
2368
+ Rte.Tools.Forecolor = new Class(Rte.Tool.Color, {
2369
+ style: 'color'
2370
+ });
2371
+
2372
+ /**
2373
+ * the 'backcolor' tool
2374
+ *
2375
+ * Copyright (C) 2010 Nikolay Nemshilov
2376
+ */
2377
+ Rte.Tools.Backcolor = new Class(Rte.Tool.Color, {
2378
+ style: 'background-color'
2379
+ });
2380
+
2381
+ /**
2382
+ * The source tool
2383
+ *
2384
+ * Copyrigth (C) 2010-2011 Nikolay Nemshilov
2385
+ */
2386
+ Rte.Tools.Source = new Class(Rte.Tool, {
2387
+ source: false, // the textarea element reference
2388
+
2389
+ exec: function() {
2390
+ this[this.rte.srcMode ? 'showPreview' : 'showSource']();
2391
+ this.rte.srcMode = !this.rte.srcMode;
2392
+ },
2393
+
2394
+ active: function() {
2395
+ return this.rte.srcMode;
2396
+ },
2397
+
2398
+ // protected
2399
+
2400
+ showPreview: function() {
2401
+ this.rte.editor.setStyle('visibility:visible');
2402
+ if (this.source) {
2403
+ this.rte.value(this.source.value());
2404
+ this.source.remove();
2405
+ }
2406
+
2407
+ this.rte.editor.focus();
2408
+ },
2409
+
2410
+ showSource: function() {
2411
+ this.rte.editor.setStyle('visibility:hidden;');
2412
+
2413
+ (
2414
+ this.source = this.source ||
2415
+ $E('textarea', {'class': 'rui-rte-source'})
2416
+ )
2417
+ .insertTo(this.rte.editor, 'before')
2418
+ .resize(this.rte.editor.size())
2419
+ .setValue('' + this.rte.value())
2420
+ .focus();
2421
+
2422
+ this.rte.focused = true;
2423
+
2424
+ this.rte.status.update();
2425
+
2426
+ // locking all the tools
2427
+ for (var name in this.rte.tools) {
2428
+ if (this.rte.tools[name] !== this) {
2429
+ this.rte.tools[name].addClass('disabled');
2430
+ }
2431
+ }
2432
+ }
2433
+ });
2434
+
2435
+ /**
2436
+ * the 'clear' tool
2437
+ *
2438
+ * Copyright (C) 2010-2011 Nikolay Nemshilov
2439
+ */
2440
+ Rte.Tools.Clear = new Class(Rte.Tool, {
2441
+
2442
+ exec: function() {
2443
+ this.rte.editor.clean();
2444
+ }
2445
+
2446
+ });
2447
+
2448
+ /**
2449
+ * the 'save' tool
2450
+ *
2451
+ * Copyright (C) 2010-2011 Nikolay Nemshilov
2452
+ */
2453
+ Rte.Tools.Save = new Class(Rte.Tool, {
2454
+
2455
+ initialize: function(rte) {
2456
+ this.$super(rte);
2457
+ if (!(rte.textarea && rte.textarea.form())) {
2458
+ this.disabled = true;
2459
+ this.addClass('disabled');
2460
+ }
2461
+ },
2462
+
2463
+ exec: function() {
2464
+ if (!this.disabled) {
2465
+ this.rte.textarea.form().submit();
2466
+ }
2467
+ },
2468
+
2469
+ check: function() {} // checked in the constructor
2470
+ });
2471
+
2472
+ /**
2473
+ * Formatting style tool
2474
+ *
2475
+ * Copyright (C) 2010 Nikolay Nemshilov
2476
+ */
2477
+ Rte.Tools.Format = new Class(Rte.Tool.Format, {
2478
+ include: Rte.Tool.Options,
2479
+
2480
+ /**
2481
+ * Constructor, builds the options list and
2482
+ * defines the formats index for quick access
2483
+ *
2484
+ * @param Rte rte
2485
+ */
2486
+ initialize: function(rte) {
2487
+ var options = {}, rule, tag, match;
2488
+
2489
+ this.formats = {};
2490
+
2491
+ for (rule in Rte.Formats) {
2492
+ if ((match = rule.match(/^([a-z0-9]+)(\.([a-z0-9_\-]+))?$/))) {
2493
+ tag = match[1];
2494
+
2495
+ this.formats[rule] = { tag: tag.toUpperCase(), attrs: {}, match: {} };
2496
+
2497
+ if (match[3]) {
2498
+ this.formats[rule]['attrs']['class'] = match[3];
2499
+ this.formats[rule]['match']['class'] = new RegExp(
2500
+ '(^|\\s+)'+ RegExp.escape(match[3]) + '(\\s+|$)'
2501
+ );
2502
+ }
2503
+
2504
+ options[rule] = '<'+ tag + ' class="'+ match[3] + '">'+
2505
+ Rte.Formats[rule] + '</'+ tag + '>';
2506
+ }
2507
+ }
2508
+
2509
+ this.$super(rte).build(options);
2510
+
2511
+ return this;
2512
+ },
2513
+
2514
+ /**
2515
+ * Handling the formatting
2516
+ *
2517
+ * @return void
2518
+ */
2519
+ exec: function() {
2520
+ // removing the formatting first
2521
+ if (this.active() && this.rule) {
2522
+ this.tag = this.formats[this.rule].tag;
2523
+ this.attrs = this.formats[this.rule].attrs;
2524
+ this.unformat();
2525
+ }
2526
+
2527
+ // applying a new formatting if needed
2528
+ if (this.value && this.formats[this.value]) {
2529
+ this.tag = this.formats[this.value].tag;
2530
+ this.attrs = this.formats[this.value].attrs;
2531
+ this.format();
2532
+ }
2533
+ },
2534
+
2535
+ /**
2536
+ * Overloading the original method so that it updated the
2537
+ * currently used formatting style in the display area
2538
+ *
2539
+ * @return boolean check result
2540
+ */
2541
+ active: function() {
2542
+ var active = this.element() !== null;
2543
+ this.updateDisplay(this.rule);
2544
+ return active;
2545
+ },
2546
+
2547
+ // protected
2548
+
2549
+ // overloading the original method to handle multiple options
2550
+ element: function() {
2551
+ var rule, element, status = this.rte.status;
2552
+
2553
+ for (rule in this.formats) {
2554
+ element = status.findElement(
2555
+ this.formats[rule]['tag'],
2556
+ this.formats[rule]['match']
2557
+ );
2558
+
2559
+ if (element !== null) {
2560
+ this.rule = rule;
2561
+ return element;
2562
+ }
2563
+ }
2564
+
2565
+ return (this.rule = null);
2566
+ }
2567
+
2568
+
2569
+
2570
+ });
2571
+
2572
+ /**
2573
+ * Font-name options tool
2574
+ *
2575
+ * Copyright (C) 2010 Nikolay Nemshilov
2576
+ */
2577
+ Rte.Tools.Fontname = new Class(Rte.Tool.Style, {
2578
+ style: 'font-family',
2579
+
2580
+ /**
2581
+ * Basic constructor
2582
+ *
2583
+ * @param Rte rte
2584
+ */
2585
+ initialize: function(rte) {
2586
+ var options = {}, name, fonts = Rte.FontNames;
2587
+
2588
+ for (name in fonts) {
2589
+ options[fonts[name]] = '<div style="font-family:'+
2590
+ fonts[name]+ '">' + name + '</div>';
2591
+ }
2592
+
2593
+ return this.$super(rte, options);
2594
+ }
2595
+ });
2596
+
2597
+
2598
+ /**
2599
+ * The font-size tool
2600
+ *
2601
+ * Copyright (C) 2010 Nikolay Nemshilov
2602
+ */
2603
+ Rte.Tools.Fontsize = new Class(Rte.Tool.Style, {
2604
+ style: 'font-size',
2605
+
2606
+ /**
2607
+ * Basic constructor
2608
+ *
2609
+ * @param Rte rte
2610
+ */
2611
+ initialize: function(rte) {
2612
+ var options = {}, i=0, sizes = Rte.FontSizes.split(/\s+/);
2613
+
2614
+ for (; i < sizes.length; i++) {
2615
+ options[sizes[i]] = '<div style="font-size:'+
2616
+ sizes[i] +'">'+ sizes[i] + '</div>';
2617
+ }
2618
+
2619
+ return this.$super(rte, options);
2620
+ }
2621
+ });
2622
+
2623
+ /**
2624
+ * The subscript tool
2625
+ *
2626
+ * Copyright (C) 2010 Nikolay Nemshilov
2627
+ */
2628
+ Rte.Tools.Subscript = new Class(Rte.Tool.Command, {
2629
+ command: 'subscript'
2630
+ });
2631
+
2632
+ /**
2633
+ * The superscript tool
2634
+ *
2635
+ * Copyright (C) 2010 Nikolay Nemshilov
2636
+ */
2637
+ Rte.Tools.Superscript = new Class(Rte.Tool.Command, {
2638
+ command: 'superscript'
2639
+ });
2640
+
2641
+ /**
2642
+ * Document level hooks for the Rte widget
2643
+ *
2644
+ * Copyright (C) 2010 Nikolay Nemshilov
2645
+ */
2646
+ $(document).onReady(function() {
2647
+ $$(Rte.Options.cssRule).each('getRich');
2648
+ });
2649
+
2650
+ /**
2651
+ * Input level hooks to convert textareas into RTE
2652
+ *
2653
+ * Copyright (C) 2010 Nikolay Nemshilov
2654
+ */
2655
+ Input.include({
2656
+ /**
2657
+ * converts an input field into a RTE
2658
+ *
2659
+ * @param Object options
2660
+ * @return Rte editor
2661
+ */
2662
+ getRich: function(options) {
2663
+ if (this._.type === 'textarea' && !this.rte) {
2664
+ this.rte = new Rte(this, options);
2665
+ }
2666
+
2667
+ return this.rte;
2668
+ }
2669
+ });
2670
+
2671
+ var embed_style = document.createElement('style'),
2672
+ embed_rules = document.createTextNode("div.rui-rte,div.rui-rte-toolbar,div.rui-rte-toolbar *,div.rui-rte-editor,div.rui-rte-status,div.rui-rte-status *{margin:0;padding:0;background:none;border:none;width:auto;height:auto}textarea[data-rte]{visibility:hidden}div.rui-rte{display:inline-block; *display:inline; *zoom:1;position:relative}div.rui-rte-toolbar{padding:.15em .3em;background:#eee;border-radius:.25em .25em 0 0;-moz-border-radius:.25em .25em 0 0;-webkit-border-radius:.25em .25em 0 0;border:1px solid #ccc;border-bottom:none}div.rui-rte-toolbar div.line{display:inline-block; *display:inline; *zoom:1;margin-bottom:1px}div.rui-rte-toolbar div.bar{display:inline-block; *display:inline; *zoom:1;margin-right:2px}div.rui-rte-toolbar div.tool{display:inline-block; *display:inline; *zoom:1;margin-right:1px;vertical-align:middle;position:relative;cursor:pointer;border:1px solid #bbb;background-image:url(/images/rightjs-ui/rte.png);background-position:0px 0px;background-color:#fff;border-radius:.25em;-moz-border-radius:.25em;-webkit-border-radius:.25em}div.rui-rte-toolbar div.tool:hover{background-color:#ddd;border-color:#777}div.rui-rte-toolbar div.active{background-position:-20px 0px;background-color:#eee;border-color:#666;box-shadow:#aaa .025em .025em .5em;-moz-box-shadow:#aaa .025em .025em .5em;-webkit-box-shadow:#aaa .025em .025em .5em}div.rui-rte-toolbar div.disabled,div.rui-rte-toolbar div.disabled:hover{opacity:.4;filter:alpha(opacity=40);background-position:-40px 0px;background-color:#eee;border-color:#aaa;cursor:default}div.rui-rte-toolbar div.highlight{background-color:#BBB;border-color:#666}div.rui-rte-toolbar div.icon{height:20px;width:20px;background-image:url(/images/rightjs-ui/rte.png);background-repeat:no-repeat;background-position:20px 20px}div.rui-rte-toolbar div.save div.icon{background-position:0px -20px}div.rui-rte-toolbar div.clear div.icon{background-position:-20px -20px}div.rui-rte-toolbar div.source div.icon{background-position:-40px -20px}div.rui-rte-toolbar div.bold div.icon{background-position:0px -40px}div.rui-rte-toolbar div.italic div.icon{background-position:-20px -40px}div.rui-rte-toolbar div.underline div.icon{background-position:-40px -40px}div.rui-rte-toolbar div.strike div.icon{background-position:-60px -40px}div.rui-rte-toolbar div.cut div.icon{background-position:0px -60px}div.rui-rte-toolbar div.copy div.icon{background-position:-20px -60px}div.rui-rte-toolbar div.paste div.icon{background-position:-40px -60px}div.rui-rte-toolbar div.left div.icon{background-position:0px -80px}div.rui-rte-toolbar div.center div.icon{background-position:-20px -80px}div.rui-rte-toolbar div.right div.icon{background-position:-40px -80px}div.rui-rte-toolbar div.justify div.icon{background-position:-60px -80px}div.rui-rte-toolbar div.undo div.icon{background-position:0px -100px}div.rui-rte-toolbar div.redo div.icon{background-position:-20px -100px}div.rui-rte-toolbar div.quote div.icon{background-position:0px -120px}div.rui-rte-toolbar div.code div.icon{background-position:-20px -120px}div.rui-rte-toolbar div.ttext div.icon{background-position:-40px -120px}div.rui-rte-toolbar div.header div.icon{background-position:-60px -120px}div.rui-rte-toolbar div.image div.icon{background-position:0px -140px}div.rui-rte-toolbar div.link div.icon{background-position:-20px -140px}div.rui-rte-toolbar div.video div.icon{background-position:-40px -140px}div.rui-rte-toolbar div.dotlist div.icon{background-position:0px -160px}div.rui-rte-toolbar div.numlist div.icon{background-position:-20px -160px}div.rui-rte-toolbar div.indent div.icon{background-position:-40px -160px}div.rui-rte-toolbar div.outdent div.icon{background-position:-60px -160px}div.rui-rte-toolbar div.forecolor div.icon{background-position:0px -180px}div.rui-rte-toolbar div.backcolor div.icon{background-position:-20px -180px}div.rui-rte-toolbar div.symbol div.icon{background-position:0px -200px}div.rui-rte-toolbar div.subscript div.icon{background-position:-20px -200px}div.rui-rte-toolbar div.superscript div.icon{background-position:-40px -200px}div.rui-rte-toolbar div.with-options{padding-right:8px}div.rui-rte-toolbar div.with-options div.trigger{position:absolute;right:0;height:100%;width:7px;text-align:center;background:#ccc;border-left:1px solid #bbb}div.rui-rte-toolbar div.bar div:hover div.trigger,div.rui-rte-toolbar div.bar div.active div.trigger{background:#aaa;border-color:#888}div.rui-rte-toolbar div.with-options div.icon{display:none}div.rui-rte-toolbar div.with-options div.display{display:block;line-height:20px;padding:0 6px;margin:0;color:#222;font-size:12px;background:#f8f8f8}div.rui-rte-toolbar div.with-options ul.options,div.rui-rte-toolbar div.with-options ul.options li{list-style:none;margin:0;padding:0}div.rui-rte-toolbar div.with-options ul.options{display:none;cursor:default;position:absolute;margin-bottom:1px;margin-left:-1px;background:#fff;border:1px solid #aaa;border-radius:.25em;-moz-border-radius:.25em;-webkit-border-radius:.25em;box-shadow:#bbb .1em .1em .25em;-moz-box-shadow:#bbb .1em .1em .25em;-webkit-box-shadow:#bbb .1em .1em .25em}div.rui-rte-toolbar div.with-options ul.options li{padding:.2em .5em;white-space:nowrap;cursor:pointer}div.rui-rte-toolbar div.with-options ul.options li:hover{background-color:#eee}div.rui-rte-toolbar div.with-options ul.options li> *{margin:0;padding:0;border:none;position:static}div.rui-rte-toolbar div.color div.icon{display:block}div.rui-rte-toolbar div.color ul.options{padding-bottom:.5em}div.rui-rte-toolbar div.color ul.options li.group,div.rui-rte-toolbar div.color ul.options li.group:hover{background:none}div.rui-rte-toolbar div.color ul.options li.group ul{width:144px;clear:both;padding-top:.5em}div.rui-rte-toolbar div.color ul.options li.group ul li{float:left;width:16px;height:16px;line-height:16px;font-size:80%;text-align:center;text-indent:-9999em;padding:0;cursor:pointer;border:1px solid transparent}div.rui-rte-toolbar div.color ul.options li.group ul li:hover,div.rui-rte-toolbar div.color ul.options li.group ul li.active{background:none;border-color:#444;border-radius:.1em;-moz-border-radius:.1em;-webkit-border-radius:.1em}div.rui-rte-toolbar div.color ul.options li.group ul li.active{text-indent:0}div.rui-rte-toolbar div.color div.display{position:absolute;text-indent:-9999em;bottom:2px;left:3px;margin:0;padding:0;width:14px;height:4px;border-radius:.1em;-moz-border-radius:.1em;-webkit-border-radius:.1em}div.rui-rte-toolbar div.color ul.options li.group ul li.none{border-color:#444}div.rui-rte-toolbar div.color ul.options li.group ul li.label,div.rui-rte-toolbar div.color ul.options li.group ul li.label:hover{text-indent:0;border:none;margin-left:.5em;font-size:1em;cursor:default}div.rui-rte-editor{outline:none;outline:hidden;padding:.1em .3em;overflow:auto;background:white;border:1px solid #ccc}div.rui-rte-editor:focus{border-color:#aaa}div.rui-rte-editor> *:first-child{margin-top:0}div.rui-rte-editor> *:last-child{margin-bottom:0}div.rui-rte textarea.rui-rte-source{position:absolute;outline:none;resize:none}div.rui-rte-status{font-size:90%;height:1.4em;padding:0 .5em;color:#888;background:#eee;border:1px solid #ccc;border-top:none;border-radius:0 0 .25em .25em;-moz-border-radius:0 0 .25em .25em;-webkit-border-radius:0 0 .25em .25em}");
2673
+
2674
+ embed_style.type = 'text/css';
2675
+ document.getElementsByTagName('head')[0].appendChild(embed_style);
2676
+
2677
+ if(embed_style.styleSheet) {
2678
+ embed_style.styleSheet.cssText = embed_rules.nodeValue;
2679
+ } else {
2680
+ embed_style.appendChild(embed_rules);
2681
+ }
2682
+
2683
+
2684
+ return Rte;
2685
+ })(RightJS, document, window);