redactor-rails 0.4 → 0.4.1

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 (48) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +12 -2
  3. data/app/controller/redactor_rails/documents_controller.rb +9 -1
  4. data/app/controller/redactor_rails/pictures_controller.rb +9 -1
  5. data/lib/redactor-rails/helper.rb +1 -4
  6. data/lib/redactor-rails/orm/active_record.rb +1 -1
  7. data/lib/redactor-rails/version.rb +1 -1
  8. data/vendor/assets/javascripts/redactor-rails/index.js +1 -1
  9. data/vendor/assets/javascripts/redactor-rails/langs/ar.js +3 -2
  10. data/vendor/assets/javascripts/redactor-rails/langs/ba.js +3 -2
  11. data/vendor/assets/javascripts/redactor-rails/langs/bg.js +3 -2
  12. data/vendor/assets/javascripts/redactor-rails/langs/by.js +4 -3
  13. data/vendor/assets/javascripts/redactor-rails/langs/cs.js +3 -2
  14. data/vendor/assets/javascripts/redactor-rails/langs/da.js +4 -3
  15. data/vendor/assets/javascripts/redactor-rails/langs/de.js +4 -3
  16. data/vendor/assets/javascripts/redactor-rails/langs/en.js +3 -2
  17. data/vendor/assets/javascripts/redactor-rails/langs/eo.js +3 -2
  18. data/vendor/assets/javascripts/redactor-rails/langs/es.js +4 -3
  19. data/vendor/assets/javascripts/redactor-rails/langs/fa.js +3 -2
  20. data/vendor/assets/javascripts/redactor-rails/langs/fi.js +3 -2
  21. data/vendor/assets/javascripts/redactor-rails/langs/fr.js +4 -3
  22. data/vendor/assets/javascripts/redactor-rails/langs/hr.js +3 -2
  23. data/vendor/assets/javascripts/redactor-rails/langs/hu.js +3 -2
  24. data/vendor/assets/javascripts/redactor-rails/langs/id.js +3 -2
  25. data/vendor/assets/javascripts/redactor-rails/langs/it.js +3 -2
  26. data/vendor/assets/javascripts/redactor-rails/langs/ja.js +4 -3
  27. data/vendor/assets/javascripts/redactor-rails/langs/ko.js +3 -2
  28. data/vendor/assets/javascripts/redactor-rails/langs/lv.js +4 -3
  29. data/vendor/assets/javascripts/redactor-rails/langs/nl.js +3 -2
  30. data/vendor/assets/javascripts/redactor-rails/langs/pl.js +4 -3
  31. data/vendor/assets/javascripts/redactor-rails/langs/pt_br.js +3 -2
  32. data/vendor/assets/javascripts/redactor-rails/langs/ro.js +3 -2
  33. data/vendor/assets/javascripts/redactor-rails/langs/ru.js +3 -2
  34. data/vendor/assets/javascripts/redactor-rails/langs/sk.js +3 -2
  35. data/vendor/assets/javascripts/redactor-rails/langs/sq.js +3 -2
  36. data/vendor/assets/javascripts/redactor-rails/langs/sr-cir.js +3 -2
  37. data/vendor/assets/javascripts/redactor-rails/langs/sr-lat.js +3 -2
  38. data/vendor/assets/javascripts/redactor-rails/langs/sv.js +3 -2
  39. data/vendor/assets/javascripts/redactor-rails/langs/tr.js +3 -2
  40. data/vendor/assets/javascripts/redactor-rails/langs/ua.js +4 -3
  41. data/vendor/assets/javascripts/redactor-rails/langs/vi.js +3 -2
  42. data/vendor/assets/javascripts/redactor-rails/langs/zh_cn.js +3 -2
  43. data/vendor/assets/javascripts/redactor-rails/langs/zh_tw.js +74 -66
  44. data/vendor/assets/javascripts/redactor-rails/redactor.js +1114 -539
  45. data/vendor/assets/javascripts/redactor-rails/redactor.min.js +3 -3
  46. data/vendor/assets/stylesheets/redactor-rails/css/redactor-iframe.css +3 -2
  47. data/vendor/assets/stylesheets/redactor-rails/css/redactor.css +56 -43
  48. metadata +3 -3
@@ -1,6 +1,6 @@
1
1
  /*
2
- Redactor v9.0.4
3
- Updated: Jul 11, 2013
2
+ Redactor v9.1.4
3
+ Updated: Sep 10, 2013
4
4
 
5
5
  http://imperavi.com/redactor/
6
6
 
@@ -72,26 +72,9 @@
72
72
  }
73
73
 
74
74
  $.Redactor = Redactor;
75
- $.Redactor.VERSION = '9.0.4';
75
+ $.Redactor.VERSION = '9.1.4';
76
76
  $.Redactor.opts = {
77
77
 
78
- // callbacks
79
- initCallback: false,
80
- changeCallback: false,
81
- focusCallback: false,
82
- blurCallback: false,
83
- keydownCallback: false,
84
- keyupCallback: false,
85
- execCommandCallback: false,
86
- pasteBeforeCallback: false,
87
- pasteAfterCallback: false,
88
- autosaveCallback: false,
89
- imageUploadCallback: false,
90
- imageUploadErrorCallback: false,
91
- imageDeleteCallback: false,
92
- fileUploadCallback: false,
93
- fileUploadErrorCallback: false,
94
-
95
78
  // settings
96
79
  rangy: false,
97
80
 
@@ -107,7 +90,10 @@
107
90
  wym: false,
108
91
  mobile: true,
109
92
  cleanup: true,
93
+ tidyHtml: true,
94
+ pastePlainText: false,
110
95
  removeEmptyTags: true,
96
+ templateVars: false,
111
97
 
112
98
  visual: true,
113
99
  focus: false,
@@ -124,26 +110,36 @@
124
110
  linkAnchor: false,
125
111
  linkEmail: false,
126
112
  linkProtocol: 'http://',
113
+ linkNofollow: false,
127
114
 
115
+ imageFloatMargin: '10px',
128
116
  imageGetJson: false, // url (ex. /folder/images.json ) or false
129
117
 
130
118
  imageUpload: false, // url
131
119
  fileUpload: false, // url
120
+ clipboardUpload: true, // or false
121
+ clipboardUploadUrl: false, // url
122
+ dragUpload: true, // false
123
+
124
+ dnbImageTypes: ['image/png', 'image/jpeg', 'image/gif'], // or false
132
125
 
133
126
  s3: false,
134
127
  uploadFields: false,
135
128
 
136
129
  observeImages: true,
130
+ observeLinks: true,
137
131
 
138
132
  modalOverlay: true,
139
133
 
134
+ tabSpaces: false, // true or number of spaces
140
135
  tabFocus: true,
141
136
 
142
137
  air: false,
143
- airButtons: ['formatting', '|', 'bold', 'italic', 'deleted', '|', 'unorderedlist', 'orderedlist', 'outdent', 'indent', '|', 'fontcolor', 'backcolor'],
138
+ airButtons: ['formatting', '|', 'bold', 'italic', 'deleted', '|', 'unorderedlist', 'orderedlist', 'outdent', 'indent'],
144
139
 
145
140
  toolbar: true,
146
141
  toolbarFixed: false,
142
+ toolbarFixedTarget: document,
147
143
  toolbarFixedTopOffset: 0, // pixels
148
144
  toolbarFixedBox: false,
149
145
  toolbarExternal: false, // ID selector
@@ -153,8 +149,7 @@
153
149
 
154
150
  buttonsCustom: {},
155
151
  buttonsAdd: [],
156
- buttons: ['html', '|', 'formatting', '|', 'bold', 'italic', 'deleted', '|', 'unorderedlist', 'orderedlist', 'outdent', 'indent', '|', 'image', 'video', 'file', 'table', 'link', '|', 'fontcolor', 'backcolor', '|', 'alignment', '|', 'horizontalrule'], // 'underline', 'alignleft', 'aligncenter', 'alignright', 'justify'
157
- colors: ['#ffffff', '#000000', '#eeece1', '#1f497d', '#4f81bd', '#c0504d', '#9bbb59', '#8064a2', '#4bacc6', '#f79646', '#ffff00', '#f2f2f2', '#7f7f7f', '#ddd9c3', '#c6d9f0', '#dbe5f1', '#f2dcdb', '#ebf1dd', '#e5e0ec', '#dbeef3', '#fdeada', '#fff2ca', '#d8d8d8', '#595959', '#c4bd97', '#8db3e2', '#b8cce4', '#e5b9b7', '#d7e3bc', '#ccc1d9', '#b7dde8', '#fbd5b5', '#ffe694', '#bfbfbf', '#3f3f3f', '#938953', '#548dd4', '#95b3d7', '#d99694', '#c3d69b', '#b2a2c7', '#b7dde8', '#fac08f', '#f2c314', '#a5a5a5', '#262626', '#494429', '#17365d', '#366092', '#953734', '#76923c', '#5f497a', '#92cddc', '#e36c09', '#c09100', '#7f7f7f', '#0c0c0c', '#1d1b10', '#0f243e', '#244061', '#632423', '#4f6128', '#3f3151', '#31859b', '#974806', '#7f6000'],
152
+ buttons: ['html', '|', 'formatting', '|', 'bold', 'italic', 'deleted', '|', 'unorderedlist', 'orderedlist', 'outdent', 'indent', '|', 'image', 'video', 'file', 'table', 'link', '|', 'alignment', '|', 'horizontalrule'], // 'underline', 'alignleft', 'aligncenter', 'alignright', 'justify'
158
153
 
159
154
  activeButtons: ['deleted', 'italic', 'bold', 'underline', 'unorderedlist', 'orderedlist', 'alignleft', 'aligncenter', 'alignright', 'justify', 'table'],
160
155
  activeButtonsStates: {
@@ -173,12 +168,14 @@
173
168
  },
174
169
  activeButtonsAdd: false, // object, ex.: { tag: 'buttonName' }
175
170
 
176
- formattingTags: ['p', 'blockquote', 'pre', 'h1', 'h2', 'h3', 'h4'],
171
+ formattingTags: ['p', 'blockquote', 'pre', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6'],
177
172
 
178
173
  linebreaks: false,
179
174
  paragraphy: true,
180
175
  convertDivs: true,
181
176
  convertLinks: true,
177
+ convertImageLinks: false,
178
+ convertVideoLinks: false,
182
179
  formattingPre: false,
183
180
  phpTags: false,
184
181
 
@@ -204,7 +201,7 @@
204
201
  newLevel: ['blockquote', 'div', 'dl', 'fieldset', 'form', 'frameset', 'map', 'ol', 'p', 'pre', 'select', 'td', 'th', 'tr', 'ul'],
205
202
  blockLevelElements: ['P', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'DD', 'DL', 'DT', 'DIV', 'LI',
206
203
  'BLOCKQUOTE', 'OUTPUT', 'FIGCAPTION', 'PRE', 'ADDRESS', 'SECTION',
207
- 'HEADER', 'FOOTER', 'ASIDE', 'ARTICLE'],
204
+ 'HEADER', 'FOOTER', 'ASIDE', 'ARTICLE', 'TD'],
208
205
  // lang
209
206
  langs: {
210
207
  en: {
@@ -224,6 +221,7 @@
224
221
  header2: 'Header 2',
225
222
  header3: 'Header 3',
226
223
  header4: 'Header 4',
224
+ header5: 'Header 5',
227
225
  bold: 'Bold',
228
226
  italic: 'Italic',
229
227
  fontcolor: 'Font Color',
@@ -274,7 +272,8 @@
274
272
  link_new_tab: 'Open link in new tab',
275
273
  underline: 'Underline',
276
274
  alignment: 'Alignment',
277
- filename: 'Name (optional)'
275
+ filename: 'Name (optional)',
276
+ edit: 'Edit'
278
277
  }
279
278
  }
280
279
  };
@@ -282,8 +281,7 @@
282
281
  // Functionality
283
282
  Redactor.fn = $.Redactor.prototype = {
284
283
 
285
- keyCode:
286
- {
284
+ keyCode: {
287
285
  BACKSPACE: 8,
288
286
  DELETE: 46,
289
287
  DOWN: 40,
@@ -303,10 +301,13 @@
303
301
  this.$element = this.$source = $(el);
304
302
  this.uuid = uuid++;
305
303
 
304
+ // clonning options
305
+ var opts = $.extend(true, {}, $.Redactor.opts);
306
+
306
307
  // current settings
307
308
  this.opts = $.extend(
308
309
  {},
309
- $.Redactor.opts,
310
+ opts,
310
311
  this.$element.data(),
311
312
  options
312
313
  );
@@ -418,6 +419,12 @@
418
419
  title: lang.header4,
419
420
  func: 'formatBlocks',
420
421
  className: 'redactor_format_h4'
422
+ },
423
+ h5:
424
+ {
425
+ title: lang.header5,
426
+ func: 'formatBlocks',
427
+ className: 'redactor_format_h5'
421
428
  }
422
429
  }
423
430
  },
@@ -649,6 +656,7 @@
649
656
  clearInterval(this.autosaveInterval);
650
657
 
651
658
  $(window).off('.redactor');
659
+ this.$source.off('redactor-textarea');
652
660
  this.$element.off('.redactor').removeData('redactor');
653
661
 
654
662
  var html = this.get();
@@ -669,6 +677,11 @@
669
677
 
670
678
  $elem.removeClass('redactor_editor').removeClass('redactor_editor_wym').removeAttr('contenteditable').html(html).show();
671
679
  }
680
+
681
+ if (this.opts.air)
682
+ {
683
+ $('.redactor_air').remove();
684
+ }
672
685
  },
673
686
 
674
687
  // API GET
@@ -707,12 +720,14 @@
707
720
 
708
721
  return html;
709
722
  },
710
- set: function(html, strip)
723
+ set: function(html, strip, placeholderRemove)
711
724
  {
712
725
  html = html.toString();
713
726
 
714
727
  if (this.opts.fullpage) this.setCodeIframe(html);
715
728
  else this.setEditor(html, strip);
729
+
730
+ if (placeholderRemove !== false) this.placeholderRemove();
716
731
  },
717
732
  setEditor: function(html, strip)
718
733
  {
@@ -784,19 +799,14 @@
784
799
 
785
800
  if ($.trim(html) === '<br>') html = '';
786
801
 
787
- if (html !== '') html = this.cleanHtml(html);
802
+ if (html !== '' && this.opts.tidyHtml) html = this.cleanHtml(html);
803
+ html = html.replace(/<br>/gi, '<br />');
788
804
 
789
805
  // before callback
790
806
  html = this.callback('syncBefore', false, html);
791
807
 
792
808
  this.$source.val(html);
793
809
 
794
- // TMP:
795
- if (typeof htmlEncode != 'undefined')
796
- {
797
- $('#' + this.$element[0].id + '_code').html(htmlEncode(html));
798
- }
799
-
800
810
  // onchange & after callback
801
811
  this.callback('syncAfter', false, html);
802
812
 
@@ -820,23 +830,40 @@
820
830
  html = html.replace(/&#8203;/gi, '');
821
831
  html = html.replace(/&nbsp;/gi, ' ');
822
832
 
833
+ // link nofollow
834
+ if (this.opts.linkNofollow)
835
+ {
836
+ html = html.replace(/<a(.*?)rel="nofollow"(.*?)>/gi, '<a$1$2>');
837
+ html = html.replace(/<a(.*?)>/gi, '<a$1 rel="nofollow">');
838
+ }
839
+
823
840
  // php code fix
824
841
  html = html.replace('<!--?php', '<?php');
825
842
  html = html.replace('?-->', '?>');
826
843
 
827
-
828
- // Remove verified attr
829
844
  html = html.replace(/ data-tagblock=""/gi, '');
830
845
  html = html.replace(/<br\s?\/?>\n?<\/(P|H[1-6]|LI|ADDRESS|SECTION|HEADER|FOOTER|ASIDE|ARTICLE)>/gi, '</$1>');
831
846
 
847
+ // remove image resize
848
+ html = html.replace(/<span(.*?)id="redactor-image-box"(.*?)>([\w\W]*?)<img(.*?)><\/span>/i, '$3<img$4>');
849
+ html = html.replace(/<span(.*?)id="redactor-image-resizer"(.*?)>(.*?)<\/span>/i, '');
850
+ html = html.replace(/<span(.*?)id="redactor-image-editter"(.*?)>(.*?)<\/span>/i, '');
851
+
852
+ // remove spans
832
853
  html = html.replace(/<span\s*?>([\w\W]*?)<\/span>/gi, '$1');
833
854
  html = html.replace(/<span(.*?)data-redactor="verified"(.*?)>([\w\W]*?)<\/span>/gi, '<span$1$2>$3</span>');
834
855
  html = html.replace(/<span(.*?)data-redactor-inlineMethods=""(.*?)>([\w\W]*?)<\/span>/gi, '<span$1$2>$3</span>' );
835
856
  html = html.replace(/<span\s*?>([\w\W]*?)<\/span>/gi, '$1');
836
857
  html = html.replace(/<span\s*?id="selection-marker(.*?)"(.*?)>([\w\W]*?)<\/span>/gi, '');
837
858
  html = html.replace(/<span\s*?>([\w\W]*?)<\/span>/gi, '$1');
859
+ html = html.replace(/<span(.*?)data-redactor="verified"(.*?)>([\w\W]*?)<\/span>/gi, '<span$1$2>$3</span>');
860
+ html = html.replace(/<span(.*?)data-redactor-inlineMethods=""(.*?)>([\w\W]*?)<\/span>/gi, '<span$1$2>$3</span>' );
838
861
  html = html.replace(/<span>([\w\W]*?)<\/span>/gi, '$1');
839
862
 
863
+ // amp fix
864
+ html = html.replace(/;amp;/gi, ';');
865
+
866
+
840
867
  html = this.cleanReConvertProtected(html);
841
868
 
842
869
  return html;
@@ -935,7 +962,7 @@
935
962
  this.$source.attr('dir', this.opts.direction).hide();
936
963
 
937
964
  // set code
938
- this.set(this.content);
965
+ this.set(this.content, true, false);
939
966
  },
940
967
  buildOptions: function()
941
968
  {
@@ -1002,333 +1029,408 @@
1002
1029
  },
1003
1030
  buildBindKeyboard: function()
1004
1031
  {
1005
- var oldsafari = false;
1006
- if (this.browser('webkit') && navigator.userAgent.indexOf('Chrome') === -1)
1032
+ if (this.opts.dragUpload)
1007
1033
  {
1008
- var arr = this.browser('version').split('.');
1009
- if (arr[0] < 536) oldsafari = true;
1034
+ this.$editor.on('drop.redactor', $.proxy(this.buildEventDrop, this));
1010
1035
  }
1011
1036
 
1012
- this.$editor.on('paste.redactor', $.proxy(function(e)
1037
+ this.$editor.on('paste.redactor', $.proxy(this.buildEventPaste, this));
1038
+ this.$editor.on('keydown.redactor', $.proxy(this.buildEventKeydown, this));
1039
+ this.$editor.on('keyup.redactor', $.proxy(this.buildEventKeyup, this));
1040
+
1041
+
1042
+
1043
+ // textarea callback
1044
+ if ($.isFunction(this.opts.textareaKeydownCallback))
1013
1045
  {
1014
- if (oldsafari) return true;
1046
+ this.$source.on('keydown.redactor-textarea', $.proxy(this.opts.textareaKeydownCallback, this));
1047
+ }
1015
1048
 
1016
- // paste except opera
1017
- if (this.browser('opera')) return true;
1049
+ // focus callback
1050
+ if ($.isFunction(this.opts.focusCallback))
1051
+ {
1052
+ this.$editor.on('focus.redactor', $.proxy(this.opts.focusCallback, this));
1053
+ }
1018
1054
 
1019
- if (this.opts.cleanup)
1020
- {
1021
- rtePaste = true;
1055
+ // blur callback
1056
+ this.$editor.on('blur.redactor', $.proxy(function()
1057
+ {
1058
+ this.selectall = false;
1059
+ }, this));
1060
+ if ($.isFunction(this.opts.blurCallback))
1061
+ {
1062
+ this.$editor.on('blur.redactor', $.proxy(this.opts.blurCallback, this));
1063
+ }
1022
1064
 
1023
- this.selectionSave();
1065
+ },
1066
+ buildEventDrop: function(e)
1067
+ {
1068
+ e = e.originalEvent || e;
1024
1069
 
1025
- if (!this.selectall)
1026
- {
1027
- if (this.opts.autoresize === true )
1028
- {
1029
- this.$editor.height(this.$editor.height());
1030
- this.saveScroll = this.document.body.scrollTop;
1031
- }
1032
- else
1033
- {
1034
- this.saveScroll = this.$editor.scrollTop();
1035
- }
1036
- }
1070
+ if (window.FormData === undefined) return true;
1037
1071
 
1038
- var frag = this.extractContent();
1072
+ var length = e.dataTransfer.files.length;
1073
+ if (length == 0) return true;
1039
1074
 
1040
- setTimeout($.proxy(function()
1041
- {
1042
- var pastedFrag = this.extractContent();
1043
- this.$editor.append(frag);
1075
+ e.preventDefault();
1044
1076
 
1045
- this.selectionRestore();
1077
+ var file = e.dataTransfer.files[0];
1046
1078
 
1047
- var html = this.getFragmentHtml(pastedFrag);
1048
- this.pasteClean(html);
1079
+ if (this.opts.dnbImageTypes !== false && this.opts.dnbImageTypes.indexOf(file.type) == -1)
1080
+ {
1081
+ return true;
1082
+ }
1049
1083
 
1050
- if (this.opts.autoresize === true) this.$editor.css('height', 'auto');
1084
+ this.bufferSet();
1051
1085
 
1052
- }, this), 1);
1053
- }
1086
+ var progress = $('<div id="redactor-progress-drag" class="redactor-progress redactor-progress-striped"><div id="redactor-progress-bar" class="redactor-progress-bar" style="width: 100%;"></div></div>');
1087
+ $(document.body).append(progress);
1054
1088
 
1055
- }, this));
1089
+ this.dragUploadAjax(this.opts.imageUpload, file, true, progress, e);
1056
1090
 
1057
- this.$editor.on('keydown.redactor', $.proxy(function(e)
1091
+ },
1092
+ buildEventPaste: function(e)
1093
+ {
1094
+ var oldsafari = false;
1095
+ if (this.browser('webkit') && navigator.userAgent.indexOf('Chrome') === -1)
1058
1096
  {
1059
- if (rtePaste) return false;
1060
-
1061
- var key = e.which;
1062
- var ctrl = e.ctrlKey || e.metaKey;
1063
- var parent = this.getParent();
1064
- var current = this.getCurrent();
1065
- var block = this.getBlock();
1066
- var pre = false;
1097
+ var arr = this.browser('version').split('.');
1098
+ if (arr[0] < 536) oldsafari = true;
1099
+ }
1067
1100
 
1068
- this.callback('keydown', e);
1101
+ if (oldsafari) return true;
1069
1102
 
1070
- // pre & down
1071
- if ((parent && $(parent).get(0).tagName === 'PRE') || (current && $(current).get(0).tagName === 'PRE'))
1072
- {
1073
- pre = true;
1074
- if (key === this.keyCode.DOWN) this.insertAfterLastElement(block);
1075
- }
1103
+ // paste except opera (not webkit)
1104
+ if (this.browser('opera')) return true;
1076
1105
 
1077
- // down
1078
- if (key === this.keyCode.DOWN)
1079
- {
1080
- if (parent && $(parent).get(0).tagName === 'BLOCKQUOTE') this.insertAfterLastElement(parent);
1081
- if (current && $(current).get(0).tagName === 'BLOCKQUOTE') this.insertAfterLastElement(current);
1082
- }
1106
+ // clipboard upload
1107
+ if (this.opts.clipboardUpload && this.buildEventClipboardUpload(e)) return true;
1083
1108
 
1084
- // shortcuts setup
1085
- if (ctrl && !e.shiftKey) this.shortcuts(e, key);
1109
+ if (this.opts.cleanup)
1110
+ {
1111
+ rtePaste = true;
1086
1112
 
1087
- // buffer setup
1088
- if (ctrl && key === 90 && !e.shiftKey && !e.altKey) // z key
1089
- {
1090
- e.preventDefault();
1091
- if (this.opts.buffer.length) this.bufferUndo();
1092
- else this.document.execCommand('undo', false, false);
1093
- return;
1094
- }
1095
- // undo
1096
- else if (ctrl && key === 90 && e.shiftKey && !e.altKey)
1097
- {
1098
- e.preventDefault();
1099
- if (this.opts.rebuffer.length != 0) this.bufferRedo();
1100
- else this.document.execCommand('redo', false, false);
1101
- return;
1102
- }
1113
+ this.selectionSave();
1103
1114
 
1104
- // select all
1105
- if (ctrl && key === 65)
1115
+ if (!this.selectall)
1106
1116
  {
1107
- this.selectall = true;
1108
- }
1109
- else if (key != this.keyCode.LEFT_WIN && !ctrl)
1110
- {
1111
- this.selectall = false;
1117
+ if (this.opts.autoresize === true )
1118
+ {
1119
+ this.$editor.height(this.$editor.height());
1120
+ this.saveScroll = this.document.body.scrollTop;
1121
+ }
1122
+ else
1123
+ {
1124
+ this.saveScroll = this.$editor.scrollTop();
1125
+ }
1112
1126
  }
1113
1127
 
1128
+ var frag = this.extractContent();
1114
1129
 
1115
- // enter
1116
- if (key == this.keyCode.ENTER && !e.shiftKey && !e.ctrlKey && !e.metaKey )
1130
+ setTimeout($.proxy(function()
1117
1131
  {
1118
- // In ie, opera in the tables are created paragraphs, fix it.
1119
- if (parent.nodeType == 1 && (parent.tagName == 'TD' || parent.tagName == 'TH'))
1120
- {
1121
- this.bufferSet();
1132
+ var pastedFrag = this.extractContent();
1133
+ this.$editor.append(frag);
1122
1134
 
1123
- this.insertNode(document.createElement('br'));
1124
- e.preventDefault();
1125
- return false;
1126
- }
1135
+ this.selectionRestore();
1127
1136
 
1128
- // pre
1129
- if (pre === true)
1130
- {
1131
- this.bufferSet();
1132
- e.preventDefault();
1137
+ var html = this.getFragmentHtml(pastedFrag);
1138
+ this.pasteClean(html);
1133
1139
 
1134
- var html = $(current).parent().text();
1135
- this.insertNode(document.createTextNode('\n'));
1136
- if (html.search(/\s$/) == -1)
1137
- {
1138
- this.insertNode(document.createTextNode('\n'));
1139
- }
1140
+ if (this.opts.autoresize === true) this.$editor.css('height', 'auto');
1140
1141
 
1141
- this.sync();
1142
+ }, this), 1);
1143
+ }
1144
+ },
1145
+ buildEventClipboardUpload: function(e)
1146
+ {
1147
+ var event = e.originalEvent || e;
1148
+ this.clipboardFilePaste = false;
1142
1149
 
1143
- return false;
1144
- }
1145
- else
1146
- {
1147
- if (!this.opts.linebreaks)
1148
- {
1149
- // replace div to p
1150
- if (block && this.opts.rBlockTest.test(block.tagName))
1151
- {
1152
- // hit enter
1153
- this.bufferSet();
1150
+ if (typeof(event.clipboardData) === 'undefined') return false;
1151
+ if (event.clipboardData.items)
1152
+ {
1153
+ var file = event.clipboardData.items[0].getAsFile();
1154
+ if (file !== null)
1155
+ {
1156
+ this.bufferSet();
1157
+ this.clipboardFilePaste = true;
1154
1158
 
1155
- setTimeout($.proxy(function()
1156
- {
1157
- var blockElem = this.getBlock();
1158
- if (blockElem.tagName === 'DIV' && !$(blockElem).hasClass('redactor_editor'))
1159
- {
1160
- var node = $('<p>' + this.opts.invisibleSpace + '</p>');
1161
- $(blockElem).replaceWith(node);
1162
- this.selectionStart(node);
1163
- }
1159
+ var reader = new FileReader();
1160
+ reader.onload = $.proxy(this.pasteClipboardUpload, this);
1161
+ reader.readAsDataURL(file);
1164
1162
 
1163
+ return true;
1164
+ }
1165
+ }
1165
1166
 
1166
- }, this), 1);
1167
- }
1168
- else if (block === false)
1169
- {
1170
- // hit enter
1171
- this.bufferSet();
1167
+ return false;
1172
1168
 
1173
- var node = $('<p>' + this.opts.invisibleSpace + '</p>');
1174
- this.insertNode(node[0]);
1175
- this.selectionStart(node);
1176
- return false;
1177
- }
1169
+ },
1170
+ buildEventKeydown: function(e)
1171
+ {
1172
+ if (rtePaste) return false;
1178
1173
 
1179
- }
1174
+ var key = e.which;
1175
+ var ctrl = e.ctrlKey || e.metaKey;
1176
+ var parent = this.getParent();
1177
+ var current = this.getCurrent();
1178
+ var block = this.getBlock();
1179
+ var pre = false;
1180
1180
 
1181
- if (this.opts.linebreaks)
1182
- {
1183
- // replace div to br
1184
- if (block && this.opts.rBlockTest.test(block.tagName))
1185
- {
1186
- // hit enter
1187
- this.bufferSet();
1181
+ this.callback('keydown', e);
1188
1182
 
1189
- setTimeout($.proxy(function()
1190
- {
1191
- var blockElem = this.getBlock();
1192
- if ((blockElem.tagName === 'DIV' || blockElem.tagName === 'P') && !$(blockElem).hasClass('redactor_editor'))
1193
- {
1194
- this.replaceLineBreak(blockElem);
1195
- }
1183
+ this.imageResizeHide(false);
1196
1184
 
1197
- }, this), 1);
1198
- }
1199
- else
1200
- {
1201
- // hit enter
1202
- this.bufferSet();
1185
+ // pre & down
1186
+ if ((parent && $(parent).get(0).tagName === 'PRE') || (current && $(current).get(0).tagName === 'PRE'))
1187
+ {
1188
+ pre = true;
1189
+ if (key === this.keyCode.DOWN) this.insertAfterLastElement(block);
1190
+ }
1203
1191
 
1204
- this.insertLineBreak();
1205
- e.preventDefault();
1206
- return;
1207
- }
1208
- }
1192
+ // down
1193
+ if (key === this.keyCode.DOWN)
1194
+ {
1195
+ if (parent && $(parent).get(0).tagName === 'BLOCKQUOTE') this.insertAfterLastElement(parent);
1196
+ if (current && $(current).get(0).tagName === 'BLOCKQUOTE') this.insertAfterLastElement(current);
1197
+ }
1209
1198
 
1210
- // blockquote, figcaption
1211
- if (block.tagName == 'BLOCKQUOTE'
1212
- || block.tagName == 'FIGCAPTION')
1213
- {
1214
- // hit enter
1215
- this.bufferSet();
1199
+ // shortcuts setup
1200
+ if (ctrl && !e.shiftKey) this.shortcuts(e, key);
1216
1201
 
1217
- this.insertLineBreak();
1218
- e.preventDefault();
1219
- return;
1220
- }
1202
+ // buffer setup
1203
+ if (ctrl && key === 90 && !e.shiftKey && !e.altKey) // z key
1204
+ {
1205
+ e.preventDefault();
1206
+ if (this.opts.buffer.length) this.bufferUndo();
1207
+ else this.document.execCommand('undo', false, false);
1208
+ return;
1209
+ }
1210
+ // undo
1211
+ else if (ctrl && key === 90 && e.shiftKey && !e.altKey)
1212
+ {
1213
+ e.preventDefault();
1214
+ if (this.opts.rebuffer.length != 0) this.bufferRedo();
1215
+ else this.document.execCommand('redo', false, false);
1216
+ return;
1217
+ }
1221
1218
 
1222
- }
1223
- }
1224
- else if (key === this.keyCode.ENTER && (e.ctrlKey || e.shiftKey)) // Shift+Enter or Ctrl+Enter
1225
- {
1226
- this.bufferSet();
1219
+ // select all
1220
+ if (ctrl && key === 65) this.selectall = true;
1221
+ else if (key != this.keyCode.LEFT_WIN && !ctrl) this.selectall = false;
1227
1222
 
1223
+ // enter
1224
+ if (key == this.keyCode.ENTER && !e.shiftKey && !e.ctrlKey && !e.metaKey )
1225
+ {
1226
+ // In ie, opera in the tables are created paragraphs, fix it.
1227
+ if (parent.nodeType == 1 && (parent.tagName == 'TD' || parent.tagName == 'TH'))
1228
+ {
1228
1229
  e.preventDefault();
1229
- this.insertLineBreak();
1230
+ this.bufferSet();
1231
+ this.insertNode(document.createElement('br'));
1232
+ this.callback('enter', e);
1233
+ return false;
1230
1234
  }
1231
1235
 
1232
- // tab
1233
- if (key === this.keyCode.TAB && this.opts.shortcuts )
1236
+ // pre
1237
+ if (pre === true)
1234
1238
  {
1235
- if (!this.opts.tabFocus) return true;
1236
- if (this.isEmpty(this.get())) return true;
1237
-
1238
1239
  e.preventDefault();
1239
-
1240
- if (pre === true && !e.shiftKey)
1241
- {
1242
- this.bufferSet();
1243
- this.insertNode(document.createTextNode('\t'));
1244
- this.sync();
1245
- return false;
1246
- }
1247
- else
1240
+ this.bufferSet();
1241
+ var html = $(current).parent().text();
1242
+ this.insertNode(document.createTextNode('\n'));
1243
+ if (html.search(/\s$/) == -1)
1248
1244
  {
1249
- if (!e.shiftKey) this.indentingIndent();
1250
- else this.indentingOutdent();
1245
+ this.insertNode(document.createTextNode('\n'));
1251
1246
  }
1252
1247
 
1248
+ this.sync();
1249
+ this.callback('enter', e);
1253
1250
  return false;
1254
1251
  }
1255
-
1256
- // delete zero-width space before the removing
1257
- if (key === this.keyCode.BACKSPACE)
1252
+ else
1258
1253
  {
1259
- if (typeof current.tagName !== 'undefined' && /^(H[1-6])$/i.test(current.tagName))
1254
+ if (!this.opts.linebreaks)
1260
1255
  {
1261
- var node;
1262
- if (this.opts.linebreaks === false) node = $('<p>' + this.opts.invisibleSpace + '</p>');
1263
- else node = $('<br>' + this.opts.invisibleSpace);
1256
+ // replace div to p
1257
+ if (block && this.opts.rBlockTest.test(block.tagName))
1258
+ {
1259
+ // hit enter
1260
+ this.bufferSet();
1261
+
1262
+ setTimeout($.proxy(function()
1263
+ {
1264
+ var blockElem = this.getBlock();
1265
+ if (blockElem.tagName === 'DIV' && !$(blockElem).hasClass('redactor_editor'))
1266
+ {
1267
+ var node = $('<p>' + this.opts.invisibleSpace + '</p>');
1268
+ $(blockElem).replaceWith(node);
1269
+ this.selectionStart(node);
1270
+ }
1271
+
1272
+ }, this), 1);
1273
+ }
1274
+ else if (block === false)
1275
+ {
1276
+ // hit enter
1277
+ this.bufferSet();
1278
+
1279
+ var node = $('<p>' + this.opts.invisibleSpace + '</p>');
1280
+ this.insertNode(node[0]);
1281
+ this.selectionStart(node);
1282
+ this.callback('enter', e);
1283
+ return false;
1284
+ }
1264
1285
 
1265
- $(current).replaceWith(node);
1266
- this.selectionStart(node);
1267
1286
  }
1268
1287
 
1269
- if (typeof current.nodeValue !== 'undefined' && current.nodeValue !== null)
1288
+ if (this.opts.linebreaks)
1270
1289
  {
1271
- var value = $.trim(current.nodeValue.replace(/[^\u0000-~]/g, ''));
1272
- if (current.remove && current.nodeType === 3 && current.nodeValue.charCodeAt(0) == 8203 && value == '')
1290
+ // replace div to br
1291
+ if (block && this.opts.rBlockTest.test(block.tagName))
1273
1292
  {
1274
- current.remove();
1293
+ // hit enter
1294
+ this.bufferSet();
1295
+
1296
+ setTimeout($.proxy(function()
1297
+ {
1298
+ var blockElem = this.getBlock();
1299
+ if ((blockElem.tagName === 'DIV' || blockElem.tagName === 'P') && !$(blockElem).hasClass('redactor_editor'))
1300
+ {
1301
+ this.replaceLineBreak(blockElem);
1302
+ }
1303
+
1304
+ }, this), 1);
1305
+ }
1306
+ else
1307
+ {
1308
+ return this.buildEventKeydownInsertLineBreak(e);
1275
1309
  }
1276
1310
  }
1311
+
1312
+ // blockquote, figcaption
1313
+ if (block.tagName == 'BLOCKQUOTE' || block.tagName == 'FIGCAPTION')
1314
+ {
1315
+ return this.buildEventKeydownInsertLineBreak(e);
1316
+ }
1317
+
1277
1318
  }
1278
1319
 
1279
- }, this));
1320
+ this.callback('enter', e);
1321
+ }
1322
+ else if (key === this.keyCode.ENTER && (e.ctrlKey || e.shiftKey)) // Shift+Enter or Ctrl+Enter
1323
+ {
1324
+ this.bufferSet();
1280
1325
 
1281
- this.$editor.on('keyup.redactor', $.proxy(function(e)
1326
+ e.preventDefault();
1327
+ this.insertLineBreak();
1328
+ }
1329
+
1330
+ // tab
1331
+ if (key === this.keyCode.TAB && this.opts.shortcuts )
1282
1332
  {
1283
- if (rtePaste) return false;
1333
+ if (!this.opts.tabFocus) return true;
1334
+ if (this.isEmpty(this.get())) return true;
1284
1335
 
1285
- var key = e.which;
1286
- var parent = this.getParent();
1287
- var current = this.getCurrent();
1336
+ e.preventDefault();
1288
1337
 
1289
- // replace to p before / after the table or body
1290
- if (!this.opts.linebreaks && current.nodeType == 3 && (parent == false || parent.tagName == 'BODY'))
1338
+ if (pre === true && !e.shiftKey)
1291
1339
  {
1292
- var node = $('<p>').append($(current).clone());
1293
- $(current).replaceWith(node);
1294
- var next = $(node).next();
1295
- if (typeof(next[0]) !== 'undefined' && next[0].tagName == 'BR')
1296
- {
1297
- next.remove();
1298
- }
1299
- this.selectionEnd(node);
1300
- }
1340
+ this.bufferSet();
1341
+ this.insertNode(document.createTextNode('\t'));
1342
+ this.sync();
1343
+ return false;
1301
1344
 
1302
- // convert links
1303
- if (this.opts.convertLinks && key === this.keyCode.ENTER)
1345
+ }
1346
+ else if (this.opts.tabSpaces !== false)
1347
+ {
1348
+ this.bufferSet();
1349
+ this.insertNode(document.createTextNode(Array(this.opts.tabSpaces + 1).join('\u00a0')));
1350
+ this.sync();
1351
+ return false;
1352
+ }
1353
+ else
1304
1354
  {
1305
- this.formatLinkify(this.opts.linkProtocol);
1355
+ if (!e.shiftKey) this.indentingIndent();
1356
+ else this.indentingOutdent();
1306
1357
  }
1307
1358
 
1308
- // if empty
1309
- if (this.opts.linebreaks === false && (key === this.keyCode.DELETE || key === this.keyCode.BACKSPACE))
1359
+ return false;
1360
+ }
1361
+
1362
+ // delete zero-width space before the removing
1363
+ if (key === this.keyCode.BACKSPACE)
1364
+ {
1365
+ if (typeof current.tagName !== 'undefined' && /^(H[1-6])$/i.test(current.tagName))
1310
1366
  {
1311
- return this.formatEmpty(e);
1367
+ var node;
1368
+ if (this.opts.linebreaks === false) node = $('<p>' + this.opts.invisibleSpace + '</p>');
1369
+ else node = $('<br>' + this.opts.invisibleSpace);
1370
+
1371
+ $(current).replaceWith(node);
1372
+ this.selectionStart(node);
1312
1373
  }
1313
1374
 
1314
- this.callback('keyup', e);
1315
- this.sync();
1375
+ if (typeof current.nodeValue !== 'undefined' && current.nodeValue !== null)
1376
+ {
1377
+ var value = $.trim(current.nodeValue.replace(/[^\u0000-\u1C7F]/g, ''));
1378
+ if (current.remove && current.nodeType === 3 && current.nodeValue.charCodeAt(0) == 8203 && value == '')
1379
+ {
1380
+ current.remove();
1381
+ }
1382
+ }
1383
+ }
1384
+ },
1385
+ buildEventKeydownInsertLineBreak: function(e)
1386
+ {
1387
+ this.bufferSet();
1388
+ e.preventDefault();
1389
+ this.insertLineBreak();
1390
+ this.callback('enter', e);
1391
+ return;
1392
+ },
1393
+ buildEventKeyup: function(e)
1394
+ {
1395
+ if (rtePaste) return false;
1316
1396
 
1317
- }, this));
1397
+ var key = e.which;
1398
+ var parent = this.getParent();
1399
+ var current = this.getCurrent();
1318
1400
 
1401
+ // replace to p before / after the table or body
1402
+ if (!this.opts.linebreaks && current.nodeType == 3 && (parent == false || parent.tagName == 'BODY'))
1403
+ {
1404
+ var node = $('<p>').append($(current).clone());
1405
+ $(current).replaceWith(node);
1406
+ var next = $(node).next();
1407
+ if (typeof(next[0]) !== 'undefined' && next[0].tagName == 'BR')
1408
+ {
1409
+ next.remove();
1410
+ }
1411
+ this.selectionEnd(node);
1412
+ }
1319
1413
 
1320
- // focus callback
1321
- if ($.isFunction(this.opts.focusCallback))
1414
+ // convert links
1415
+ if ((this.opts.convertLinks || this.opts.convertImageLinks || this.opts.convertVideoLinks) && key === this.keyCode.ENTER)
1322
1416
  {
1323
- this.$editor.on('focus.redactor', $.proxy(this.opts.focusCallback, this));
1417
+ this.formatLinkify(this.opts.linkProtocol, this.opts.convertLinks, this.opts.convertImageLinks, this.opts.convertVideoLinks);
1418
+
1419
+ setTimeout($.proxy(function()
1420
+ {
1421
+ if (this.opts.convertImageLinks) this.observeImages();
1422
+ if (this.opts.observeLinks) this.observeLinks();
1423
+ }, this), 5);
1324
1424
  }
1325
1425
 
1326
- // blur callback
1327
- if ($.isFunction(this.opts.blurCallback))
1426
+ // if empty
1427
+ if (this.opts.linebreaks === false && (key === this.keyCode.DELETE || key === this.keyCode.BACKSPACE))
1328
1428
  {
1329
- this.$editor.on('blur.redactor', $.proxy(this.opts.blurCallback, this));
1429
+ return this.formatEmpty(e);
1330
1430
  }
1331
1431
 
1432
+ this.callback('keyup', e);
1433
+ this.sync();
1332
1434
  },
1333
1435
  buildPlugins: function()
1334
1436
  {
@@ -1434,7 +1536,7 @@
1434
1536
  this.iframeAddCss();
1435
1537
 
1436
1538
  if (this.opts.fullpage) this.setFullpageOnInit(this.$editor.html());
1437
- else this.set(this.content);
1539
+ else this.set(this.content, true, false);
1438
1540
 
1439
1541
  this.buildOptions();
1440
1542
  this.buildAfter();
@@ -1583,7 +1685,7 @@
1583
1685
  this.$source.height(height).show().focus();
1584
1686
 
1585
1687
  // textarea indenting
1586
- this.$source.on('keydown.redactor-textarea', function (e)
1688
+ this.$source.on('keydown.redactor-textarea-indenting', function (e)
1587
1689
  {
1588
1690
  if (e.keyCode === 9)
1589
1691
  {
@@ -1625,7 +1727,7 @@
1625
1727
 
1626
1728
  if (this.opts.fullpage ) this.$editor.attr('contenteditable', true );
1627
1729
 
1628
- this.$source.off('keydown.redactor-textarea');
1730
+ this.$source.off('keydown.redactor-textarea-indenting');
1629
1731
 
1630
1732
  this.$editor.focus();
1631
1733
  this.selectionRestore();
@@ -1721,8 +1823,6 @@
1721
1823
 
1722
1824
  $.each(this.opts.buttons, $.proxy(function(i, btnName)
1723
1825
  {
1724
-
1725
-
1726
1826
  // separator
1727
1827
  if ( btnName === '|' ) this.$toolbar.append($(this.opts.buttonSeparator));
1728
1828
  else if(this.opts.toolbar[btnName])
@@ -1740,7 +1840,7 @@
1740
1840
  if (this.opts.toolbarFixed)
1741
1841
  {
1742
1842
  this.toolbarObserveScroll();
1743
- $(document).on('scroll.redactor', $.proxy(this.toolbarObserveScroll, this));
1843
+ $(this.opts.toolbarFixedTarget).on('scroll.redactor', $.proxy(this.toolbarObserveScroll, this));
1744
1844
  }
1745
1845
 
1746
1846
  // buttons response
@@ -1752,7 +1852,7 @@
1752
1852
  },
1753
1853
  toolbarObserveScroll: function()
1754
1854
  {
1755
- var scrollTop = $(this.document).scrollTop();
1855
+ var scrollTop = $(this.opts.toolbarFixedTarget).scrollTop();
1756
1856
  var boxTop = this.$box.offset().top;
1757
1857
  var left = 0;
1758
1858
 
@@ -1909,53 +2009,6 @@
1909
2009
  if (this.opts.iframe) hideHandler(this.document);
1910
2010
  },
1911
2011
 
1912
- // COLORPICKER
1913
- pickerBuild: function($dropdown, key)
1914
- {
1915
- $dropdown.width(210);
1916
-
1917
- var rule = 'color';
1918
- if (key === 'backcolor') rule = 'background-color';
1919
-
1920
- var len = this.opts.colors.length;
1921
- var _self = this;
1922
- for (var i = 0; i < len; i++)
1923
- {
1924
- var color = this.opts.colors[i];
1925
-
1926
- var $swatch = $('<a rel="' + color + '" href="javascript:;" class="redactor_color_link"></a>').css({ 'backgroundColor': color });
1927
- $dropdown.append($swatch);
1928
-
1929
- $swatch.on('click', function()
1930
- {
1931
- var type = $(this).attr('rel');
1932
- if (key === 'backcolor') type = $(this).css('background-color');
1933
-
1934
- _self.pickerSet(rule, type);
1935
- });
1936
- }
1937
-
1938
- var $elNone = $('<a href="javascript:;" class="redactor_color_none"></a>')
1939
- .html(this.opts.curLang.none)
1940
- .on('click', function()
1941
- {
1942
- _self.pickerSet(rule, false);
1943
- });
1944
-
1945
- $dropdown.append($elNone);
1946
- },
1947
-
1948
- pickerSet: function(rule, type)
1949
- {
1950
- this.bufferSet();
1951
-
1952
- this.$editor.focus();
1953
- this.inlineRemoveStyle(rule);
1954
- if (type !== false) this.inlineSetStyle(rule, type);
1955
- if (this.opts.air) this.$air.fadeOut(100);
1956
- this.sync();
1957
- },
1958
-
1959
2012
  // DROPDOWNS
1960
2013
  dropdownBuild: function($dropdown, dropdownObject)
1961
2014
  {
@@ -1967,7 +2020,7 @@
1967
2020
  if (btnObject.name === 'separator') $item = $('<a class="redactor_separator_drop">');
1968
2021
  else
1969
2022
  {
1970
- $item = $('<a href="javascript:;" class="' + btnObject.className + ' redactor_dropdown_' + btnName + '">' + btnObject.title + '</a>');
2023
+ $item = $('<a href="#" class="' + btnObject.className + ' redactor_dropdown_' + btnName + '">' + btnObject.title + '</a>');
1971
2024
  $item.on('click', $.proxy(function(e)
1972
2025
  {
1973
2026
  if (e.preventDefault) e.preventDefault();
@@ -1987,7 +2040,7 @@
1987
2040
 
1988
2041
  }, this));
1989
2042
  },
1990
- dropdownShow: function (e, $dropdown, key)
2043
+ dropdownShow: function(e, key)
1991
2044
  {
1992
2045
  if (!this.opts.visual)
1993
2046
  {
@@ -1995,30 +2048,43 @@
1995
2048
  return false;
1996
2049
  }
1997
2050
 
1998
- if (this.buttonGet(key).hasClass('dropact')) this.dropdownHideAll();
2051
+ var $dropdown = this.$toolbar.find('.redactor_dropdown_box_' + key);
2052
+ var $button = this.buttonGet(key);
2053
+
2054
+ if ($button.hasClass('dropact')) this.dropdownHideAll();
1999
2055
  else
2000
2056
  {
2001
2057
  this.dropdownHideAll();
2002
2058
 
2003
2059
  this.buttonActive(key);
2004
- this.buttonGet(key).addClass('dropact');
2005
-
2006
- var keyPosition = this.buttonGet(key).position(), left = keyPosition.left + 'px', btnHeight = 29;
2060
+ $button.addClass('dropact');
2007
2061
 
2008
- if (this.opts.air)
2062
+ var keyPosition = $button.position();
2063
+ if (this.toolbarFixed)
2009
2064
  {
2010
- $dropdown.css({ position: 'absolute', left: left, top: btnHeight + 'px' }).show();
2065
+ keyPosition = $button.offset();
2011
2066
  }
2012
- else if (this.opts.toolbarFixed && this.toolbarFixed)
2013
- {
2014
- $dropdown.css({ position: 'fixed', left: left, top: btnHeight + 'px' }).show();
2015
- }
2016
- else
2067
+
2068
+ // fix right placement
2069
+ var dropdownWidth = $dropdown.width();
2070
+ if ((keyPosition.left + dropdownWidth) > $(document).width())
2017
2071
  {
2018
- $dropdown.css({ position: 'absolute', left: left, top: keyPosition.top + btnHeight + 'px' }).show();
2072
+ keyPosition.left -= dropdownWidth;
2019
2073
  }
2074
+
2075
+ var left = keyPosition.left + 'px';
2076
+ var btnHeight = 29;
2077
+
2078
+ var position = 'absolute';
2079
+ var top = btnHeight + 'px';
2080
+
2081
+ if (this.opts.toolbarFixed && this.toolbarFixed) position = 'fixed';
2082
+ else if (!this.opts.air) top = keyPosition.top + btnHeight + 'px';
2083
+
2084
+ $dropdown.css({ position: position, left: left, top: top }).show();
2020
2085
  }
2021
2086
 
2087
+
2022
2088
  var hdlHideDropDown = $.proxy(function(e)
2023
2089
  {
2024
2090
  this.dropdownHide(e, $dropdown);
@@ -2048,7 +2114,6 @@
2048
2114
  buttonBuild: function(btnName, btnObject)
2049
2115
  {
2050
2116
  var $button = $('<a href="javascript:;" title="' + btnObject.title + '" class="redactor_btn redactor_btn_' + btnName + '"></a>');
2051
- var $dropdown = $('<div class="redactor_dropdown" style="display: none;">');
2052
2117
 
2053
2118
  $button.on('click', $.proxy(function(e)
2054
2119
  {
@@ -2081,9 +2146,9 @@
2081
2146
  this.airBindMousemoveHide();
2082
2147
 
2083
2148
  }
2084
- else if (btnName === 'backcolor' || btnName === 'fontcolor' || btnObject.dropdown)
2149
+ else if (btnObject.dropdown)
2085
2150
  {
2086
- this.dropdownShow(e, $dropdown, btnName);
2151
+ this.dropdownShow(e, btnName);
2087
2152
  }
2088
2153
 
2089
2154
  this.buttonActiveObserver(false, btnName);
@@ -2091,12 +2156,11 @@
2091
2156
  }, this));
2092
2157
 
2093
2158
  // dropdown
2094
- if (btnName === 'backcolor' || btnName === 'fontcolor' || btnObject.dropdown)
2159
+ if (btnObject.dropdown)
2095
2160
  {
2161
+ var $dropdown = $('<div class="redactor_dropdown redactor_dropdown_box_' + btnName + '" style="display: none;">');
2096
2162
  $dropdown.appendTo(this.$toolbar);
2097
-
2098
- if ( btnName === 'backcolor' || btnName === 'fontcolor') this.pickerBuild($dropdown, btnName);
2099
- else this.dropdownBuild($dropdown, btnObject.dropdown);
2163
+ this.dropdownBuild($dropdown, btnObject.dropdown);
2100
2164
  }
2101
2165
 
2102
2166
  return $button;
@@ -2192,14 +2256,18 @@
2192
2256
  if (!this.opts.toolbar) return;
2193
2257
  var btn = this.buttonBuild(key, { title: title, callback: callback, dropdown: dropdown });
2194
2258
  var $btn = this.buttonGet(afterkey);
2195
- $btn.parent().after($('<li>').append(btn));
2259
+
2260
+ if ($btn.size() !== 0) $btn.parent().after($('<li>').append(btn));
2261
+ else this.$toolbar.append($('<li>').append(btn));
2196
2262
  },
2197
2263
  buttonAddBefore: function(beforekey, key, title, callback, dropdown)
2198
2264
  {
2199
2265
  if (!this.opts.toolbar) return;
2200
2266
  var btn = this.buttonBuild(key, { title: title, callback: callback, dropdown: dropdown });
2201
2267
  var $btn = this.buttonGet(beforekey);
2202
- $btn.parent().before($('<li>').append(btn));
2268
+
2269
+ if ($btn.size() !== 0) $btn.parent().before($('<li>').append(btn));
2270
+ else this.$toolbar.append($('<li>').append(btn));
2203
2271
  },
2204
2272
  buttonRemove: function (key, separator)
2205
2273
  {
@@ -2215,7 +2283,10 @@
2215
2283
 
2216
2284
  if (e === false && btnName !== 'html')
2217
2285
  {
2218
- this.buttonActiveToggle(btnName);
2286
+ if ($.inArray(btnName, this.opts.activeButtons) != -1)
2287
+ {
2288
+ this.buttonActiveToggle(btnName);
2289
+ }
2219
2290
  return;
2220
2291
  }
2221
2292
 
@@ -2372,7 +2443,7 @@
2372
2443
  this.document.execCommand(cmd);
2373
2444
 
2374
2445
  var parent = this.getParent();
2375
- var $list = $(parent).parents('ol, ul');
2446
+ var $list = $(parent).closest('ol, ul');
2376
2447
 
2377
2448
  if ($list.length)
2378
2449
  {
@@ -2399,14 +2470,14 @@
2399
2470
  return;
2400
2471
  }
2401
2472
 
2402
- if (cmd === 'unlink' )
2473
+ if (cmd === 'unlink')
2403
2474
  {
2404
2475
  this.bufferSet();
2405
2476
 
2406
- var parent = this.getParent();
2407
- if (parent && $(parent)[0].tagName === 'A')
2477
+ var link = this.currentOrParentIs('A');
2478
+ if (link)
2408
2479
  {
2409
- $(parent).replaceWith($(parent).text());
2480
+ $(link).replaceWith($(link).text());
2410
2481
 
2411
2482
  this.sync();
2412
2483
  this.callback('execCommand', cmd, param);
@@ -2488,6 +2559,8 @@
2488
2559
  {
2489
2560
  var $el = false;
2490
2561
 
2562
+ if (elem.tagName === 'TD') return;
2563
+
2491
2564
  if ($.inArray(elem.tagName, this.opts.alignmentTags) !== -1)
2492
2565
  {
2493
2566
  $el = $(elem);
@@ -2615,6 +2688,12 @@
2615
2688
  },
2616
2689
  cleanConvertProtected: function(html)
2617
2690
  {
2691
+ if (this.opts.templateVars)
2692
+ {
2693
+ html = html.replace(/\{\{(.*?)\}\}/gi, '<!-- template double $1 -->');
2694
+ html = html.replace(/\{(.*?)\}/gi, '<!-- template $1 -->');
2695
+ }
2696
+
2618
2697
  html = html.replace(/<script(.*?)>([\w\W]*?)<\/script>/gi, '<title type="text/javascript" style="display: none;" class="redactor-script-tag"$1>$2</title>');
2619
2698
  html = html.replace(/<style(.*?)>([\w\W]*?)<\/style>/gi, '<section$1 style="display: none;" rel="redactor-style-tag">$2</section>');
2620
2699
  html = html.replace(/<form(.*?)>([\w\W]*?)<\/form>/gi, '<section$1 rel="redactor-form-tag">$2</section>');
@@ -2627,6 +2706,12 @@
2627
2706
  },
2628
2707
  cleanReConvertProtected: function(html)
2629
2708
  {
2709
+ if (this.opts.templateVars)
2710
+ {
2711
+ html = html.replace(/<!-- template double (.*?) -->/gi, '{{$1}}');
2712
+ html = html.replace(/<!-- template (.*?) -->/gi, '{$1}');
2713
+ }
2714
+
2630
2715
  html = html.replace(/<title type="text\/javascript" style="display: none;" class="redactor-script-tag"(.*?)>([\w\W]*?)<\/title>/gi, '<script$1 type="text/javascript">$2</script>');
2631
2716
  html = html.replace(/<section(.*?) style="display: none;" rel="redactor-style-tag">([\w\W]*?)<\/section>/gi, '<style$1>$2</style>');
2632
2717
  html = html.replace(/<section(.*?)rel="redactor-form-tag"(.*?)>([\w\W]*?)<\/section>/gi, '<form$1$2>$3</form>');
@@ -2640,31 +2725,23 @@
2640
2725
  {
2641
2726
  if (buffer !== false)
2642
2727
  {
2643
- // save code
2644
- var buffer = [], z = 0, code;
2645
- code = html.match(/<(pre|style|script|title)(.*?)>([\w\W]*?)<\/(pre|style|script|title)>/gi);
2646
- if (code !== null)
2728
+ var buffer = []
2729
+ var matches = html.match(/<(pre|style|script|title)(.*?)>([\w\W]*?)<\/(pre|style|script|title)>/gi);
2730
+ if (matches === null) matches = [];
2731
+
2732
+ if (this.opts.phpTags)
2647
2733
  {
2648
- $.each(code, function(i,s)
2649
- {
2650
- z = i;
2651
- html = html.replace(s, 'buffer_' + z);
2652
- buffer.push(s);
2653
- });
2734
+ var phpMatches = html.match(/<\?php([\w\W]*?)\?>/gi);
2735
+ if (phpMatches) matches = $.merge(matches, phpMatches);
2654
2736
  }
2655
2737
 
2656
- if (this.opts.phpTags)
2738
+ if (matches)
2657
2739
  {
2658
- code = html.match(/<\?php([\w\W]*?)\?>/gi);
2659
- if (code !== null)
2740
+ $.each(matches, function(i, s)
2660
2741
  {
2661
- $.each(code, function(i,s)
2662
- {
2663
- z = z + i;
2664
- html = html.replace(s, 'buffer_' + i);
2665
- buffer.push(s);
2666
- });
2667
- }
2742
+ html = html.replace(s, 'buffer_' + i);
2743
+ buffer.push(s);
2744
+ });
2668
2745
  }
2669
2746
  }
2670
2747
 
@@ -2673,26 +2750,22 @@
2673
2750
  html = html.replace(/\n\s*\n/g, "\n");
2674
2751
  html = html.replace(/^[\s\n]*/g, ' ');
2675
2752
  html = html.replace(/[\s\n]*$/g, ' ');
2676
- html = html.replace( />\s{2,}</g, '><' ); // between inline tags can be only one space
2753
+ html = html.replace( />\s{2,}</g, '> <'); // between inline tags can be only one space
2677
2754
 
2678
- if (buffer !== false)
2679
- {
2680
- html = this.cleanReplacer(buffer, html);
2681
- }
2755
+ html = this.cleanReplacer(html, buffer);
2682
2756
 
2683
2757
  html = html.replace(/\n\n/g, "\n");
2684
2758
 
2685
2759
  return html;
2686
2760
  },
2687
- cleanReplacer: function(arr, html)
2761
+ cleanReplacer: function(html, buffer)
2688
2762
  {
2689
- if (arr)
2763
+ if (buffer === false) return html;
2764
+
2765
+ $.each(buffer, function(i,s)
2690
2766
  {
2691
- $.each(arr, function(i,s)
2692
- {
2693
- html = html.replace('buffer_' + i, s);
2694
- });
2695
- }
2767
+ html = html.replace('buffer_' + i, s);
2768
+ });
2696
2769
 
2697
2770
  return html;
2698
2771
  },
@@ -2730,35 +2803,29 @@
2730
2803
  html = html + "\n";
2731
2804
 
2732
2805
  var safes = [];
2733
- var z = 0;
2734
2806
  var matches = html.match(/<(table|div|pre|object)(.*?)>([\w\W]*?)<\/(table|div|pre|object)>/gi);
2807
+ if (matches === null) matches = [];
2735
2808
 
2736
- if (matches)
2737
- {
2738
- $.each(matches, function(i,s)
2739
- {
2740
- z++;
2741
- safes[z] = s;
2742
- html = html.replace(s, '{replace' + z + '}\n');
2743
- });
2744
- }
2809
+ var commentsMatches = html.match(/<!--([\w\W]*?)-->/gi);
2810
+ if (commentsMatches) matches = $.merge(matches, commentsMatches);
2745
2811
 
2746
2812
  if (this.opts.phpTags)
2747
2813
  {
2748
2814
  var phpMatches = html.match(/<section(.*?)rel="redactor-php-tag">([\w\W]*?)<\/section>/gi);
2749
2815
  if (phpMatches)
2750
2816
  {
2751
- $.each(phpMatches, function(i,s)
2752
- {
2753
- z++;
2754
- safes[z] = s;
2755
- html = html.replace(s, '{replace' + z + '}\n');
2756
- });
2817
+ matches = $.merge(matches, phpMatches);
2757
2818
  }
2758
2819
  }
2759
2820
 
2760
- // comments safe
2761
- html = html.replace(/<\!\-\-([\w\W]*?)\-\->/gi, "<comment>$1</comment>");
2821
+ if (matches)
2822
+ {
2823
+ $.each(matches, function(i,s)
2824
+ {
2825
+ safes[i] = s;
2826
+ html = html.replace(s, '{replace' + i + '}\n');
2827
+ });
2828
+ }
2762
2829
 
2763
2830
  html = html.replace(/<br \/>\s*<br \/>/gi, "\n\n");
2764
2831
 
@@ -2813,23 +2880,24 @@
2813
2880
  html = R('<br />(\s*</?(?:p|li|div|dl|dd|dt|th|pre|td|ul|ol)[^>]*>)', 'gi', '$1');
2814
2881
  html = R("\n</p>", 'gi', '</p>');
2815
2882
 
2883
+ html = R('<li><p>', 'gi', '<li>');
2884
+ html = R('</p></li>', 'gi', '</li>');
2816
2885
  html = R('</li><p>', 'gi', '</li>');
2817
2886
  //html = R('</ul><p>(.*?)</li>', 'gi', '</ul></li>');
2818
- html = R('</ol><p>', 'gi', '</ol>');
2887
+ /* html = R('</ol><p>', 'gi', '</ol>'); */
2819
2888
  html = R('<p>\t?\n?<p>', 'gi', '<p>');
2820
2889
  html = R('</dt><p>', 'gi', '</dt>');
2821
2890
  html = R('</dd><p>', 'gi', '</dd>');
2822
2891
  html = R('<br></p></blockquote>', 'gi', '</blockquote>');
2823
- html = R('<p> </p>', 'gi', '');
2892
+ html = R('<p>\t*</p>', 'gi', '');
2824
2893
 
2894
+
2895
+ // restore safes
2825
2896
  $.each(safes, function(i,s)
2826
2897
  {
2827
2898
  html = html.replace('{replace' + i + '}', s);
2828
2899
  });
2829
2900
 
2830
- // comments safe
2831
- html = html.replace(/<comment>([\w\W]*?)<\/comment>/gi, '<!--$1-->');
2832
-
2833
2901
  return $.trim(html);
2834
2902
  },
2835
2903
  cleanConvertInlineTags: function(html)
@@ -2910,18 +2978,18 @@
2910
2978
 
2911
2979
  var $elem = this.$editor.find('li, img, a, b, strong, sub, sup, i, em, u, small, strike, del, span, cite');
2912
2980
 
2913
- $elem.filter('[style*="font-size"][style*="line-height"]')
2981
+ $elem.not('[data-redactor="verified"]').filter('[style*="font-size"][style*="line-height"]')
2914
2982
  .css('font-size', '')
2915
2983
  .css('line-height', '');
2916
2984
 
2917
- $elem.filter('[style*="background-color: transparent;"][style*="line-height"]')
2985
+ $elem.not('[data-redactor="verified"]').filter('[style*="background-color: transparent;"][style*="line-height"]')
2918
2986
  .css('background-color', '')
2919
2987
  .css('line-height', '');
2920
2988
 
2921
- $elem.filter('[style*="background-color: transparent;"]')
2989
+ $elem.not('[data-redactor="verified"]').filter('[style*="background-color: transparent;"]')
2922
2990
  .css('background-color', '');
2923
2991
 
2924
- $elem.css('line-height', '');
2992
+ $elem.not('[data-redactor="verified"]').css('line-height', '');
2925
2993
 
2926
2994
  $.each($elem, $.proxy(function(i,s)
2927
2995
  {
@@ -3338,8 +3406,10 @@
3338
3406
  $(wrapper).html(html);
3339
3407
  this.selectionElement(wrapper);
3340
3408
  var next = $(wrapper).next();
3341
- if (next[0].tagName === 'BR') next.remove();
3342
-
3409
+ if (next.size() != 0 && next[0].tagName === 'BR')
3410
+ {
3411
+ next.remove();
3412
+ }
3343
3413
  }
3344
3414
  }
3345
3415
 
@@ -3460,7 +3530,7 @@
3460
3530
  var range = this.getRange()
3461
3531
  var el = this.getElement();
3462
3532
 
3463
- if (range.collapsed || range.startContainer === range.endContainer && el)
3533
+ if ((range.collapsed || range.startContainer === range.endContainer) && el && !this.nodeTestBlocks(el))
3464
3534
  {
3465
3535
  $(el)[type](attr, value);
3466
3536
  }
@@ -3567,15 +3637,22 @@
3567
3637
 
3568
3638
  var utag = tag.toUpperCase();
3569
3639
  var nodes = this.getNodes();
3640
+ var parent = $(this.getParent()).parent();
3570
3641
 
3571
3642
  $.each(nodes, function(i, s)
3572
3643
  {
3573
- if (s.tagName === utag) $(s).replaceWith($(s).contents());
3644
+ if (s.tagName === utag) this.inlineRemoveFormatReplace(s);
3574
3645
  });
3575
3646
 
3647
+ if (parent && parent[0].tagName === utag) this.inlineRemoveFormatReplace(parent);
3648
+
3576
3649
  this.selectionRestore();
3577
3650
  this.sync();
3578
3651
  },
3652
+ inlineRemoveFormatReplace: function(el)
3653
+ {
3654
+ $(el).replaceWith($(el).contents());
3655
+ },
3579
3656
 
3580
3657
  // INSERT
3581
3658
  insertHtml: function (html, sync)
@@ -3617,17 +3694,20 @@
3617
3694
  }
3618
3695
 
3619
3696
  // in the quote, we can insert text or links only
3697
+ /*
3620
3698
  if (currBlock.tagName == 'BLOCKQUOTE' && html.indexOf('<a') === -1)
3621
3699
  {
3622
3700
  this.insertText(html);
3623
3701
  }
3624
- else if ($html.contents().length > 1 && currBlock
3702
+ else
3703
+ */
3704
+ if ($html.contents().length > 1 && currBlock
3625
3705
  || $html.contents().is('p, :header, ul, ol, div, table, blockquote, pre, address, section, header, footer, aside, article'))
3626
3706
  {
3627
- if(this.browser('msie')) this.document.selection.createRange().pasteHTML(html);
3707
+ if (this.browser('msie')) this.document.selection.createRange().pasteHTML(html);
3628
3708
  else this.document.execCommand('inserthtml', false, html);
3629
3709
  }
3630
- else this.insertHtmlAdvanced(html);
3710
+ else this.insertHtmlAdvanced(html, false);
3631
3711
 
3632
3712
  if (this.selectall)
3633
3713
  {
@@ -3643,7 +3723,7 @@
3643
3723
 
3644
3724
  if (sync !== false) this.sync();
3645
3725
  },
3646
- insertHtmlAdvanced: function(html)
3726
+ insertHtmlAdvanced: function(html, sync)
3647
3727
  {
3648
3728
  var sel = this.getSelection();
3649
3729
 
@@ -3671,6 +3751,12 @@
3671
3751
  sel.addRange(range);
3672
3752
  }
3673
3753
  }
3754
+
3755
+ if (sync !== false)
3756
+ {
3757
+ this.sync();
3758
+ }
3759
+
3674
3760
  },
3675
3761
  insertText: function(html)
3676
3762
  {
@@ -3701,11 +3787,41 @@
3701
3787
  sel.addRange(range);
3702
3788
  }
3703
3789
  },
3790
+ insertNodeToCaretPositionFromPoint: function(e, node)
3791
+ {
3792
+ var range;
3793
+ var x = e.clientX, y = e.clientY;
3794
+ if (this.document.caretPositionFromPoint)
3795
+ {
3796
+ var pos = this.document.caretPositionFromPoint(x, y);
3797
+ range = this.getRange();
3798
+ range.setStart(pos.offsetNode, pos.offset);
3799
+ range.collapse(true);
3800
+ range.insertNode(node);
3801
+ }
3802
+ else if (this.document.caretRangeFromPoint)
3803
+ {
3804
+ range = this.document.caretRangeFromPoint(x, y);
3805
+ range.insertNode(node);
3806
+ }
3807
+ else if (typeof document.body.createTextRange != "undefined")
3808
+ {
3809
+ range = this.document.body.createTextRange();
3810
+ range.moveToPoint(x, y);
3811
+ var endRange = range.duplicate();
3812
+ endRange.moveToPoint(x, y);
3813
+ range.setEndPoint("EndToEnd", endRange);
3814
+ range.select();
3815
+ }
3816
+
3817
+ },
3704
3818
  insertAfterLastElement: function(element)
3705
3819
  {
3706
3820
  if (this.isEndOfElement())
3707
3821
  {
3708
- if (this.$editor.contents().last()[0] !== element) return false;
3822
+
3823
+ if ($($.trim(this.$editor.html())).get(0) != $.trim(element)
3824
+ && this.$editor.contents().last()[0] !== element) return false;
3709
3825
 
3710
3826
  this.bufferSet();
3711
3827
 
@@ -3749,6 +3865,22 @@
3749
3865
  {
3750
3866
  html = this.callback('pasteBefore', false, html);
3751
3867
 
3868
+ if (this.opts.pastePlainText)
3869
+ {
3870
+ var tmp = this.document.createElement('div');
3871
+
3872
+ html = html.replace(/<br>|<\/H[1-6]>|<\/p>|<\/div>/gi, '\n');
3873
+
3874
+ tmp.innerHTML = html;
3875
+ html = tmp.textContent || tmp.innerText;
3876
+
3877
+ html = html.replace('\n', '<br>');
3878
+ html = this.cleanParagraphy(html);
3879
+
3880
+ this.pasteInsert(html);
3881
+ return false;
3882
+ }
3883
+
3752
3884
  // clean up pre
3753
3885
  if (this.currentOrParentIs('PRE'))
3754
3886
  {
@@ -3770,6 +3902,7 @@
3770
3902
 
3771
3903
  // remove google docs marker
3772
3904
  html = html.replace(/<b\sid="internal-source-marker(.*?)">([\w\W]*?)<\/b>/gi, "$2");
3905
+ html = html.replace(/<b(.*?)id="docs-internal-guid(.*?)">([\w\W]*?)<\/b>/gi, "$3");
3773
3906
 
3774
3907
  // strip tags
3775
3908
  html = this.cleanStripTags(html);
@@ -3786,6 +3919,7 @@
3786
3919
  html = html.replace(/<object(.*?)>([\w\W]*?)<\/object>/gi, '[object$1]$2[/object]');
3787
3920
  html = html.replace(/<param(.*?)>/gi, '[param$1]');
3788
3921
  html = html.replace(/<img(.*?)style="(.*?)"(.*?)>/gi, '[img$1$3]');
3922
+ html = html.replace(/<img(.*?)>/gi, '[img$1]');
3789
3923
 
3790
3924
  // remove classes
3791
3925
  html = html.replace(/ class="(.*?)"/gi, '');
@@ -3817,8 +3951,15 @@
3817
3951
  html = html.replace(/<\/p><\/div>/gi, '</p>');
3818
3952
  }
3819
3953
 
3954
+ if (this.currentOrParentIs('LI'))
3955
+ {
3956
+ html = html.replace(/<p>([\w\W]*?)<\/p>/gi, '$1<br>');
3957
+ }
3958
+ else
3959
+ {
3960
+ html = this.cleanParagraphy(html);
3961
+ }
3820
3962
 
3821
- html = this.cleanParagraphy(html);
3822
3963
 
3823
3964
  // remove span
3824
3965
  html = html.replace(/<span(.*?)>([\w\W]*?)<\/span>/gi, '$2');
@@ -3844,9 +3985,28 @@
3844
3985
  // remove empty finally
3845
3986
  html = html.replace(/<[^\/>][^>][^img|param|source]*>(\s*|\t*|\n*|&nbsp;|<br>)<\/[^>]+>/gi, '');
3846
3987
 
3847
- // FF fix
3988
+ // remove safari local images
3989
+ html = html.replace(/<img src="webkit-fake-url\:\/\/(.*?)"(.*?)>/gi, '');
3990
+
3991
+ // FF specific
3992
+ this.pasteClipboardMozilla = false;
3848
3993
  if (this.browser('mozilla'))
3849
3994
  {
3995
+ if (this.opts.clipboardUpload)
3996
+ {
3997
+ var matches = html.match(/<img src="data:image(.*?)"(.*?)>/gi);
3998
+ if (matches !== null)
3999
+ {
4000
+ this.pasteClipboardMozilla = matches;
4001
+ for (k in matches)
4002
+ {
4003
+ var img = matches[k].replace('<img', '<img data-mozilla-paste-image="' + k + '" ');
4004
+ html = html.replace(matches[k], img);
4005
+ }
4006
+ }
4007
+ }
4008
+
4009
+ // FF fix
3850
4010
  while (/<br>$/gi.test(html))
3851
4011
  {
3852
4012
  html = html.replace(/<br>$/gi, '');
@@ -3889,12 +4049,97 @@
3889
4049
  this.insertHtml(html);
3890
4050
 
3891
4051
  this.selectall = false;
3892
- setTimeout(function() { rtePaste = false; }, 100);
4052
+ setTimeout($.proxy(function()
4053
+ {
4054
+ rtePaste = false;
4055
+
4056
+ // FF specific
4057
+ if (this.browser('mozilla'))
4058
+ {
4059
+ this.$editor.find('p:empty').remove()
4060
+ }
4061
+ if (this.pasteClipboardMozilla !== false)
4062
+ {
4063
+ this.pasteClipboardUploadMozilla();
4064
+ }
4065
+
4066
+ }, this), 100);
3893
4067
 
3894
4068
  if (this.opts.autoresize) $(this.document.body).scrollTop(this.saveScroll);
3895
4069
  else this.$editor.scrollTop(this.saveScroll);
3896
4070
  },
3897
4071
 
4072
+ pasteClipboardUploadMozilla: function()
4073
+ {
4074
+ var imgs = this.$editor.find('img[data-mozilla-paste-image]');
4075
+ $.each(imgs, $.proxy(function(i,s)
4076
+ {
4077
+ var $s = $(s);
4078
+ var arr = s.src.split(",");
4079
+ var data = arr[1]; // raw base64
4080
+ var contentType = arr[0].split(";")[0].split(":")[1];
4081
+
4082
+ $.post(this.opts.clipboardUploadUrl, {
4083
+ contentType: contentType,
4084
+ data: data
4085
+ },
4086
+ $.proxy(function(data)
4087
+ {
4088
+ var json = $.parseJSON(data);
4089
+ $s.attr('src', json.filelink);
4090
+ $s.removeAttr('data-mozilla-paste-image');
4091
+
4092
+ this.sync();
4093
+
4094
+ // upload callback
4095
+ this.callback('imageUpload', $s, json);
4096
+
4097
+ }, this));
4098
+
4099
+ }, this));
4100
+ },
4101
+ pasteClipboardUpload: function(e)
4102
+ {
4103
+ var result = e.target.result;
4104
+ var arr = result.split(",");
4105
+ var data = arr[1]; // raw base64
4106
+ var contentType = arr[0].split(";")[0].split(":")[1];
4107
+
4108
+ if (this.opts.clipboardUpload)
4109
+ {
4110
+ $.post(this.opts.clipboardUploadUrl, {
4111
+ contentType: contentType,
4112
+ data: data
4113
+ },
4114
+ $.proxy(function(data)
4115
+ {
4116
+ var json = $.parseJSON(data);
4117
+
4118
+ var html = '<img src="' + json.filelink + '" id="clipboard-image-marker" />';
4119
+ this.execCommand('inserthtml', html, false);
4120
+
4121
+ var image = $(this.$editor.find('img#clipboard-image-marker'));
4122
+
4123
+ if (image.length) image.removeAttr('id');
4124
+ else image = false;
4125
+
4126
+ this.sync();
4127
+
4128
+ // upload callback
4129
+ if (image)
4130
+ {
4131
+ this.callback('imageUpload', image, json);
4132
+ }
4133
+
4134
+
4135
+ }, this));
4136
+ }
4137
+ else
4138
+ {
4139
+ this.insertHtml('<img src="' + result + '" />');
4140
+ }
4141
+ },
4142
+
3898
4143
  // BUFFER
3899
4144
  bufferSet: function(html)
3900
4145
  {
@@ -3948,6 +4193,17 @@
3948
4193
  {
3949
4194
  this.observeImages();
3950
4195
  this.observeTables();
4196
+
4197
+ if (this.opts.observeLinks) this.observeLinks();
4198
+ },
4199
+ observeLinks: function()
4200
+ {
4201
+ this.$editor.find('a').on('click', $.proxy(this.linkObserver, this));
4202
+ this.$editor.on('click.redactor', $.proxy(function(e)
4203
+ {
4204
+ this.linkObserverTooltipClose(e);
4205
+
4206
+ }, this));
3951
4207
  },
3952
4208
  observeTables: function()
3953
4209
  {
@@ -3964,6 +4220,62 @@
3964
4220
 
3965
4221
  }, this));
3966
4222
  },
4223
+ linkObserver: function(e)
4224
+ {
4225
+ var $link = $(e.target);
4226
+ var pos = $link.offset();
4227
+ if (this.opts.iframe)
4228
+ {
4229
+ var posFrame = this.$frame.offset();
4230
+ pos.top = posFrame.top + (pos.top - $(this.document).scrollTop());
4231
+ pos.left += posFrame.left;
4232
+ }
4233
+
4234
+ var tooltip = $('<span class="redactor-link-tooltip"></span>');
4235
+
4236
+ var href = $link.attr('href');
4237
+ if (href.length > 24) href = href.substring(0,24) + '...';
4238
+
4239
+ var aLink = $('<a href="' + $link.attr('href') + '" target="_blank">' + href + '</a>').on('click', $.proxy(function(e)
4240
+ {
4241
+ this.linkObserverTooltipClose(false);
4242
+ }, this));
4243
+
4244
+ var aEdit = $('<a href="#">' + this.opts.curLang.edit + '</a>').on('click', $.proxy(function(e)
4245
+ {
4246
+ e.preventDefault();
4247
+ this.linkShow();
4248
+ this.linkObserverTooltipClose(false);
4249
+
4250
+ }, this));
4251
+
4252
+ var aUnlink = $('<a href="#">' + this.opts.curLang.unlink + '</a>').on('click', $.proxy(function(e)
4253
+ {
4254
+ e.preventDefault();
4255
+ this.execCommand('unlink');
4256
+ this.linkObserverTooltipClose(false);
4257
+
4258
+ }, this));
4259
+
4260
+
4261
+ tooltip.append(aLink);
4262
+ tooltip.append(' | ');
4263
+ tooltip.append(aEdit);
4264
+ tooltip.append(' | ');
4265
+ tooltip.append(aUnlink);
4266
+ tooltip.css({
4267
+ top: (pos.top + 20) + 'px',
4268
+ left: pos.left + 'px'
4269
+ });
4270
+
4271
+ $('.redactor-link-tooltip').remove();
4272
+ $('body').append(tooltip);
4273
+ },
4274
+ linkObserverTooltipClose: function(e)
4275
+ {
4276
+ if (e !== false && e.target.tagName == 'A') return false;
4277
+ $('.redactor-link-tooltip').remove();
4278
+ },
3967
4279
 
3968
4280
 
3969
4281
  // SELECTION
@@ -4063,7 +4375,7 @@
4063
4375
  {
4064
4376
  var caretOffset = 0;
4065
4377
 
4066
- var range = this.getSelection().getRangeAt(0);
4378
+ var range = this.getRange();
4067
4379
  var preCaretRange = range.cloneRange();
4068
4380
  preCaretRange.selectNodeContents(element);
4069
4381
  preCaretRange.setEnd(range.endContainer, range.endOffset);
@@ -4191,7 +4503,11 @@
4191
4503
  var node1 = this.$editor.find('span#selection-marker-1');
4192
4504
  var node2 = this.$editor.find('span#selection-marker-2');
4193
4505
 
4194
- if (!this.isFocused())
4506
+ if (this.browser('mozilla'))
4507
+ {
4508
+ this.$editor.focus();
4509
+ }
4510
+ else if (!this.isFocused())
4195
4511
  {
4196
4512
  this.$editor.focus();
4197
4513
  }
@@ -4223,7 +4539,7 @@
4223
4539
  {
4224
4540
  $.each(this.$editor.find('span.redactor-selection-marker'), function()
4225
4541
  {
4226
- var html = $.trim($(this).html().replace(/[^\u0000-~]/g, ''));
4542
+ var html = $.trim($(this).html().replace(/[^\u0000-\u1C7F]/g, ''));
4227
4543
  if (html == '')
4228
4544
  {
4229
4545
  $(this).remove();
@@ -4527,13 +4843,13 @@
4527
4843
  this.selectionRestore();
4528
4844
 
4529
4845
  var current = this.getBlock() || this.getCurrent();
4530
- if (current)
4846
+ if (current && current.tagName != 'BODY')
4531
4847
  {
4532
4848
  $(current).after(html)
4533
4849
  }
4534
4850
  else
4535
4851
  {
4536
- this.insertHtmlAdvanced(html);
4852
+ this.insertHtmlAdvanced(html, false);
4537
4853
 
4538
4854
  }
4539
4855
 
@@ -4543,6 +4859,7 @@
4543
4859
  this.tableObserver(table);
4544
4860
  this.buttonActiveObserver();
4545
4861
 
4862
+ table.find('span#selection-marker-1').remove();
4546
4863
  table.removeAttr('id');
4547
4864
 
4548
4865
  this.sync();
@@ -4714,13 +5031,11 @@
4714
5031
  this.selectionRestore();
4715
5032
 
4716
5033
  var current = this.getBlock() || this.getCurrent();
4717
- if (current)
4718
- {
4719
- $(current).after(data)
4720
- this.sync();
4721
- }
4722
- else this.insertHtmlAdvanced(data);
4723
5034
 
5035
+ if (current) $(current).after(data)
5036
+ else this.insertHtmlAdvanced(data, false);
5037
+
5038
+ this.sync();
4724
5039
  this.modalClose();
4725
5040
  },
4726
5041
 
@@ -4758,6 +5073,13 @@
4758
5073
  var thref = self.location.href.replace(/\/$/i, '');
4759
5074
  var turl = url.replace(thref, '');
4760
5075
 
5076
+ // remove host from href
5077
+ if (this.opts.linkProtocol === false)
5078
+ {
5079
+ var re = new RegExp('^(http|ftp|https)://' + self.location.host, 'i');
5080
+ turl = turl.replace(re, '');
5081
+ }
5082
+
4761
5083
  var tabs = $('#redactor_tabs').find('a');
4762
5084
 
4763
5085
  if (this.opts.linkEmail === false) tabs.eq(1).remove();
@@ -4844,6 +5166,7 @@
4844
5166
  text = $('#redactor_link_anchor_text').val();
4845
5167
  }
4846
5168
 
5169
+ text = text.replace(/<|>/g, '');
4847
5170
  this.linkInsert('<a href="' + link + '"' + target + '>' + text + '</a>', $.trim(text), link, targetBlank);
4848
5171
 
4849
5172
  },
@@ -5102,10 +5425,10 @@
5102
5425
  this.modalInit(this.opts.curLang.image, this.opts.modal_image, 610, callback);
5103
5426
 
5104
5427
  },
5105
- imageEdit: function(e)
5428
+ imageEdit: function(image)
5106
5429
  {
5107
- var $el = $(e.target);
5108
- var parent = $el.parent();
5430
+ var $el = image;
5431
+ var parent = $el.parent().parent();
5109
5432
 
5110
5433
  var callback = $.proxy(function()
5111
5434
  {
@@ -5115,7 +5438,12 @@
5115
5438
 
5116
5439
  if ($(parent).get(0).tagName === 'A')
5117
5440
  {
5118
- $('#redactor_file_link').val($( parent ).attr('href'));
5441
+ $('#redactor_file_link').val($(parent).attr('href'));
5442
+
5443
+ if ($(parent).attr('target') == '_blank')
5444
+ {
5445
+ $('#redactor_link_blank').prop('checked', true);
5446
+ }
5119
5447
  }
5120
5448
 
5121
5449
  $('#redactor_image_delete_btn').click($.proxy(function()
@@ -5154,43 +5482,67 @@
5154
5482
  },
5155
5483
  imageSave: function(el)
5156
5484
  {
5157
- var parent = $(el).parent();
5485
+ var $el = $(el);
5486
+ var parent = $el.parent();
5158
5487
 
5159
- $(el).attr('alt', $('#redactor_file_alt').val());
5488
+ $el.attr('alt', $('#redactor_file_alt').val());
5160
5489
 
5161
5490
  var floating = $('#redactor_form_image_align').val();
5162
5491
 
5163
5492
  if (floating === 'left')
5164
5493
  {
5165
- $(el).css({ 'float': 'left', 'margin': '0 10px 10px 0' });
5494
+ $el.css({ 'float': 'left', 'margin': '0 ' + this.opts.imageFloatMargin + ' ' + this.opts.imageFloatMargin + ' 0' });
5166
5495
  }
5167
5496
  else if (floating === 'right')
5168
5497
  {
5169
- $(el).css({ 'float': 'right', 'margin': '0 0 10px 10px' });
5498
+ $el.css({ 'float': 'right', 'margin': '0 0 ' + this.opts.imageFloatMargin + ' ' + this.opts.imageFloatMargin + '' });
5170
5499
  }
5171
5500
  else
5172
5501
  {
5173
- $(el).css({ 'float': 'none', 'margin': '0' });
5502
+ var imageBox = $el.closest('#redactor-image-box');
5503
+ if (imageBox.size() != 0) imageBox.css({ 'float': '', 'margin': '' });
5504
+ $el.css({ 'float': '', 'margin': '' });
5174
5505
  }
5175
5506
 
5176
5507
  // as link
5177
5508
  var link = $.trim($('#redactor_file_link').val());
5178
5509
  if (link !== '')
5179
5510
  {
5180
- if ($(parent).get(0).tagName !== 'A')
5511
+ var target = false;
5512
+ if ($('#redactor_link_blank').prop('checked'))
5513
+ {
5514
+ target = true;
5515
+ }
5516
+
5517
+ if (parent.get(0).tagName !== 'A')
5181
5518
  {
5182
- $(el).replaceWith('<a href="' + link + '">' + this.outerHtml(el) + '</a>');
5519
+ var a = $('<a href="' + link + '">' + this.outerHtml(el) + '</a>');
5520
+
5521
+ if (target)
5522
+ {
5523
+ a.attr('target', '_blank');
5524
+ }
5525
+
5526
+ $el.replaceWith(a);
5183
5527
  }
5184
5528
  else
5185
5529
  {
5186
- $(parent).attr('href', link);
5530
+ parent.attr('href', link);
5531
+ if (target)
5532
+ {
5533
+ parent.attr('target', '_blank');
5534
+ }
5535
+ else
5536
+ {
5537
+ parent.removeAttr('target');
5538
+ }
5187
5539
  }
5188
5540
  }
5189
5541
  else
5190
5542
  {
5191
- if ($(parent).get(0).tagName === 'A')
5543
+ if (parent.get(0).tagName === 'A')
5192
5544
  {
5193
- $(parent).replaceWith(this.outerHtml(el));
5545
+ parent.replaceWith(this.outerHtml(el));
5194
5546
  }
5195
5547
  }
5196
5548
 
@@ -5199,76 +5551,224 @@
5199
5551
  this.sync();
5200
5552
 
5201
5553
  },
5202
- imageResize: function(image)
5554
+ imageResizeHide: function(e)
5203
5555
  {
5204
- var $image = $(image),
5205
- clicked = false,
5206
- clicker = false,
5207
- start_x,
5208
- start_y,
5209
- ratio = $image.width() / $image.height(),
5210
- min_w = 10,
5211
- min_h = 10;
5212
-
5213
- $image.off('hover mousedown mouseup click mousemove');
5556
+ if (e !== false && $(e.target).parent().size() != 0 && $(e.target).parent()[0].id === 'redactor-image-box')
5557
+ {
5558
+ return false;
5559
+ }
5214
5560
 
5215
- $image.hover(function()
5561
+ var imageBox = this.$editor.find('#redactor-image-box');
5562
+ if (imageBox.size() == 0)
5216
5563
  {
5217
- $image.css('cursor', 'nw-resize');
5218
- },
5219
- function()
5564
+ return false;
5565
+ }
5566
+
5567
+ this.$editor.find('#redactor-image-editter, #redactor-image-resizer').remove();
5568
+
5569
+ var margin = imageBox.css('margin');
5570
+ if (margin != '0px')
5220
5571
  {
5221
- $image.css('cursor', '');
5222
- clicked = false;
5223
- });
5572
+ imageBox.find('img').css('margin', margin);
5573
+ imageBox.css('margin', '');
5574
+ }
5224
5575
 
5225
- $image.mousedown(function(e)
5576
+ imageBox.find('img').css('opacity', '');
5577
+ imageBox.replaceWith(function()
5226
5578
  {
5227
- e.preventDefault();
5579
+ return $(this).contents();
5580
+ });
5228
5581
 
5229
- ratio = $image.width() / $image.height();
5582
+ $(document).off('click.redactor-image-resize-hide');
5583
+ this.$editor.off('click.redactor-image-resize-hide');
5584
+ this.$editor.off('keydown.redactor-image-delete');
5230
5585
 
5231
- clicked = true;
5232
- clicker = true;
5586
+ this.sync()
5233
5587
 
5234
- start_x = Math.round(e.pageX - $image.eq( 0 ).offset().left);
5235
- start_y = Math.round(e.pageY - $image.eq( 0 ).offset().top);
5236
- });
5588
+ },
5589
+ imageResize: function(image)
5590
+ {
5591
+ var $image = $(image);
5237
5592
 
5238
- $image.mouseup( $.proxy(function(e)
5593
+ $image.on('mousedown', $.proxy(function()
5239
5594
  {
5240
- clicked = false;
5241
- $image.css('cursor', '');
5242
- this.sync();
5243
-
5595
+ this.imageResizeHide(false);
5244
5596
  }, this));
5245
5597
 
5246
- $image.click( $.proxy(function(e)
5598
+ $image.on('dragstart', $.proxy(function()
5247
5599
  {
5248
- if (clicker) this.imageEdit(e);
5600
+ this.$editor.on('drop.redactor-image-inside-drop', $.proxy(function()
5601
+ {
5602
+ setTimeout($.proxy(function()
5603
+ {
5604
+ this.observeImages();
5605
+ this.$editor.off('drop.redactor-image-inside-drop');
5606
+ this.sync();
5607
+
5608
+ }, this), 1);
5249
5609
 
5610
+ },this));
5250
5611
  }, this));
5251
5612
 
5252
- $image.mousemove(function(e)
5613
+ $image.on('click', $.proxy(function(e)
5253
5614
  {
5254
- if (clicked)
5615
+ if (this.$editor.find('#redactor-image-box').size() != 0)
5616
+ {
5617
+ return false;
5618
+ }
5619
+
5620
+ var clicked = false,
5621
+ start_x,
5622
+ start_y,
5623
+ ratio = $image.width() / $image.height(),
5624
+ min_w = 20,
5625
+ min_h = 10;
5626
+
5627
+ var imageResizer = this.imageResizeControls($image);
5628
+
5629
+ // resize
5630
+ var isResizing = false;
5631
+ imageResizer.on('mousedown', function(e)
5255
5632
  {
5256
- clicker = false;
5633
+ isResizing = true;
5634
+ e.preventDefault();
5635
+
5636
+ ratio = $image.width() / $image.height();
5637
+
5638
+ start_x = Math.round(e.pageX - $image.eq(0).offset().left);
5639
+ start_y = Math.round(e.pageY - $image.eq(0).offset().top);
5257
5640
 
5258
- var mouse_x = Math.round(e.pageX - $(this).eq(0).offset().left) - start_x;
5259
- var mouse_y = Math.round(e.pageY - $(this).eq(0).offset().top) - start_y;
5641
+ });
5260
5642
 
5261
- var div_h = $image.height();
5643
+ $(this.document.body).on('mousemove', $.proxy(function(e)
5644
+ {
5645
+ if (isResizing)
5646
+ {
5647
+ var mouse_x = Math.round(e.pageX - $image.eq(0).offset().left) - start_x;
5648
+ var mouse_y = Math.round(e.pageY - $image.eq(0).offset().top) - start_y;
5262
5649
 
5263
- var new_h = parseInt(div_h, 10) + mouse_y;
5264
- var new_w = Math.round(new_h * ratio);
5650
+ var div_h = $image.height();
5265
5651
 
5266
- if (new_w > min_w) $image.width(new_w);
5652
+ var new_h = parseInt(div_h, 10) + mouse_y;
5653
+ var new_w = Math.round(new_h * ratio);
5267
5654
 
5268
- start_x = Math.round(e.pageX - $(this).eq(0).offset().left);
5269
- start_y = Math.round(e.pageY - $(this).eq(0).offset().top);
5270
- }
5655
+ if (new_w > min_w)
5656
+ {
5657
+ $image.width(new_w);
5658
+
5659
+ if (new_w < 100)
5660
+ {
5661
+ this.imageEditter.css({
5662
+ marginTop: '-7px',
5663
+ marginLeft: '-13px',
5664
+ fontSize: '9px',
5665
+ padding: '3px 5px'
5666
+ });
5667
+ }
5668
+ else
5669
+ {
5670
+ this.imageEditter.css({
5671
+ marginTop: '-11px',
5672
+ marginLeft: '-18px',
5673
+ fontSize: '11px',
5674
+ padding: '7px 10px'
5675
+ });
5676
+ }
5677
+ }
5678
+
5679
+ start_x = Math.round(e.pageX - $image.eq(0).offset().left);
5680
+ start_y = Math.round(e.pageY - $image.eq(0).offset().top);
5681
+
5682
+ this.sync()
5683
+ }
5684
+ }, this)).on('mouseup', function()
5685
+ {
5686
+ isResizing = false;
5687
+ });
5688
+
5689
+
5690
+ this.$editor.on('keydown.redactor-image-delete', $.proxy(function(e)
5691
+ {
5692
+ var key = e.which;
5693
+
5694
+ if (this.keyCode.BACKSPACE == key || this.keyCode.DELETE == key)
5695
+ {
5696
+ this.imageResizeHide(false);
5697
+ this.imageRemove($image);
5698
+ }
5699
+
5700
+ }, this));
5701
+
5702
+ $(document).on('click.redactor-image-resize-hide', $.proxy(this.imageResizeHide, this));
5703
+ this.$editor.on('click.redactor-image-resize-hide', $.proxy(this.imageResizeHide, this));
5704
+
5705
+
5706
+ }, this));
5707
+ },
5708
+ imageResizeControls: function($image)
5709
+ {
5710
+ var imageBox = $('<span id="redactor-image-box" data-redactor="verified">');
5711
+ imageBox.css({
5712
+ position: 'relative',
5713
+ display: 'inline-block',
5714
+ lineHeight: 0,
5715
+ outline: '1px dashed rgba(0, 0, 0, .6)',
5716
+ 'float': $image.css('float')
5717
+ });
5718
+ imageBox.attr('contenteditable', false);
5719
+
5720
+ var margin = $image.css('margin');
5721
+ if (margin != '0px')
5722
+ {
5723
+ imageBox.css('margin', margin);
5724
+ $image.css('margin', '');
5725
+ }
5726
+
5727
+ $image.css('opacity', .5).after(imageBox);
5728
+
5729
+ // editter
5730
+ this.imageEditter = $('<span id="redactor-image-editter" data-redactor="verified">' + this.opts.curLang.edit + '</span>');
5731
+ this.imageEditter.css({
5732
+ position: 'absolute',
5733
+ zIndex: 2,
5734
+ top: '50%',
5735
+ left: '50%',
5736
+ marginTop: '-11px',
5737
+ marginLeft: '-18px',
5738
+ lineHeight: 1,
5739
+ backgroundColor: '#000',
5740
+ color: '#fff',
5741
+ fontSize: '11px',
5742
+ padding: '7px 10px',
5743
+ cursor: 'pointer'
5271
5744
  });
5745
+ this.imageEditter.attr('contenteditable', false);
5746
+ this.imageEditter.on('click', $.proxy(function()
5747
+ {
5748
+ this.imageEdit($image);
5749
+ }, this));
5750
+ imageBox.append(this.imageEditter);
5751
+
5752
+ // resizer
5753
+ var imageResizer = $('<span id="redactor-image-resizer" data-redactor="verified"></span>');
5754
+ imageResizer.css({
5755
+ position: 'absolute',
5756
+ zIndex: 2,
5757
+ lineHeight: 1,
5758
+ cursor: 'nw-resize',
5759
+ bottom: '-4px',
5760
+ right: '-5px',
5761
+ border: '1px solid #fff',
5762
+ backgroundColor: '#000',
5763
+ width: '8px',
5764
+ height: '8px'
5765
+ });
5766
+ imageResizer.attr('contenteditable', false);
5767
+ imageBox.append(imageResizer);
5768
+
5769
+ imageBox.append($image);
5770
+
5771
+ return imageResizer;
5272
5772
  },
5273
5773
  imageThumbClick: function(e)
5274
5774
  {
@@ -5355,6 +5855,7 @@
5355
5855
  + '<input id="redactor_file_alt" class="redactor_input" />'
5356
5856
  + '<label>' + this.opts.curLang.link + '</label>'
5357
5857
  + '<input id="redactor_file_link" class="redactor_input" />'
5858
+ + '<label><input type="checkbox" id="redactor_link_blank"> ' + this.opts.curLang.link_new_tab + '</label>'
5358
5859
  + '<label>' + this.opts.curLang.image_position + '</label>'
5359
5860
  + '<select id="redactor_form_image_align">'
5360
5861
  + '<option value="none">' + this.opts.curLang.none + '</option>'
@@ -5363,8 +5864,8 @@
5363
5864
  + '</select>'
5364
5865
  + '</section>'
5365
5866
  + '<footer>'
5366
- + '<a href="#" id="redactor_image_delete_btn" class="redactor_modal_btn">' + this.opts.curLang._delete + '</a>&nbsp;&nbsp;&nbsp;'
5367
- + '<a href="#" class="redactor_modal_btn redactor_btn_modal_close">' + this.opts.curLang.cancel + '</a>'
5867
+ + '<button id="redactor_image_delete_btn" class="redactor_modal_btn">' + this.opts.curLang._delete + '</button>&nbsp;&nbsp;&nbsp;'
5868
+ + '<button class="redactor_modal_btn redactor_btn_modal_close">' + this.opts.curLang.cancel + '</button>'
5368
5869
  + '<input type="button" name="save" class="redactor_modal_btn" id="redactorSaveBtn" value="' + this.opts.curLang.save + '" />'
5369
5870
  + '</footer>',
5370
5871
 
@@ -5392,7 +5893,7 @@
5392
5893
  + '</div>'
5393
5894
  + '</section>'
5394
5895
  + '<footer>'
5395
- + '<a href="#" class="redactor_modal_btn redactor_btn_modal_close">' + this.opts.curLang.cancel + '</a>'
5896
+ + '<button class="redactor_modal_btn redactor_btn_modal_close">' + this.opts.curLang.cancel + '</button>'
5396
5897
  + '<input type="button" name="upload" class="redactor_modal_btn" id="redactor_upload_btn" value="' + this.opts.curLang.insert + '" />'
5397
5898
  + '</footer>',
5398
5899
 
@@ -5427,7 +5928,7 @@
5427
5928
  + '</form>'
5428
5929
  + '</section>'
5429
5930
  + '<footer>'
5430
- + '<a href="#" class="redactor_modal_btn redactor_btn_modal_close">' + this.opts.curLang.cancel + '</a>'
5931
+ + '<button class="redactor_modal_btn redactor_btn_modal_close">' + this.opts.curLang.cancel + '</button>'
5431
5932
  + '<input type="button" class="redactor_modal_btn" id="redactor_insert_link_btn" value="' + this.opts.curLang.insert + '" />'
5432
5933
  + '</footer>',
5433
5934
 
@@ -5439,7 +5940,7 @@
5439
5940
  + '<input type="text" size="5" value="3" id="redactor_table_columns" />'
5440
5941
  + '</section>'
5441
5942
  + '<footer>'
5442
- + '<a href="#" class="redactor_modal_btn redactor_btn_modal_close">' + this.opts.curLang.cancel + '</a>'
5943
+ + '<button class="redactor_modal_btn redactor_btn_modal_close">' + this.opts.curLang.cancel + '</button>'
5443
5944
  + '<input type="button" name="upload" class="redactor_modal_btn" id="redactor_insert_table_btn" value="' + this.opts.curLang.insert + '" />'
5444
5945
  + '</footer>',
5445
5946
 
@@ -5451,7 +5952,7 @@
5451
5952
  + '</form>'
5452
5953
  + '</section>'
5453
5954
  + '<footer>'
5454
- + '<a href="javascript:void(null);" class="redactor_modal_btn redactor_btn_modal_close">' + this.opts.curLang.cancel + '</a>'
5955
+ + '<button class="redactor_modal_btn redactor_btn_modal_close">' + this.opts.curLang.cancel + '</button>'
5455
5956
  + '<input type="button" class="redactor_modal_btn" id="redactor_insert_video_btn" value="' + this.opts.curLang.insert + '" />'
5456
5957
  + '</footer>'
5457
5958
 
@@ -5549,6 +6050,9 @@
5549
6050
 
5550
6051
  $redactorModal.find('.redactor_btn_modal_close').on('click', $.proxy(this.modalClose, this));
5551
6052
 
6053
+ // save scroll
6054
+ if (this.opts.autoresize === true) this.saveModalScroll = this.document.body.scrollTop;
6055
+
5552
6056
  if (this.isMobile() === false)
5553
6057
  {
5554
6058
  $redactorModal.css({
@@ -5590,7 +6094,7 @@
5590
6094
  minHeight: 'auto',
5591
6095
  marginTop: '-' + (height + 10) / 2 + 'px'
5592
6096
  });
5593
- }, 20 );
6097
+ }, 10);
5594
6098
  }
5595
6099
 
5596
6100
  },
@@ -5619,6 +6123,9 @@
5619
6123
 
5620
6124
  this.selectionRestore();
5621
6125
 
6126
+ // restore scroll
6127
+ if (this.opts.autoresize && this.saveModalScroll) $(this.document.body).scrollTop(this.saveModalScroll);
6128
+
5622
6129
  }, this));
5623
6130
 
5624
6131
 
@@ -5870,7 +6377,7 @@
5870
6377
  }
5871
6378
 
5872
6379
  var oldElement = this.uploadOptions.input;
5873
- var newElement = $( oldElement ).clone();
6380
+ var newElement = $(oldElement).clone();
5874
6381
 
5875
6382
  $(oldElement).attr('id', fileId).before(newElement).appendTo(this.form);
5876
6383
 
@@ -5979,28 +6486,36 @@
5979
6486
 
5980
6487
  this.dropareabox.removeClass('hover').addClass('drop');
5981
6488
 
5982
- //this.handleFileSelect(e);
5983
- this.draguploadUpload(e.dataTransfer.files[0]);
6489
+ this.dragUploadAjax(this.draguploadOptions.url, e.dataTransfer.files[0], false);
5984
6490
 
5985
6491
  }, this );
5986
6492
  },
5987
- draguploadUpload: function(file)
6493
+ dragUploadAjax: function(url, file, directupload, progress, e)
5988
6494
  {
5989
- var xhr = jQuery.ajaxSettings.xhr();
5990
6495
 
5991
- if (xhr.upload)
6496
+
6497
+ if (!directupload)
5992
6498
  {
5993
- xhr.upload.addEventListener('progress', $.proxy(this.uploadProgress, this), false);
5994
- }
6499
+ var xhr = $.ajaxSettings.xhr();
6500
+ if (xhr.upload)
6501
+ {
6502
+ xhr.upload.addEventListener('progress', $.proxy(this.uploadProgress, this), false);
6503
+ }
5995
6504
 
5996
- var provider = function () { return xhr; };
6505
+ $.ajaxSetup({
6506
+ xhr: function () { return xhr; }
6507
+ });
6508
+ }
5997
6509
 
5998
6510
  var fd = new FormData();
5999
6511
 
6512
+ // append file data
6513
+ fd.append('file', file);
6514
+
6000
6515
  // append hidden fields
6001
- if (this.draguploadOptions.uploadFields !== false && typeof this.draguploadOptions.uploadFields === 'object')
6516
+ if (this.opts.uploadFields !== false && typeof this.opts.uploadFields === 'object')
6002
6517
  {
6003
- $.each(this.draguploadOptions.uploadFields, $.proxy(function(k, v)
6518
+ $.each(this.opts.uploadFields, $.proxy(function(k, v)
6004
6519
  {
6005
6520
  if (v != null && v.toString().indexOf('#') === 0) v = $(v).val();
6006
6521
  fd.append(k, v);
@@ -6008,14 +6523,10 @@
6008
6523
  }, this));
6009
6524
  }
6010
6525
 
6011
- // append file data
6012
- fd.append('file', file);
6013
-
6014
6526
  $.ajax({
6015
- url: this.draguploadOptions.url,
6527
+ url: url,
6016
6528
  dataType: 'html',
6017
6529
  data: fd,
6018
- xhr: provider,
6019
6530
  cache: false,
6020
6531
  contentType: false,
6021
6532
  processData: false,
@@ -6027,19 +6538,46 @@
6027
6538
 
6028
6539
  var json = $.parseJSON(data);
6029
6540
 
6030
- if (typeof json.error == 'undefined')
6541
+ if (directupload)
6031
6542
  {
6032
- this.draguploadOptions.success(json);
6543
+ progress.fadeOut('slow', function()
6544
+ {
6545
+ $(this).remove();
6546
+ });
6547
+
6548
+ var $img = $('<img>');
6549
+ $img.attr('src', json.filelink).attr('id', 'drag-image-marker');
6550
+
6551
+ this.insertNodeToCaretPositionFromPoint(e, $img[0]);
6552
+
6553
+ var image = $(this.$editor.find('img#drag-image-marker'));
6554
+ if (image.length) image.removeAttr('id');
6555
+ else image = false;
6556
+
6557
+ this.sync();
6558
+ this.observeImages();
6559
+
6560
+ // upload callback
6561
+ if (image) this.callback('imageUpload', image, json);
6562
+
6563
+ // error callback
6564
+ if (typeof json.error !== 'undefined') this.callback('imageUploadError', json);
6033
6565
  }
6034
6566
  else
6035
6567
  {
6036
- this.draguploadOptions.error(this, json);
6037
- this.draguploadOptions.success(false);
6568
+ if (typeof json.error == 'undefined')
6569
+ {
6570
+ this.draguploadOptions.success(json);
6571
+ }
6572
+ else
6573
+ {
6574
+ this.draguploadOptions.error(this, json);
6575
+ this.draguploadOptions.success(false);
6576
+ }
6038
6577
  }
6039
6578
 
6040
6579
  }, this)
6041
6580
  });
6042
-
6043
6581
  },
6044
6582
  draguploadOndrag: function()
6045
6583
  {
@@ -6080,7 +6618,8 @@
6080
6618
  {
6081
6619
  html = html.replace(/&#x200b;|<br>|<br\/>|&nbsp;/gi, '');
6082
6620
  html = html.replace(/\s/g, '');
6083
- html = html.replace(/^<p>[^\w\d]*?<\/p>$/i, '');
6621
+ html = html.replace(/^<p>[^\W\w\D\d]*?<\/p>$/i, '');
6622
+
6084
6623
 
6085
6624
  return html == '';
6086
6625
  },
@@ -6146,6 +6685,7 @@
6146
6685
  var text = $.trim($(current).text()).replace(/\n\r\n/g, '');
6147
6686
 
6148
6687
  var len = text.length;
6688
+
6149
6689
  if (offset == len) return true;
6150
6690
  else return false;
6151
6691
  },
@@ -6184,10 +6724,13 @@
6184
6724
  Redactor.prototype.init.prototype = Redactor.prototype;
6185
6725
 
6186
6726
  // LINKIFY
6187
- $.Redactor.fn.formatLinkify = function(protocol)
6727
+ $.Redactor.fn.formatLinkify = function(protocol, convertLinks, convertImageLinks, convertVideoLinks)
6188
6728
  {
6189
6729
  var url1 = /(^|&lt;|\s)(www\..+?\..+?)(\s|&gt;|$)/g,
6190
- url2 = /(^|&lt;|\s)(((https?|ftp):\/\/|mailto:).+?)(\s|&gt;|$)/g;
6730
+ url2 = /(^|&lt;|\s)(((https?|ftp):\/\/|mailto:).+?)(\s|&gt;|$)/g,
6731
+ urlImage = /(https?:\/\/.*\.(?:png|jpg|jpeg|gif))/gi,
6732
+ urlYoutube = /^.*(youtu.be\/|v\/|u\/\w\/|embed\/|watch\?v=|\&v=)([^#\&\?]*).*/,
6733
+ urlVimeo = /http:\/\/(www\.)?vimeo.com\/(\d+)($|\/)/;
6191
6734
 
6192
6735
  var childNodes = (this.$editor ? this.$editor.get(0) : this).childNodes, i = childNodes.length;
6193
6736
  while (i--)
@@ -6196,23 +6739,55 @@
6196
6739
  if (n.nodeType === 3)
6197
6740
  {
6198
6741
  var html = n.nodeValue;
6199
- if (html && (html.match(url1) || html.match(url2)))
6742
+
6743
+ // youtube & vimeo
6744
+ if (convertVideoLinks && html)
6200
6745
  {
6746
+ var iframeStart = '<iframe width="500" height="281" src="',
6747
+ iframeEnd = '" frameborder="0" allowfullscreen></iframe>';
6748
+
6749
+ if (html.match(urlYoutube))
6750
+ {
6751
+ html = html.replace(urlYoutube, iframeStart + '//www.youtube.com/embed/$2' + iframeEnd);
6752
+ $(n).after(html).remove();
6753
+ }
6754
+ else if (html.match(urlVimeo))
6755
+ {
6756
+ html = html.replace(urlVimeo, iframeStart + '//player.vimeo.com/video/$2' + iframeEnd);
6757
+ $(n).after(html).remove();
6758
+ }
6759
+ }
6760
+
6761
+ // image
6762
+ if (convertImageLinks && html && html.match(urlImage))
6763
+ {
6764
+ html = html.replace(urlImage, '<img src="$1">');
6765
+
6766
+ $(n).after(html).remove();
6767
+ }
6768
+
6769
+ // link
6770
+ if (convertLinks && html && (html.match(url1) || html.match(url2)))
6771
+ {
6772
+ var href = (html.match(url1) || html.match(url2));
6773
+ href = href[0];
6774
+ if (href.length > 50) href = href.substring(0, 50) + '...';
6775
+
6201
6776
  html = html.replace(/&/g, '&amp;')
6202
6777
  .replace(/</g, '&lt;')
6203
6778
  .replace(/>/g, '&gt;')
6204
- .replace(url1, '$1<a href="' + protocol + '$2">$2</a>$3')
6205
- .replace(url2, '$1<a href="$2">$2</a>$5');
6779
+ .replace(url1, '$1<a href="' + protocol + '$2">' + href + '</a>$3')
6780
+ .replace(url2, '$1<a href="$2">' + href + '</a>$5');
6781
+
6206
6782
 
6207
6783
  $(n).after(html).remove();
6208
6784
  }
6209
6785
  }
6210
6786
  else if (n.nodeType === 1 && !/^(a|button|textarea)$/i.test(n.tagName))
6211
6787
  {
6212
- $.Redactor.fn.formatLinkify.call(n, protocol);
6788
+ $.Redactor.fn.formatLinkify.call(n, protocol, convertLinks, convertImageLinks, convertVideoLinks);
6213
6789
  }
6214
6790
  }
6215
6791
  };
6216
6792
 
6217
-
6218
6793
  })(jQuery);