redactor-rails 0.4 → 0.4.1

Sign up to get free protection for your applications and to get access to all the features.
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);