scrivito_editors 0.0.12 → 0.0.13

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 (26) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +19 -0
  3. data/app/assets/fonts/redactor-font.eot +0 -0
  4. data/app/assets/javascripts/redactor.js +844 -415
  5. data/app/assets/javascripts/scrivito_editors/date_editor.js.coffee +1 -1
  6. data/app/assets/javascripts/scrivito_editors/enum_editor.js.coffee +1 -1
  7. data/app/assets/javascripts/scrivito_editors/html_editor.js.coffee +1 -1
  8. data/app/assets/javascripts/scrivito_editors/image_editor.js.coffee +72 -0
  9. data/app/assets/javascripts/scrivito_editors/link_editor.js.coffee +124 -0
  10. data/app/assets/javascripts/scrivito_editors/linklist_editor.js.coffee +1 -1
  11. data/app/assets/javascripts/scrivito_editors/multienum_editor.js.coffee +1 -1
  12. data/app/assets/javascripts/scrivito_editors/reference_editor.js.coffee +1 -1
  13. data/app/assets/javascripts/scrivito_editors/referencelist_editor.js.coffee +1 -1
  14. data/app/assets/javascripts/scrivito_editors/slider_editor.js.coffee +1 -1
  15. data/app/assets/javascripts/scrivito_editors/string_editor.js.coffee +5 -3
  16. data/app/assets/javascripts/scrivito_editors/text_editor.js.coffee +2 -2
  17. data/app/assets/javascripts/scrivito_editors.js +2 -7
  18. data/app/assets/javascripts/scrivito_editors_addons.js +4 -0
  19. data/app/assets/javascripts/scrivito_editors_core.js +3 -0
  20. data/app/assets/stylesheets/redactor.css.erb +66 -53
  21. data/app/assets/stylesheets/scrivito_editors/editors/image_editor.css +7 -0
  22. data/app/assets/stylesheets/scrivito_editors/editors/link_editor.css +80 -0
  23. data/app/assets/stylesheets/scrivito_editors.css +2 -2
  24. data/lib/scrivito_editors/version.rb +1 -1
  25. metadata +23 -4
  26. data/app/assets/javascripts/jquery_additions/jquery_center.js.coffee +0 -6
@@ -1,6 +1,6 @@
1
1
  /*
2
- Redactor v9.2.1
3
- Updated: Mar 19, 2014
2
+ Redactor v9.2.6
3
+ Updated: Jul 19, 2014
4
4
 
5
5
  http://imperavi.com/redactor/
6
6
 
@@ -9,7 +9,6 @@
9
9
 
10
10
  Usage: $('#content').redactor();
11
11
  */
12
-
13
12
  (function($)
14
13
  {
15
14
  var uuid = 0;
@@ -31,6 +30,9 @@
31
30
  return this[0] === this[1];
32
31
  };
33
32
 
33
+ var reUrlYoutube = /https?:\/\/(?:[0-9A-Z-]+\.)?(?:youtu\.be\/|youtube\.com\S*[^\w\-\s])([\w\-]{11})(?=[^\w\-]|$)(?![?=&+%\w.-]*(?:['"][^<>]*>|<\/a>))[?=&+%\w.-]*/ig;
34
+ var reUrlVimeo = /https?:\/\/(www\.)?vimeo.com\/(\d+)($|\/)/;
35
+
34
36
  // Plugin
35
37
  $.fn.redactor = function(options)
36
38
  {
@@ -71,7 +73,7 @@
71
73
  }
72
74
 
73
75
  $.Redactor = Redactor;
74
- $.Redactor.VERSION = '9.2.1';
76
+ $.Redactor.VERSION = '9.2.6';
75
77
  $.Redactor.opts = {
76
78
 
77
79
  // settings
@@ -84,7 +86,7 @@
84
86
  lang: 'en',
85
87
  direction: 'ltr', // ltr or rtl
86
88
 
87
- placeholder: '',
89
+ placeholder: false,
88
90
 
89
91
  typewriter: false,
90
92
  wym: false,
@@ -104,7 +106,17 @@
104
106
  autoresize: true,
105
107
  minHeight: false,
106
108
  maxHeight: false,
107
- shortcuts: true,
109
+ shortcuts: {
110
+ 'ctrl+m, meta+m': "this.execCommand('removeFormat', false)",
111
+ 'ctrl+b, meta+b': "this.execCommand('bold', false)",
112
+ 'ctrl+i, meta+i': "this.execCommand('italic', false)",
113
+ 'ctrl+h, meta+h': "this.execCommand('superscript', false)",
114
+ 'ctrl+l, meta+l': "this.execCommand('subscript', false)",
115
+ 'ctrl+k, meta+k': "this.linkShow()",
116
+ 'ctrl+shift+7': "this.execCommand('insertorderedlist', false)",
117
+ 'ctrl+shift+8': "this.execCommand('insertunorderedlist', false)"
118
+ },
119
+ shortcutsAdd: false,
108
120
 
109
121
  autosave: false, // false or url
110
122
  autosaveInterval: 60, // seconds
@@ -116,14 +128,16 @@
116
128
  linkProtocol: 'http://',
117
129
  linkNofollow: false,
118
130
  linkSize: 50,
131
+ predefinedLinks: false, // json url (ex. /some-url.json ) or false
119
132
 
120
133
  imageFloatMargin: '10px',
121
- imageGetJson: false, // url (ex. /folder/images.json ) or false
134
+ imageGetJson: false, // json url (ex. /some-images.json ) or false
122
135
 
123
136
  dragUpload: true, // false
124
137
  imageTabLink: true,
125
138
  imageUpload: false, // url
126
139
  imageUploadParam: 'file', // input name
140
+ imageResizable: true,
127
141
 
128
142
  fileUpload: false, // url
129
143
  fileUploadParam: 'file', // input name
@@ -212,6 +226,7 @@
212
226
  'BLOCKQUOTE', 'OUTPUT', 'FIGCAPTION', 'PRE', 'ADDRESS', 'SECTION',
213
227
  'HEADER', 'FOOTER', 'ASIDE', 'ARTICLE', 'TD'],
214
228
 
229
+
215
230
  // lang
216
231
  langs: {
217
232
  en: {
@@ -383,6 +398,12 @@
383
398
  // load lang
384
399
  this.opts.curLang = this.opts.langs[this.opts.lang];
385
400
 
401
+ // extend shortcuts
402
+ $.extend(this.opts.shortcuts, this.opts.shortcutsAdd);
403
+
404
+ // init placeholder
405
+ this.placeholderInit();
406
+
386
407
  // Build
387
408
  this.buildStart();
388
409
 
@@ -802,6 +823,12 @@
802
823
  },
803
824
  setFullpageOnInit: function(html)
804
825
  {
826
+ this.fullpageDoctype = html.match(/^<\!doctype[^>]*>/i);
827
+ if (this.fullpageDoctype && this.fullpageDoctype.length == 1)
828
+ {
829
+ html = html.replace(/^<\!doctype[^>]*>/i, '');
830
+ }
831
+
805
832
  html = this.cleanSavePreCode(html, true);
806
833
  html = this.cleanConverters(html);
807
834
  html = this.cleanEmpty(html);
@@ -814,6 +841,14 @@
814
841
  this.setSpansVerified();
815
842
  this.sync();
816
843
  },
844
+ setFullpageDoctype: function()
845
+ {
846
+ if (this.fullpageDoctype && this.fullpageDoctype.length == 1)
847
+ {
848
+ var source = this.fullpageDoctype[0] + '\n' + this.$source.val();
849
+ this.$source.val(source);
850
+ }
851
+ },
817
852
  setSpansVerified: function()
818
853
  {
819
854
  var spans = this.$editor.find('span');
@@ -866,8 +901,6 @@
866
901
  // do not sync
867
902
  return false;
868
903
  }
869
-
870
-
871
904
  // fix second level up ul, ol
872
905
  html = html.replace(/<\/li><(ul|ol)>([\w\W]*?)<\/(ul|ol)>/gi, '<$1>$2</$1></li>');
873
906
 
@@ -888,6 +921,7 @@
888
921
  html = this.callback('syncBefore', false, html);
889
922
 
890
923
  this.$source.val(html);
924
+ this.setFullpageDoctype();
891
925
 
892
926
  // onchange & after callback
893
927
  this.callback('syncAfter', false, html);
@@ -972,20 +1006,26 @@
972
1006
 
973
1007
  // remove spans
974
1008
  html = html.replace(/<span(.*?)>([\w\W]*?)<\/span>/gi, '$2');
1009
+ html = html.replace(/<inline>([\w\W]*?)<\/inline>/gi, '$1');
975
1010
  html = html.replace(/<inline>/gi, '<span>');
976
1011
  html = html.replace(/<inline /gi, '<span ');
977
1012
  html = html.replace(/<\/inline>/gi, '</span>');
1013
+
1014
+ if (this.opts.removeEmptyTags)
1015
+ {
1016
+ html = html.replace(/<span>([\w\W]*?)<\/span>/gi, '$1');
1017
+ }
1018
+
978
1019
  html = html.replace(/<span(.*?)class="redactor_placeholder"(.*?)>([\w\W]*?)<\/span>/gi, '');
979
- html = html.replace(/<span>([\w\W]*?)<\/span>/gi, '$1');
1020
+ html = html.replace(/<img(.*?)contenteditable="false"(.*?)>/gi, '<img$1$2>');
980
1021
 
981
1022
  // special characters
982
- html = html.replace(/&amp;/gi, '&');
983
- html = html.replace(/™/gi, '&trade;');
984
- html = html.replace(/©/gi, '&copy;');
985
- html = html.replace(/…/gi, '&hellip;');
986
- html = html.replace(/—/gi, '&mdash;');
987
- html = html.replace(/‐/gi, '&dash;');
988
-
1023
+ html = html.replace(/&/gi, '&');
1024
+ html = html.replace(/\u2122/gi, '&trade;');
1025
+ html = html.replace(/\u00a9/gi, '&copy;');
1026
+ html = html.replace(/\u2026/gi, '&hellip;');
1027
+ html = html.replace(/\u2014/gi, '&mdash;');
1028
+ html = html.replace(/\u2010/gi, '&dash;');
989
1029
 
990
1030
  html = this.cleanReConvertProtected(html);
991
1031
 
@@ -993,6 +1033,7 @@
993
1033
  },
994
1034
 
995
1035
 
1036
+
996
1037
  // BUILD
997
1038
  buildStart: function()
998
1039
  {
@@ -1001,7 +1042,6 @@
1001
1042
 
1002
1043
  // container
1003
1044
  this.$box = $('<div class="redactor_box" />');
1004
- this.$box.css('z-index', 100-this.uuid);
1005
1045
 
1006
1046
  // textarea test
1007
1047
  if (this.$source[0].tagName === 'TEXTAREA') this.opts.textareamode = true;
@@ -1175,11 +1215,17 @@
1175
1215
  {
1176
1216
  this.dblEnter = 0;
1177
1217
 
1178
- if (this.opts.dragUpload && this.opts.imageUpload !== false)
1218
+ if (this.opts.dragUpload && (this.opts.imageUpload !== false || this.opts.s3 !== false))
1179
1219
  {
1180
1220
  this.$editor.on('drop.redactor', $.proxy(this.buildEventDrop, this));
1181
1221
  }
1182
1222
 
1223
+ this.$editor.on('click.redactor', $.proxy(function()
1224
+ {
1225
+ this.selectall = false;
1226
+
1227
+ }, this));
1228
+
1183
1229
  this.$editor.on('input.redactor', $.proxy(this.sync, this));
1184
1230
  this.$editor.on('paste.redactor', $.proxy(this.buildEventPaste, this));
1185
1231
  this.$editor.on('keydown.redactor', $.proxy(this.buildEventKeydown, this));
@@ -1233,12 +1279,11 @@
1233
1279
 
1234
1280
  this.bufferSet();
1235
1281
 
1236
- var progress = $('<div id="redactor-progress"><span></span></div>');
1237
- $(document.body).append(progress);
1282
+ this.showProgressBar();
1238
1283
 
1239
1284
  if (this.opts.s3 === false)
1240
1285
  {
1241
- this.dragUploadAjax(this.opts.imageUpload, file, true, progress, e, this.opts.imageUploadParam);
1286
+ this.dragUploadAjax(this.opts.imageUpload, file, true, e, this.opts.imageUploadParam);
1242
1287
  }
1243
1288
  else
1244
1289
  {
@@ -1305,6 +1350,7 @@
1305
1350
  var event = e.originalEvent || e;
1306
1351
  this.clipboardFilePaste = false;
1307
1352
 
1353
+
1308
1354
  if (typeof(event.clipboardData) === 'undefined') return false;
1309
1355
  if (event.clipboardData.items)
1310
1356
  {
@@ -1338,13 +1384,38 @@
1338
1384
 
1339
1385
  this.callback('keydown', e);
1340
1386
 
1341
- // disabling cmd|ctrl + left
1342
- if (this.browser('mozilla') && ctrl && key === 37)
1387
+ /*
1388
+ firefox cmd+left/Cmd+right browser back/forward fix -
1389
+ http://joshrhoderick.wordpress.com/2010/05/05/how-firefoxs-command-key-bug-kills-usability-on-the-mac/
1390
+ */
1391
+ if (this.browser('mozilla') && "modify" in window.getSelection())
1343
1392
  {
1344
- e.preventDefault();
1345
- return false;
1393
+ if ((ctrl) && (e.keyCode===37 || e.keyCode===39))
1394
+ {
1395
+ var selection = this.getSelection();
1396
+ var lineOrWord = (e.metaKey ? "line" : "word");
1397
+ if (e.keyCode===37)
1398
+ {
1399
+ selection.modify("extend","left",lineOrWord);
1400
+ if (!e.shiftKey)
1401
+ {
1402
+ selection.collapseToStart();
1403
+ }
1404
+ }
1405
+ if (e.keyCode===39)
1406
+ {
1407
+ selection.modify("extend","right",lineOrWord);
1408
+ if (!e.shiftKey)
1409
+ {
1410
+ selection.collapseToEnd();
1411
+ }
1412
+ }
1413
+
1414
+ e.preventDefault();
1415
+ }
1346
1416
  }
1347
1417
 
1418
+
1348
1419
  this.imageResizeHide(false);
1349
1420
 
1350
1421
  // pre & down
@@ -1371,7 +1442,7 @@
1371
1442
  }
1372
1443
 
1373
1444
  // shortcuts setup
1374
- if (ctrl && !e.shiftKey) this.shortcuts(e, key);
1445
+ this.shortcuts(e, key);
1375
1446
 
1376
1447
  // buffer setup
1377
1448
  if (ctrl && key === 90 && !e.shiftKey && !e.altKey) // z key
@@ -1408,9 +1479,9 @@
1408
1479
  }
1409
1480
 
1410
1481
  // enter
1411
- if (key == this.keyCode.ENTER && !e.shiftKey && !e.ctrlKey && !e.metaKey )
1482
+ if (key == this.keyCode.ENTER && !e.shiftKey && !e.ctrlKey && !e.metaKey)
1412
1483
  {
1413
- //
1484
+ // remove selected content on enter
1414
1485
  var range = this.getRange();
1415
1486
  if (range && range.collapsed === false)
1416
1487
  {
@@ -1473,11 +1544,43 @@
1473
1544
  }
1474
1545
 
1475
1546
  // pre
1476
- if (pre === true) return this.buildEventKeydownPre(e, current);
1547
+ if (pre === true)
1548
+ {
1549
+ return this.buildEventKeydownPre(e, current);
1550
+ }
1477
1551
  else
1478
1552
  {
1479
1553
  if (!this.opts.linebreaks)
1480
1554
  {
1555
+ // lists exit
1556
+ if (block && block.tagName == 'LI')
1557
+ {
1558
+ var listCurrent = this.getBlock();
1559
+ if (listCurrent !== false || listCurrent.tagName === 'LI')
1560
+ {
1561
+ var listText = $.trim($(block).text());
1562
+ var listCurrentText = $.trim($(listCurrent).text());
1563
+ if (listText == ''
1564
+ && listCurrentText == ''
1565
+ && $(listCurrent).next('li').size() == 0
1566
+ && $(listCurrent).parents('li').size() == 0)
1567
+ {
1568
+ this.bufferSet();
1569
+
1570
+ var $list = $(listCurrent).closest('ol, ul');
1571
+ $(listCurrent).remove();
1572
+ var node = $('<p>' + this.opts.invisibleSpace + '</p>');
1573
+ $list.after(node);
1574
+ this.selectionStart(node);
1575
+
1576
+ this.sync();
1577
+ this.callback('enter', e);
1578
+ return false;
1579
+ }
1580
+ }
1581
+
1582
+ }
1583
+
1481
1584
  // replace div to p
1482
1585
  if (block && this.opts.rBlockTest.test(block.tagName))
1483
1586
  {
@@ -1500,7 +1603,6 @@
1500
1603
  {
1501
1604
  // hit enter
1502
1605
  this.bufferSet();
1503
-
1504
1606
  var node = $('<p>' + this.opts.invisibleSpace + '</p>');
1505
1607
  this.insertNode(node[0]);
1506
1608
  this.selectionStart(node);
@@ -1559,7 +1661,7 @@
1559
1661
  }
1560
1662
 
1561
1663
  // delete zero-width space before the removing
1562
- if (key === this.keyCode.BACKSPACE) this.buildEventKeydownBackspace(current);
1664
+ if (key === this.keyCode.BACKSPACE) this.buildEventKeydownBackspace(e, current, parent);
1563
1665
 
1564
1666
  },
1565
1667
  buildEventKeydownPre: function(e, current)
@@ -1607,8 +1709,23 @@
1607
1709
 
1608
1710
  return false;
1609
1711
  },
1610
- buildEventKeydownBackspace: function(current)
1712
+ buildEventKeydownBackspace: function(e, current, parent)
1611
1713
  {
1714
+ // remove empty list in table
1715
+ if (parent && current && parent.parentNode.tagName == 'TD'
1716
+ && parent.tagName == 'UL' && current.tagName == 'LI' && $(parent).children('li').size() == 1)
1717
+ {
1718
+ var text = $(current).text().replace(/[\u200B-\u200D\uFEFF]/g, '');
1719
+ if (text == '')
1720
+ {
1721
+ var node = parent.parentNode;
1722
+ $(parent).remove();
1723
+ this.selectionStart(node);
1724
+ this.sync();
1725
+ return false;
1726
+ }
1727
+ }
1728
+
1612
1729
  if (typeof current.tagName !== 'undefined' && /^(H[1-6])$/i.test(current.tagName))
1613
1730
  {
1614
1731
  var node;
@@ -1617,15 +1734,15 @@
1617
1734
 
1618
1735
  $(current).replaceWith(node);
1619
1736
  this.selectionStart(node);
1737
+ this.sync();
1620
1738
  }
1621
1739
 
1622
1740
  if (typeof current.nodeValue !== 'undefined' && current.nodeValue !== null)
1623
1741
  {
1624
-
1625
- //var value = $.trim(current.nodeValue.replace(/[^\u0000-\u1C7F]/g, ''));
1626
1742
  if (current.remove && current.nodeType === 3 && current.nodeValue.match(/[^\u200B]/g) == null)
1627
1743
  {
1628
- current.remove();
1744
+ $(current).prev().remove();
1745
+ this.sync();
1629
1746
  }
1630
1747
  }
1631
1748
  },
@@ -1787,7 +1904,10 @@
1787
1904
  // iframe css
1788
1905
  this.iframeAddCss();
1789
1906
 
1790
- if (this.opts.fullpage) this.setFullpageOnInit(this.$editor.html());
1907
+ if (this.opts.fullpage)
1908
+ {
1909
+ this.setFullpageOnInit(this.$source.val());
1910
+ }
1791
1911
  else this.set(this.content, true, false);
1792
1912
 
1793
1913
  this.buildOptions();
@@ -1795,24 +1915,41 @@
1795
1915
  },
1796
1916
 
1797
1917
  // PLACEHOLDER
1798
- placeholderStart: function(html)
1918
+ placeholderInit: function()
1799
1919
  {
1800
- if (this.isEmpty(html))
1920
+ if (this.opts.placeholder !== false)
1921
+ {
1922
+ this.placeholderText = this.opts.placeholder;
1923
+ this.opts.placeholder = true;
1924
+ }
1925
+ else
1801
1926
  {
1802
- if (this.$element.attr('placeholder'))
1927
+ if (typeof this.$element.attr('placeholder') == 'undefined' || this.$element.attr('placeholder') == '')
1803
1928
  {
1804
- this.opts.placeholder = this.$element.attr('placeholder');
1929
+ this.opts.placeholder = false;
1805
1930
  }
1806
-
1807
- if (this.opts.placeholder !== '')
1931
+ else
1808
1932
  {
1809
- this.opts.focus = false;
1810
- this.placeholderOnFocus();
1811
- this.placeholderOnBlur();
1812
-
1813
- return this.placeholderGet();
1933
+ this.placeholderText = this.$element.attr('placeholder');
1934
+ this.opts.placeholder = true;
1814
1935
  }
1815
1936
  }
1937
+ },
1938
+ placeholderStart: function(html)
1939
+ {
1940
+ if (this.opts.placeholder === false)
1941
+ {
1942
+ return false;
1943
+ }
1944
+
1945
+ if (this.isEmpty(html))
1946
+ {
1947
+ this.opts.focus = false;
1948
+ this.placeholderOnFocus();
1949
+ this.placeholderOnBlur();
1950
+
1951
+ return this.placeholderGet();
1952
+ }
1816
1953
  else
1817
1954
  {
1818
1955
  this.placeholderOnBlur();
@@ -1830,7 +1967,14 @@
1830
1967
  },
1831
1968
  placeholderGet: function()
1832
1969
  {
1833
- return $('<span class="redactor_placeholder">').data('redactor', 'verified').attr('contenteditable', false).text(this.opts.placeholder);
1970
+ var ph = $('<span class="redactor_placeholder">').data('redactor', 'verified')
1971
+ .attr('contenteditable', false).text(this.placeholderText);
1972
+
1973
+ if (this.opts.linebreaks === false)
1974
+ {
1975
+ return $('<p>').append(ph);
1976
+ }
1977
+ else return ph;
1834
1978
  },
1835
1979
  placeholderBlur: function()
1836
1980
  {
@@ -1880,32 +2024,98 @@
1880
2024
  shortcuts: function(e, key)
1881
2025
  {
1882
2026
 
2027
+ // disable browser's hot keys for bold and italic
1883
2028
  if (!this.opts.shortcuts)
1884
2029
  {
1885
- if (key === 66 || key === 73) e.preventDefault();
2030
+ if ((e.ctrlKey || e.metaKey) && (key === 66 || key === 73))
2031
+ {
2032
+ e.preventDefault();
2033
+ }
2034
+
1886
2035
  return false;
1887
2036
  }
1888
2037
 
1889
- if (key === 77) this.shortcutsLoad(e, 'removeFormat'); // Ctrl + m
1890
- else if (key === 66) this.shortcutsLoad(e, 'bold'); // Ctrl + b
1891
- else if (key === 73) this.shortcutsLoad(e, 'italic'); // Ctrl + i
2038
+ $.each(this.opts.shortcuts, $.proxy(function(str, command)
2039
+ {
2040
+ var keys = str.split(',');
2041
+ for (var i in keys)
2042
+ {
2043
+ if (typeof keys[i] === 'string')
2044
+ {
2045
+ this.shortcutsHandler(e, $.trim(keys[i]), $.proxy(function()
2046
+ {
2047
+ eval(command);
2048
+ }, this));
2049
+ }
2050
+
2051
+ }
1892
2052
 
1893
- else if (key === 74) this.shortcutsLoad(e, 'insertunorderedlist'); // Ctrl + j
1894
- else if (key === 75) this.shortcutsLoad(e, 'insertorderedlist'); // Ctrl + k
2053
+ }, this));
1895
2054
 
1896
- else if (key === 72) this.shortcutsLoad(e, 'superscript'); // Ctrl + h
1897
- else if (key === 76) this.shortcutsLoad(e, 'subscript'); // Ctrl + l
1898
2055
 
1899
2056
  },
1900
- shortcutsLoad: function(e, cmd)
1901
- {
1902
- e.preventDefault();
1903
- this.execCommand(cmd, false);
1904
- },
1905
- shortcutsLoadFormat: function(e, cmd)
2057
+ shortcutsHandler: function(e, keys, origHandler)
1906
2058
  {
1907
- e.preventDefault();
1908
- this.formatBlocks(cmd);
2059
+ // based on https://github.com/jeresig/jquery.hotkeys
2060
+ var hotkeysSpecialKeys =
2061
+ {
2062
+ 8: "backspace", 9: "tab", 10: "return", 13: "return", 16: "shift", 17: "ctrl", 18: "alt", 19: "pause",
2063
+ 20: "capslock", 27: "esc", 32: "space", 33: "pageup", 34: "pagedown", 35: "end", 36: "home",
2064
+ 37: "left", 38: "up", 39: "right", 40: "down", 45: "insert", 46: "del", 59: ";", 61: "=",
2065
+ 96: "0", 97: "1", 98: "2", 99: "3", 100: "4", 101: "5", 102: "6", 103: "7",
2066
+ 104: "8", 105: "9", 106: "*", 107: "+", 109: "-", 110: ".", 111 : "/",
2067
+ 112: "f1", 113: "f2", 114: "f3", 115: "f4", 116: "f5", 117: "f6", 118: "f7", 119: "f8",
2068
+ 120: "f9", 121: "f10", 122: "f11", 123: "f12", 144: "numlock", 145: "scroll", 173: "-", 186: ";", 187: "=",
2069
+ 188: ",", 189: "-", 190: ".", 191: "/", 192: "`", 219: "[", 220: "\\", 221: "]", 222: "'"
2070
+ };
2071
+
2072
+
2073
+ var hotkeysShiftNums =
2074
+ {
2075
+ "`": "~", "1": "!", "2": "@", "3": "#", "4": "$", "5": "%", "6": "^", "7": "&",
2076
+ "8": "*", "9": "(", "0": ")", "-": "_", "=": "+", ";": ": ", "'": "\"", ",": "<",
2077
+ ".": ">", "/": "?", "\\": "|"
2078
+ };
2079
+
2080
+ keys = keys.toLowerCase().split(" ");
2081
+ var special = hotkeysSpecialKeys[e.keyCode],
2082
+ character = String.fromCharCode( e.which ).toLowerCase(),
2083
+ modif = "", possible = {};
2084
+
2085
+ $.each([ "alt", "ctrl", "meta", "shift"], function(index, specialKey)
2086
+ {
2087
+ if (e[specialKey + 'Key'] && special !== specialKey)
2088
+ {
2089
+ modif += specialKey + '+';
2090
+ }
2091
+ });
2092
+
2093
+
2094
+ if (special)
2095
+ {
2096
+ possible[modif + special] = true;
2097
+ }
2098
+
2099
+ if (character)
2100
+ {
2101
+ possible[modif + character] = true;
2102
+ possible[modif + hotkeysShiftNums[character]] = true;
2103
+
2104
+ // "$" can be triggered as "Shift+4" or "Shift+$" or just "$"
2105
+ if (modif === "shift+")
2106
+ {
2107
+ possible[hotkeysShiftNums[character]] = true;
2108
+ }
2109
+ }
2110
+
2111
+ for (var i = 0, l = keys.length; i < l; i++)
2112
+ {
2113
+ if (possible[keys[i]])
2114
+ {
2115
+ e.preventDefault();
2116
+ return origHandler.apply(this, arguments);
2117
+ }
2118
+ }
1909
2119
  },
1910
2120
 
1911
2121
  // FOCUS
@@ -1984,21 +2194,33 @@
1984
2194
  toggleVisual: function()
1985
2195
  {
1986
2196
  var html = this.$source.hide().val();
1987
-
1988
2197
  if (typeof this.modified !== 'undefined')
1989
2198
  {
1990
- this.modified = this.cleanRemoveSpaces(this.modified, false) !== this.cleanRemoveSpaces(html, false);
2199
+ var modified = this.modified.replace(/\n/g, '');
2200
+
2201
+ var thtml = html.replace(/\n/g, '');
2202
+ thtml = this.cleanRemoveSpaces(thtml, false);
2203
+
2204
+ this.modified = this.cleanRemoveSpaces(modified, false) !== thtml;
1991
2205
  }
1992
2206
 
1993
2207
  if (this.modified)
1994
2208
  {
1995
2209
  // don't remove the iframe even if cleared all.
1996
- if (this.opts.fullpage && html === '') this.setFullpageOnInit(html);
2210
+ if (this.opts.fullpage && html === '')
2211
+ {
2212
+ this.setFullpageOnInit(html);
2213
+ }
1997
2214
  else
1998
2215
  {
1999
2216
  this.set(html);
2000
- if (this.opts.fullpage) this.buildBindKeyboard();
2217
+ if (this.opts.fullpage)
2218
+ {
2219
+ this.buildBindKeyboard();
2220
+ }
2001
2221
  }
2222
+
2223
+ this.callback('change', false, html);
2002
2224
  }
2003
2225
 
2004
2226
  if (this.opts.iframe) this.$frame.show();
@@ -2015,6 +2237,8 @@
2015
2237
  this.buttonActiveVisual();
2016
2238
  this.buttonInactive('html');
2017
2239
  this.opts.visual = true;
2240
+
2241
+
2018
2242
  },
2019
2243
  toggleCode: function(direct)
2020
2244
  {
@@ -2292,6 +2516,8 @@
2292
2516
  {
2293
2517
  if (!this.opts.air) return;
2294
2518
 
2519
+ this.selectionSave();
2520
+
2295
2521
  var left, top;
2296
2522
  $('.redactor_air').hide();
2297
2523
 
@@ -2397,6 +2623,11 @@
2397
2623
  $item = $('<a href="#" class="' + btnObject.className + ' redactor_dropdown_' + btnName + '">' + btnObject.title + '</a>');
2398
2624
  $item.on('click', $.proxy(function(e)
2399
2625
  {
2626
+ if (this.opts.air)
2627
+ {
2628
+ this.selectionRestore();
2629
+ }
2630
+
2400
2631
  if (e.preventDefault) e.preventDefault();
2401
2632
  if (this.browser('msie')) e.returnValue = false;
2402
2633
 
@@ -2407,6 +2638,7 @@
2407
2638
  this.buttonActiveObserver();
2408
2639
  if (this.opts.air) this.$air.fadeOut(100);
2409
2640
 
2641
+
2410
2642
  }, this));
2411
2643
  }
2412
2644
 
@@ -2422,9 +2654,11 @@
2422
2654
  return false;
2423
2655
  }
2424
2656
 
2425
- var $dropdown = this.$toolbar.find('.redactor_dropdown_box_' + key);
2426
2657
  var $button = this.buttonGet(key);
2427
2658
 
2659
+ // Always re-append it to the end of <body> so it always has the highest sub-z-index.
2660
+ var $dropdown = $button.data('dropdown').appendTo(document.body);
2661
+
2428
2662
  if ($button.hasClass('dropact')) this.dropdownHideAll();
2429
2663
  else
2430
2664
  {
@@ -2434,11 +2668,7 @@
2434
2668
  this.buttonActive(key);
2435
2669
  $button.addClass('dropact');
2436
2670
 
2437
- var keyPosition = $button.position();
2438
- if (this.toolbarFixed)
2439
- {
2440
- keyPosition = $button.offset();
2441
- }
2671
+ var keyPosition = $button.offset();
2442
2672
 
2443
2673
  // fix right placement
2444
2674
  var dropdownWidth = $dropdown.width();
@@ -2451,10 +2681,10 @@
2451
2681
  var btnHeight = $button.innerHeight();
2452
2682
 
2453
2683
  var position = 'absolute';
2454
- var top = btnHeight + 'px';
2684
+ var top = (btnHeight + this.opts.toolbarFixedTopOffset) + 'px';
2455
2685
 
2456
2686
  if (this.opts.toolbarFixed && this.toolbarFixed) position = 'fixed';
2457
- else if (!this.opts.air) top = keyPosition.top + btnHeight + 'px';
2687
+ else top = keyPosition.top + btnHeight + 'px';
2458
2688
 
2459
2689
  $dropdown.css({ position: position, left: left, top: top }).show();
2460
2690
  this.callback('dropdownShown', { dropdown: $dropdown, key: key, button: $button });
@@ -2469,6 +2699,8 @@
2469
2699
 
2470
2700
  $(document).one('click', hdlHideDropDown);
2471
2701
  this.$editor.one('click', hdlHideDropDown);
2702
+ this.$editor.one('touchstart', hdlHideDropDown);
2703
+
2472
2704
 
2473
2705
  e.stopPropagation();
2474
2706
  this.focusWithSaveScroll();
@@ -2543,7 +2775,7 @@
2543
2775
  if (btnObject.dropdown)
2544
2776
  {
2545
2777
  var $dropdown = $('<div class="redactor_dropdown redactor_dropdown_box_' + btnName + '" style="display: none;">');
2546
- $dropdown.appendTo(this.$toolbar);
2778
+ $button.data('dropdown', $dropdown);
2547
2779
  this.dropdownBuild($dropdown, btnObject.dropdown);
2548
2780
  }
2549
2781
 
@@ -2681,22 +2913,12 @@
2681
2913
  if ($parent.length)
2682
2914
  {
2683
2915
  var align = $parent.css('text-align');
2684
-
2685
- switch (align)
2916
+ if (align == '')
2686
2917
  {
2687
- case 'right':
2688
- this.buttonActive('alignright');
2689
- break;
2690
- case 'center':
2691
- this.buttonActive('aligncenter');
2692
- break;
2693
- case 'justify':
2694
- this.buttonActive('alignjustify');
2695
- break;
2696
- default:
2697
- this.buttonActive('alignleft');
2698
- break;
2918
+ align = 'left';
2699
2919
  }
2920
+
2921
+ this.buttonActive('align' + align);
2700
2922
  }
2701
2923
  },
2702
2924
 
@@ -2873,7 +3095,8 @@
2873
3095
  }
2874
3096
  else
2875
3097
  {
2876
- data += cloned.html() + '<br>';
3098
+ var clonedHtml = cloned.html().replace(/<br\s?\/?>$/i, '');
3099
+ data += clonedHtml + '<br>';
2877
3100
  }
2878
3101
 
2879
3102
  if (i == 0)
@@ -2899,11 +3122,49 @@
2899
3122
  {
2900
3123
  var firstParent = $(this.getParent()).closest('td');
2901
3124
 
2902
- this.document.execCommand(cmd);
3125
+ if (this.browser('msie') && !this.isIe11() && this.opts.linebreaks)
3126
+ {
3127
+ var wrapper = this.selectionWrap('div');
3128
+ var wrapperHtml = $(wrapper).html();
3129
+ var tmpList = $('<ul>');
3130
+ if (cmd == 'insertorderedlist')
3131
+ {
3132
+ tmpList = $('<ol>');
3133
+ }
3134
+
3135
+ var tmpLi = $('<li>');
3136
+
3137
+ if ($.trim(wrapperHtml) == '')
3138
+ {
3139
+ tmpLi.append(wrapperHtml + '<span id="selection-marker-1">' + this.opts.invisibleSpace + '</span>');
3140
+ tmpList.append(tmpLi);
3141
+ this.$editor.find('#selection-marker-1').replaceWith(tmpList);
3142
+ }
3143
+ else
3144
+ {
3145
+ tmpLi.append(wrapperHtml);
3146
+ tmpList.append(tmpLi);
3147
+ $(wrapper).replaceWith(tmpList);
3148
+ }
3149
+ }
3150
+ else
3151
+ {
3152
+ this.document.execCommand(cmd);
3153
+ }
2903
3154
 
2904
3155
  var parent = this.getParent();
2905
3156
  var $list = $(parent).closest('ol, ul');
2906
3157
 
3158
+ if (this.opts.linebreaks === false)
3159
+ {
3160
+ var listText = $.trim($list.text());
3161
+ if (listText == '')
3162
+ {
3163
+ $list.children('li').find('br').remove();
3164
+ $list.children('li').append('<span id="selection-marker-1">' + this.opts.invisibleSpace + '</span>');
3165
+ }
3166
+ }
3167
+
2907
3168
  if (firstParent.size() != 0)
2908
3169
  {
2909
3170
  $list.wrapAll('<td>');
@@ -2926,7 +3187,7 @@
2926
3187
  }
2927
3188
 
2928
3189
  this.selectionRestore();
2929
-
3190
+ this.$editor.find('#selection-marker-1').removeAttr('id');
2930
3191
  this.sync();
2931
3192
  this.callback('execCommand', cmd, param);
2932
3193
  return;
@@ -3054,7 +3315,7 @@
3054
3315
  // linebreaks
3055
3316
  if (this.opts.linebreaks === true && typeof($el.data('tagblock')) !== 'undefined')
3056
3317
  {
3057
- $el.replaceWith($el.html());
3318
+ $el.replaceWith($el.html() + '<br>');
3058
3319
  }
3059
3320
  // all block tags
3060
3321
  else
@@ -3177,7 +3438,6 @@
3177
3438
  }
3178
3439
 
3179
3440
  this.selectionRestore();
3180
-
3181
3441
  this.sync();
3182
3442
  },
3183
3443
 
@@ -3198,7 +3458,11 @@
3198
3458
  cleanConverters: function(html)
3199
3459
  {
3200
3460
  // convert div to p
3201
- if (this.opts.convertDivs) html = html.replace(/<div(.*?)>([\w\W]*?)<\/div>/gi, '<p$1>$2</p>');
3461
+ if (this.opts.convertDivs && !this.opts.gallery)
3462
+ {
3463
+ html = html.replace(/<div(.*?)>([\w\W]*?)<\/div>/gi, '<p$1>$2</p>');
3464
+ }
3465
+
3202
3466
  if (this.opts.paragraphy) html = this.cleanParagraphy(html);
3203
3467
 
3204
3468
  return html;
@@ -3288,8 +3552,6 @@
3288
3552
  },
3289
3553
  cleanRemoveEmptyTags: function(html)
3290
3554
  {
3291
- html = html.replace(/<span>([\w\W]*?)<\/span>/gi, '$1');
3292
-
3293
3555
  // remove zero width-space
3294
3556
  html = html.replace(/[\u200B-\u200D\uFEFF]/g, '');
3295
3557
 
@@ -3347,6 +3609,7 @@
3347
3609
  }
3348
3610
 
3349
3611
  html = html.replace(/<br \/>\s*<br \/>/gi, "\n\n");
3612
+ html = html.replace(/<br><br>/gi, "\n\n");
3350
3613
 
3351
3614
  function R(str, mod, r)
3352
3615
  {
@@ -3370,7 +3633,7 @@
3370
3633
  {
3371
3634
  if (htmls[i].search('{replace') == -1)
3372
3635
  {
3373
- htmls[i] = htmls[i].replace(/<p>\n\t<\/p>/gi, '');
3636
+ htmls[i] = htmls[i].replace(/<p>\n\t?<\/p>/gi, '');
3374
3637
  htmls[i] = htmls[i].replace(/<p><\/p>/gi, '');
3375
3638
 
3376
3639
  if (htmls[i] != '')
@@ -3435,14 +3698,10 @@
3435
3698
  if (this.opts.italicTag === 'em') html = html.replace(/<i>([\w\W]*?)<\/i>/gi, '<em>$1</em>');
3436
3699
  else html = html.replace(/<em>([\w\W]*?)<\/em>/gi, '<i>$1</i>');
3437
3700
 
3438
- if (set !== true)
3439
- {
3440
- html = html.replace(/<strike>([\w\W]*?)<\/strike>/gi, '<del>$1</del>');
3441
- }
3442
- else
3443
- {
3444
- html = html.replace(/<del>([\w\W]*?)<\/del>/gi, '<strike>$1</strike>');
3445
- }
3701
+ html = html.replace(/<span style="text-decoration: underline;">([\w\W]*?)<\/span>/gi, '<u>$1</u>');
3702
+
3703
+ if (set !== true) html = html.replace(/<strike>([\w\W]*?)<\/strike>/gi, '<del>$1</del>');
3704
+ else html = html.replace(/<del>([\w\W]*?)<\/del>/gi, '<strike>$1</strike>');
3446
3705
 
3447
3706
  return html;
3448
3707
  },
@@ -3514,6 +3773,14 @@
3514
3773
  this.removeEmptyAttr(s, 'style');
3515
3774
  }, this));
3516
3775
 
3776
+ var $elem2 = this.$editor.find('b, strong, i, em, u, strike, del');
3777
+ $elem2.css('font-size', '');
3778
+
3779
+ $.each($elem2, $.proxy(function(i,s)
3780
+ {
3781
+ this.removeEmptyAttr(s, 'style');
3782
+ }, this));
3783
+
3517
3784
  // When we paste text in Safari is wrapping inserted div (remove it)
3518
3785
  this.$editor.find('div[style="text-align: -webkit-auto;"]').contents().unwrap();
3519
3786
 
@@ -3632,7 +3899,7 @@
3632
3899
  }
3633
3900
  }
3634
3901
 
3635
- return this.cleanFinish( out );
3902
+ return this.cleanFinish(out);
3636
3903
  },
3637
3904
  cleanGetTabs: function()
3638
3905
  {
@@ -3646,10 +3913,10 @@
3646
3913
  },
3647
3914
  cleanFinish: function(code)
3648
3915
  {
3649
- code = code.replace( /\n\s*\n/g, '\n' );
3650
- code = code.replace( /^[\s\n]*/, '' );
3651
- code = code.replace( /[\s\n]*$/, '' );
3652
- code = code.replace( /<script(.*?)>\n<\/script>/gi, '<script$1></script>' );
3916
+ code = code.replace(/\n\s*\n/g, '\n');
3917
+ code = code.replace(/^[\s\n]*/, '');
3918
+ code = code.replace(/[\s\n]*$/, '');
3919
+ code = code.replace(/<script(.*?)>\n<\/script>/gi, '<script$1></script>');
3653
3920
 
3654
3921
  this.cleanlevel = 0;
3655
3922
 
@@ -3738,6 +4005,11 @@
3738
4005
  },
3739
4006
  formatBlocks: function(tag)
3740
4007
  {
4008
+ if (this.browser('mozilla') && this.isFocused())
4009
+ {
4010
+ this.$editor.focus();
4011
+ }
4012
+
3741
4013
  this.bufferSet();
3742
4014
 
3743
4015
  var nodes = this.getBlocks();
@@ -3842,6 +4114,11 @@
3842
4114
  // QUOTE
3843
4115
  formatQuote: function()
3844
4116
  {
4117
+ if (this.browser('mozilla') && this.isFocused())
4118
+ {
4119
+ this.$editor.focus();
4120
+ }
4121
+
3845
4122
  this.bufferSet();
3846
4123
 
3847
4124
  // paragraphy
@@ -4081,7 +4358,6 @@
4081
4358
  this.selectionRestore();
4082
4359
  this.sync();
4083
4360
  },
4084
-
4085
4361
  inlineSetClass: function(className)
4086
4362
  {
4087
4363
  var current = this.getCurrent();
@@ -4140,7 +4416,25 @@
4140
4416
  }
4141
4417
  else
4142
4418
  {
4143
- this.document.execCommand('fontSize', false, 4 );
4419
+ var cmd, arg = value;
4420
+ switch (attr)
4421
+ {
4422
+ case 'font-size':
4423
+ cmd = 'fontSize';
4424
+ arg = 4;
4425
+ break;
4426
+ case 'font-family':
4427
+ cmd = 'fontName';
4428
+ break;
4429
+ case 'color':
4430
+ cmd = 'foreColor';
4431
+ break;
4432
+ case 'background-color':
4433
+ cmd = 'backColor';
4434
+ break;
4435
+ }
4436
+
4437
+ this.document.execCommand(cmd, false, arg);
4144
4438
 
4145
4439
  var fonts = this.$editor.find('font');
4146
4440
  $.each(fonts, $.proxy(function(i, s)
@@ -4267,6 +4561,7 @@
4267
4561
  $(el).replaceWith($(el).contents());
4268
4562
  },
4269
4563
 
4564
+
4270
4565
  // INSERT
4271
4566
  insertHtml: function (html, sync)
4272
4567
  {
@@ -4361,9 +4656,9 @@
4361
4656
  var range = sel.getRangeAt(0);
4362
4657
  range.deleteContents();
4363
4658
 
4364
- var el = this.document.createElement('div');
4659
+ var el = document.createElement('div');
4365
4660
  el.innerHTML = html;
4366
- var frag = this.document.createDocumentFragment(), node, lastNode;
4661
+ var frag = document.createDocumentFragment(), node, lastNode;
4367
4662
  while ((node = el.firstChild))
4368
4663
  {
4369
4664
  lastNode = frag.appendChild(node);
@@ -4415,8 +4710,21 @@
4415
4710
 
4416
4711
  this.focusWithSaveScroll();
4417
4712
 
4418
- if (this.browser('msie') && !this.isIe11()) this.document.selection.createRange().pasteHTML(html);
4419
- else this.document.execCommand('inserthtml', false, html);
4713
+ if (this.browser('msie'))
4714
+ {
4715
+ if (!this.isIe11())
4716
+ {
4717
+ this.document.selection.createRange().pasteHTML(html);
4718
+ }
4719
+ else
4720
+ {
4721
+ this.execPasteFrag(html);
4722
+ }
4723
+ }
4724
+ else
4725
+ {
4726
+ this.document.execCommand('inserthtml', false, html);
4727
+ }
4420
4728
 
4421
4729
  this.sync();
4422
4730
  },
@@ -4452,6 +4760,8 @@
4452
4760
  sel.removeAllRanges();
4453
4761
  sel.addRange(range);
4454
4762
  }
4763
+
4764
+ return node;
4455
4765
  },
4456
4766
  insertNodeToCaretPositionFromPoint: function(e, node)
4457
4767
  {
@@ -4671,8 +4981,16 @@
4671
4981
  html = html.replace(/<b\sid="internal-source-marker(.*?)">([\w\W]*?)<\/b>/gi, "$2");
4672
4982
  html = html.replace(/<b(.*?)id="docs-internal-guid(.*?)">([\w\W]*?)<\/b>/gi, "$3");
4673
4983
 
4984
+
4985
+ html = html.replace(/<span[^>]*(font-style: italic; font-weight: bold|font-weight: bold; font-style: italic)[^>]*>/gi, '<span style="font-weight: bold;"><span style="font-style: italic;">');
4986
+ html = html.replace(/<span[^>]*font-style: italic[^>]*>/gi, '<span style="font-style: italic;">');
4987
+ html = html.replace(/<span[^>]*font-weight: bold[^>]*>/gi, '<span style="font-weight: bold;">');
4988
+ html = html.replace(/<span[^>]*text-decoration: underline[^>]*>/gi, '<span style="text-decoration: underline;">');
4989
+
4674
4990
  // strip tags
4675
- html = this.cleanStripTags(html);
4991
+ //html = this.cleanStripTags(html);
4992
+
4993
+
4676
4994
 
4677
4995
  // prevert
4678
4996
  html = html.replace(/<td>\u200b*<\/td>/gi, '[td]');
@@ -4687,6 +5005,7 @@
4687
5005
  html = html.replace(/<embed(.*?)>([\w\W]*?)<\/embed>/gi, '[embed$1]$2[/embed]');
4688
5006
  html = html.replace(/<object(.*?)>([\w\W]*?)<\/object>/gi, '[object$1]$2[/object]');
4689
5007
  html = html.replace(/<param(.*?)>/gi, '[param$1]');
5008
+
4690
5009
  html = html.replace(/<img(.*?)>/gi, '[img$1]');
4691
5010
 
4692
5011
  // remove classes
@@ -4742,6 +5061,9 @@
4742
5061
  html = html.replace(/<div><\/div>/gi, '<br />');
4743
5062
  }
4744
5063
 
5064
+ // strip tags
5065
+ html = this.cleanStripTags(html);
5066
+
4745
5067
  if (this.currentOrParentIs('LI'))
4746
5068
  {
4747
5069
  html = html.replace(/<p>([\w\W]*?)<\/p>/gi, '$1<br>');
@@ -4986,19 +5308,20 @@
4986
5308
  },
4987
5309
 
4988
5310
  // BUFFER
4989
- bufferSet: function(html, selectionSave)
5311
+ bufferSet: function(selectionSave)
4990
5312
  {
4991
- if (html !== undefined || html === false) this.opts.buffer.push(html);
4992
- else
5313
+ if (selectionSave !== false)
4993
5314
  {
4994
- if (selectionSave !== false)
4995
- {
4996
- this.selectionSave();
4997
- }
5315
+ this.selectionSave();
5316
+ }
5317
+
5318
+ this.opts.buffer.push(this.$editor.html());
4998
5319
 
4999
- this.opts.buffer.push(this.$editor.html());
5320
+ if (selectionSave !== false)
5321
+ {
5000
5322
  this.selectionRemoveMarkers('buffer');
5001
5323
  }
5324
+
5002
5325
  },
5003
5326
  bufferUndo: function()
5004
5327
  {
@@ -5046,11 +5369,13 @@
5046
5369
  observeLinks: function()
5047
5370
  {
5048
5371
  this.$editor.find('a').on('click', $.proxy(this.linkObserver, this));
5372
+
5049
5373
  this.$editor.on('click.redactor', $.proxy(function(e)
5050
5374
  {
5051
5375
  this.linkObserverTooltipClose(e);
5052
5376
 
5053
5377
  }, this));
5378
+
5054
5379
  $(document).on('click.redactor', $.proxy(function(e)
5055
5380
  {
5056
5381
  this.linkObserverTooltipClose(e);
@@ -5064,14 +5389,29 @@
5064
5389
  this.$editor.find('img').each($.proxy(function(i, elem)
5065
5390
  {
5066
5391
  if (this.browser('msie')) $(elem).attr('unselectable', 'on');
5067
- this.imageResize(elem);
5392
+
5393
+ var parent = $(elem).parent();
5394
+ if (!parent.hasClass('royalSlider') && !parent.hasClass('fotorama'))
5395
+ {
5396
+ this.imageResize(elem);
5397
+ }
5068
5398
 
5069
5399
  }, this));
5400
+
5401
+ // royalSlider and fotorama
5402
+ this.$editor.find('.fotorama, .royalSlider').on('click', $.proxy(this.editGallery, this));
5403
+
5070
5404
  },
5071
5405
  linkObserver: function(e)
5072
5406
  {
5073
5407
  var $link = $(e.target);
5074
5408
 
5409
+ var parent = $(e.target).parent();
5410
+ if (parent.hasClass('royalSlider') || parent.hasClass('fotorama'))
5411
+ {
5412
+ return;
5413
+ }
5414
+
5075
5415
  if ($link.size() == 0 || $link[0].tagName !== 'A') return;
5076
5416
 
5077
5417
  var pos = $link.offset();
@@ -5399,6 +5739,12 @@
5399
5739
 
5400
5740
  return newnodes;
5401
5741
  },
5742
+ isInlineNode: function(node)
5743
+ {
5744
+ if (node.nodeType != 1) return false;
5745
+
5746
+ return !this.rTestBlock.test(node.nodeName);
5747
+ },
5402
5748
  nodeTestBlocks: function(node)
5403
5749
  {
5404
5750
  return node.nodeType == 1 && this.rTestBlock.test(node.nodeName);
@@ -5552,7 +5898,10 @@
5552
5898
  // SAVE & RESTORE
5553
5899
  selectionSave: function()
5554
5900
  {
5555
- if (!this.isFocused()) this.focusWithSaveScroll();
5901
+ if (!this.isFocused())
5902
+ {
5903
+ this.focusWithSaveScroll();
5904
+ }
5556
5905
 
5557
5906
  if (!this.opts.rangy)
5558
5907
  {
@@ -5589,10 +5938,19 @@
5589
5938
  {
5590
5939
  var boundaryRange = range.cloneRange();
5591
5940
 
5592
- boundaryRange.collapse(type);
5941
+ try {
5942
+ boundaryRange.collapse(type);
5943
+ boundaryRange.insertNode(node);
5944
+ boundaryRange.detach();
5945
+ }
5946
+ catch (e)
5947
+ {
5948
+ var html = this.opts.emptyHtml;
5949
+ if (this.opts.linebreaks) html = '<br>';
5593
5950
 
5594
- boundaryRange.insertNode(node);
5595
- boundaryRange.detach();
5951
+ this.$editor.prepend(html);
5952
+ this.focus();
5953
+ }
5596
5954
  },
5597
5955
  selectionRestore: function(replace, remove)
5598
5956
  {
@@ -5617,6 +5975,7 @@
5617
5975
 
5618
5976
  if (node1.length != 0 && node2.length != 0)
5619
5977
  {
5978
+
5620
5979
  this.selectionSet(node1[0], 0, node2[0], 0);
5621
5980
  }
5622
5981
  else if (node1.length != 0)
@@ -5679,7 +6038,7 @@
5679
6038
  },
5680
6039
  tableInsert: function()
5681
6040
  {
5682
- this.bufferSet(false, false);
6041
+ this.bufferSet(false);
5683
6042
 
5684
6043
  var rows = $('#redactor_table_rows').val(),
5685
6044
  columns = $('#redactor_table_columns').val(),
@@ -5711,6 +6070,11 @@
5711
6070
  $table_box.append($table);
5712
6071
  var html = $table_box.html();
5713
6072
 
6073
+ if (this.opts.linebreaks === false && this.browser('mozilla'))
6074
+ {
6075
+ html += '<p>' + this.opts.invisibleSpace + '</p>';
6076
+ }
6077
+
5714
6078
  this.modalClose();
5715
6079
  this.selectionRestore();
5716
6080
 
@@ -5727,6 +6091,7 @@
5727
6091
  }
5728
6092
  else
5729
6093
  {
6094
+
5730
6095
  this.insertHtmlAdvanced(html, false);
5731
6096
  }
5732
6097
 
@@ -5775,6 +6140,7 @@
5775
6140
 
5776
6141
  $current_tr.remove();
5777
6142
  this.selectionRestore();
6143
+ $table.find('span#selection-marker-1').remove();
5778
6144
  this.sync();
5779
6145
  },
5780
6146
  tableDeleteColumn: function()
@@ -5809,6 +6175,7 @@
5809
6175
  }, this));
5810
6176
 
5811
6177
  this.selectionRestore();
6178
+ $table.find('span#selection-marker-1').remove();
5812
6179
  this.sync();
5813
6180
  },
5814
6181
  tableAddHead: function()
@@ -5935,6 +6302,19 @@
5935
6302
  var data = $('#redactor_insert_video_area').val();
5936
6303
  data = this.cleanStripTags(data);
5937
6304
 
6305
+ // parse if it is link on youtube & vimeo
6306
+ var iframeStart = '<iframe width="500" height="281" src="',
6307
+ iframeEnd = '" frameborder="0" allowfullscreen></iframe>';
6308
+
6309
+ if (data.match(reUrlYoutube))
6310
+ {
6311
+ data = data.replace(reUrlYoutube, iframeStart + '//www.youtube.com/embed/$1' + iframeEnd);
6312
+ }
6313
+ else if (data.match(reUrlVimeo))
6314
+ {
6315
+ data = data.replace(reUrlVimeo, iframeStart + '//player.vimeo.com/video/$2' + iframeEnd);
6316
+ }
6317
+
5938
6318
  this.selectionRestore();
5939
6319
 
5940
6320
  var current = this.getBlock() || this.getCurrent();
@@ -5954,6 +6334,40 @@
5954
6334
 
5955
6335
  var callback = $.proxy(function()
5956
6336
  {
6337
+ // Predefined links
6338
+ if (this.opts.predefinedLinks !== false)
6339
+ {
6340
+ this.predefinedLinksStorage = {};
6341
+ var that = this;
6342
+ $.getJSON(this.opts.predefinedLinks, function(data)
6343
+ {
6344
+ var $select = $('#redactor-predefined-links');
6345
+ $select .html('');
6346
+ $.each(data, function(key, val)
6347
+ {
6348
+ that.predefinedLinksStorage[key] = val;
6349
+ $select.append($('<option>').val(key).html(val.name));
6350
+ });
6351
+
6352
+ $select.on('change', function()
6353
+ {
6354
+ var key = $(this).val();
6355
+ var name = '', url = '';
6356
+ if (key != 0)
6357
+ {
6358
+ name = that.predefinedLinksStorage[key].name;
6359
+ url = that.predefinedLinksStorage[key].url;
6360
+ }
6361
+
6362
+ $('#redactor_link_url').val(url);
6363
+ $('#redactor_link_url_text').val(name);
6364
+
6365
+ });
6366
+
6367
+ $select.show();
6368
+ });
6369
+ }
6370
+
5957
6371
  this.insert_link_node = false;
5958
6372
 
5959
6373
  var sel = this.getSelection();
@@ -5999,7 +6413,8 @@
5999
6413
  }
6000
6414
 
6001
6415
  this.linkInsertPressed = false;
6002
- $('#redactor_insert_link_btn').click($.proxy(this.linkProcess, this));
6416
+ $('#redactor_insert_link_btn').on('click', $.proxy(this.linkProcess, this));
6417
+
6003
6418
 
6004
6419
  setTimeout(function()
6005
6420
  {
@@ -6026,7 +6441,7 @@
6026
6441
  var text = $('#redactor_link_url_text').val();
6027
6442
 
6028
6443
  // mailto
6029
- if (link.search('@') != -1)
6444
+ if (link.search('@') != -1 && /(http|ftp|https):\/\//i.test(link) === false)
6030
6445
  {
6031
6446
  link = 'mailto:' + link;
6032
6447
  }
@@ -6200,6 +6615,7 @@
6200
6615
  // json
6201
6616
  if (this.opts.imageGetJson)
6202
6617
  {
6618
+
6203
6619
  $.getJSON(this.opts.imageGetJson, $.proxy(function(data)
6204
6620
  {
6205
6621
  var folders = {}, count = 0;
@@ -6261,7 +6677,7 @@
6261
6677
  }
6262
6678
  else
6263
6679
  {
6264
- $('#redactor-modal-tab-2').remove();
6680
+ $('#redactor-tab-control-2').remove();
6265
6681
  }
6266
6682
 
6267
6683
  if (this.opts.imageUpload || this.opts.s3)
@@ -6316,8 +6732,8 @@
6316
6732
  }
6317
6733
  else
6318
6734
  {
6319
- $('#redactor-modal-tab-1').remove();
6320
- $('#redactor-modal-tab-2').addClass('redactor_tabs_act');
6735
+ $('#redactor-tab-control-1').remove();
6736
+ $('#redactor-tab-control-2').addClass('redactor_tabs_act');
6321
6737
  $('#redactor_tab2').show();
6322
6738
  }
6323
6739
  }
@@ -6425,6 +6841,8 @@
6425
6841
  },
6426
6842
  imageSave: function(el)
6427
6843
  {
6844
+ this.imageResizeHide(false);
6845
+
6428
6846
  var $el = $(el);
6429
6847
  var parent = $el.parent();
6430
6848
 
@@ -6433,8 +6851,6 @@
6433
6851
  var floating = $('#redactor_form_image_align').val();
6434
6852
  var margin = '';
6435
6853
 
6436
- this.imageResizeHide(false);
6437
-
6438
6854
  if (floating === 'left')
6439
6855
  {
6440
6856
  margin = '0 ' + this.opts.imageFloatMargin + ' ' + this.opts.imageFloatMargin + ' 0';
@@ -6581,63 +6997,66 @@
6581
6997
 
6582
6998
  // resize
6583
6999
  var isResizing = false;
6584
- imageResizer.on('mousedown', function(e)
7000
+ if (imageResizer !== false)
6585
7001
  {
6586
- isResizing = true;
6587
- e.preventDefault();
7002
+ imageResizer.on('mousedown', function(e)
7003
+ {
7004
+ isResizing = true;
7005
+ e.preventDefault();
6588
7006
 
6589
- ratio = $image.width() / $image.height();
7007
+ ratio = $image.width() / $image.height();
6590
7008
 
6591
- start_x = Math.round(e.pageX - $image.eq(0).offset().left);
6592
- start_y = Math.round(e.pageY - $image.eq(0).offset().top);
7009
+ start_x = Math.round(e.pageX - $image.eq(0).offset().left);
7010
+ start_y = Math.round(e.pageY - $image.eq(0).offset().top);
6593
7011
 
6594
- });
7012
+ });
6595
7013
 
6596
- $(this.document.body).on('mousemove', $.proxy(function(e)
6597
- {
6598
- if (isResizing)
7014
+ $(this.document.body).on('mousemove', $.proxy(function(e)
6599
7015
  {
6600
- var mouse_x = Math.round(e.pageX - $image.eq(0).offset().left) - start_x;
6601
- var mouse_y = Math.round(e.pageY - $image.eq(0).offset().top) - start_y;
6602
-
6603
- var div_h = $image.height();
7016
+ if (isResizing)
7017
+ {
7018
+ var mouse_x = Math.round(e.pageX - $image.eq(0).offset().left) - start_x;
7019
+ var mouse_y = Math.round(e.pageY - $image.eq(0).offset().top) - start_y;
6604
7020
 
6605
- var new_h = parseInt(div_h, 10) + mouse_y;
6606
- var new_w = Math.round(new_h * ratio);
7021
+ var div_h = $image.height();
6607
7022
 
6608
- if (new_w > min_w)
6609
- {
6610
- $image.width(new_w);
7023
+ var new_h = parseInt(div_h, 10) + mouse_y;
7024
+ var new_w = Math.round(new_h * ratio);
6611
7025
 
6612
- if (new_w < 100)
6613
- {
6614
- this.imageEditter.css({
6615
- marginTop: '-7px',
6616
- marginLeft: '-13px',
6617
- fontSize: '9px',
6618
- padding: '3px 5px'
6619
- });
6620
- }
6621
- else
7026
+ if (new_w > min_w)
6622
7027
  {
6623
- this.imageEditter.css({
6624
- marginTop: '-11px',
6625
- marginLeft: '-18px',
6626
- fontSize: '11px',
6627
- padding: '7px 10px'
6628
- });
7028
+ $image.width(new_w);
7029
+
7030
+ if (new_w < 100)
7031
+ {
7032
+ this.imageEditter.css({
7033
+ marginTop: '-7px',
7034
+ marginLeft: '-13px',
7035
+ fontSize: '9px',
7036
+ padding: '3px 5px'
7037
+ });
7038
+ }
7039
+ else
7040
+ {
7041
+ this.imageEditter.css({
7042
+ marginTop: '-11px',
7043
+ marginLeft: '-18px',
7044
+ fontSize: '11px',
7045
+ padding: '7px 10px'
7046
+ });
7047
+ }
6629
7048
  }
6630
- }
6631
7049
 
6632
- start_x = Math.round(e.pageX - $image.eq(0).offset().left);
6633
- start_y = Math.round(e.pageY - $image.eq(0).offset().top);
7050
+ start_x = Math.round(e.pageX - $image.eq(0).offset().left);
7051
+ start_y = Math.round(e.pageY - $image.eq(0).offset().top);
6634
7052
 
6635
- this.sync()
6636
- }
6637
- }, this)).on('mouseup', function()
6638
- {
6639
- isResizing = false;
6640
- });
7053
+ this.sync()
7054
+ }
7055
+ }, this)).on('mouseup', function()
7056
+ {
7057
+ isResizing = false;
7058
+ });
7059
+ }
6641
7060
 
6642
7061
 
6643
7062
  this.$editor.on('keydown.redactor-image-delete', $.proxy(function(e)
@@ -6646,7 +7065,7 @@
6646
7065
 
6647
7066
  if (this.keyCode.BACKSPACE == key || this.keyCode.DELETE == key)
6648
7067
  {
6649
- this.bufferSet(false, false);
7068
+ this.bufferSet(false);
6650
7069
  this.imageResizeHide(false);
6651
7070
  this.imageRemove($image);
6652
7071
  }
@@ -6713,25 +7132,34 @@
6713
7132
  imageBox.append(this.imageEditter);
6714
7133
 
6715
7134
  // resizer
6716
- var imageResizer = $('<span id="redactor-image-resizer" data-redactor="verified"></span>');
6717
- imageResizer.css({
6718
- position: 'absolute',
6719
- zIndex: 2,
6720
- lineHeight: 1,
6721
- cursor: 'nw-resize',
6722
- bottom: '-4px',
6723
- right: '-5px',
6724
- border: '1px solid #fff',
6725
- backgroundColor: '#000',
6726
- width: '8px',
6727
- height: '8px'
6728
- });
6729
- imageResizer.attr('contenteditable', false);
6730
- imageBox.append(imageResizer);
7135
+ if (this.opts.imageResizable)
7136
+ {
7137
+ var imageResizer = $('<span id="redactor-image-resizer" data-redactor="verified"></span>');
7138
+ imageResizer.css({
7139
+ position: 'absolute',
7140
+ zIndex: 2,
7141
+ lineHeight: 1,
7142
+ cursor: 'nw-resize',
7143
+ bottom: '-4px',
7144
+ right: '-5px',
7145
+ border: '1px solid #fff',
7146
+ backgroundColor: '#000',
7147
+ width: '8px',
7148
+ height: '8px'
7149
+ });
7150
+ imageResizer.attr('contenteditable', false);
7151
+ imageBox.append(imageResizer);
7152
+
7153
+ imageBox.append($image);
6731
7154
 
6732
- imageBox.append($image);
7155
+ return imageResizer;
7156
+ }
7157
+ else
7158
+ {
7159
+ imageBox.append($image);
6733
7160
 
6734
- return imageResizer;
7161
+ return false;
7162
+ }
6735
7163
  },
6736
7164
  imageThumbClick: function(e)
6737
7165
  {
@@ -6799,6 +7227,24 @@
6799
7227
  this.observeImages();
6800
7228
  },
6801
7229
 
7230
+ // PROGRESS BAR
7231
+ buildProgressBar: function()
7232
+ {
7233
+ if ($('#redactor-progress').size() != 0) return;
7234
+
7235
+ this.$progressBar = $('<div id="redactor-progress"><span></span></div>');
7236
+ $(document.body).append(this.$progressBar);
7237
+ },
7238
+ showProgressBar: function()
7239
+ {
7240
+ this.buildProgressBar();
7241
+ $('#redactor-progress').fadeIn();
7242
+ },
7243
+ hideProgressBar: function()
7244
+ {
7245
+ $('#redactor-progress').fadeOut(1500);
7246
+ },
7247
+
6802
7248
  // MODAL
6803
7249
  modalTemplatesInit: function()
6804
7250
  {
@@ -6806,7 +7252,6 @@
6806
7252
  {
6807
7253
  modal_file: String()
6808
7254
  + '<section id="redactor-modal-file-insert">'
6809
- + '<div id="redactor-progress" class="redactor-progress-inline" style="display: none;"><span></span></div>'
6810
7255
  + '<form id="redactorUploadFileForm" method="post" action="" enctype="multipart/form-data">'
6811
7256
  + '<label>' + this.opts.curLang.filename + '</label>'
6812
7257
  + '<input type="text" id="redactor_filename" class="redactor_input" />'
@@ -6844,7 +7289,6 @@
6844
7289
  + '<a href="#" id="redactor-tab-control-2">' + this.opts.curLang.choose + '</a>'
6845
7290
  + '<a href="#" id="redactor-tab-control-3">' + this.opts.curLang.link + '</a>'
6846
7291
  + '</div>'
6847
- + '<div id="redactor-progress" class="redactor-progress-inline" style="display: none;"><span></span></div>'
6848
7292
  + '<form id="redactorInsertImageForm" method="post" action="" enctype="multipart/form-data">'
6849
7293
  + '<div id="redactor_tab1" class="redactor_tab">'
6850
7294
  + '<input type="file" id="redactor_file" name="' + this.opts.imageUploadParam + '" />'
@@ -6865,6 +7309,7 @@
6865
7309
 
6866
7310
  modal_link: String()
6867
7311
  + '<section id="redactor-modal-link-insert">'
7312
+ + '<select id="redactor-predefined-links" style="width: 99.5%; display: none;"></select>'
6868
7313
  + '<label>URL</label>'
6869
7314
  + '<input type="text" class="redactor_input" id="redactor_link_url" />'
6870
7315
  + '<label>' + this.opts.curLang.text + '</label>'
@@ -6904,44 +7349,107 @@
6904
7349
  },
6905
7350
  modalInit: function(title, content, width, callback)
6906
7351
  {
6907
- var $redactorModalOverlay = $('#redactor_modal_overlay');
7352
+ this.modalSetOverlay();
6908
7353
 
6909
- // modal overlay
6910
- if (!$redactorModalOverlay.length)
7354
+ this.$redactorModalWidth = width;
7355
+ this.$redactorModal = $('#redactor_modal');
7356
+
7357
+ if (!this.$redactorModal.length)
6911
7358
  {
6912
- this.$overlay = $redactorModalOverlay = $('<div id="redactor_modal_overlay" style="display: none;"></div>');
6913
- $('body').prepend(this.$overlay);
7359
+ this.$redactorModal = $('<div id="redactor_modal" style="display: none;" />');
7360
+ this.$redactorModal.append($('<div id="redactor_modal_close">&times;</div>'));
7361
+ this.$redactorModal.append($('<header id="redactor_modal_header" />'));
7362
+ this.$redactorModal.append($('<div id="redactor_modal_inner" />'));
7363
+ this.$redactorModal.appendTo(document.body);
6914
7364
  }
6915
7365
 
6916
- if (this.opts.modalOverlay)
7366
+ $('#redactor_modal_close').on('click', $.proxy(this.modalClose, this));
7367
+ $(document).on('keyup', $.proxy(this.modalCloseHandler, this));
7368
+ this.$editor.on('keyup', $.proxy(this.modalCloseHandler, this));
7369
+
7370
+ this.modalSetContent(content);
7371
+ this.modalSetTitle(title);
7372
+ this.modalSetDraggable();
7373
+ this.modalLoadTabs();
7374
+ this.modalOnCloseButton();
7375
+ this.modalSetButtonsWidth();
7376
+
7377
+ this.saveModalScroll = this.document.body.scrollTop;
7378
+ if (this.opts.autoresize === false)
6917
7379
  {
6918
- $redactorModalOverlay.show().on('click', $.proxy(this.modalClose, this));
7380
+ this.saveModalScroll = this.$editor.scrollTop();
6919
7381
  }
6920
7382
 
6921
- var $redactorModal = $('#redactor_modal');
7383
+ if (this.isMobile() === false) this.modalShowOnDesktop();
7384
+ else this.modalShowOnMobile();
6922
7385
 
6923
- if (!$redactorModal.length)
7386
+ // modal actions callback
7387
+ if (typeof callback === 'function')
6924
7388
  {
6925
- this.$modal = $redactorModal = $('<div id="redactor_modal" style="display: none;"><div id="redactor_modal_close">&times;</div><header id="redactor_modal_header"></header><div id="redactor_modal_inner"></div></div>');
6926
- $('body').append(this.$modal);
7389
+ callback();
6927
7390
  }
6928
7391
 
6929
- $('#redactor_modal_close').on('click', $.proxy(this.modalClose, this));
7392
+ // modal shown callback
7393
+ setTimeout($.proxy(function()
7394
+ {
7395
+ this.callback('modalOpened', this.$redactorModal);
7396
+
7397
+ }, this), 11);
7398
+
7399
+ // fix bootstrap modal focus
7400
+ $(document).off('focusin.modal');
6930
7401
 
6931
- this.hdlModalClose = $.proxy(function(e)
7402
+ // enter
7403
+ this.$redactorModal.find('input[type=text]').on('keypress', $.proxy(function(e)
6932
7404
  {
6933
- if (e.keyCode === this.keyCode.ESC)
7405
+ if (e.which === 13)
6934
7406
  {
6935
- this.modalClose();
6936
- return false;
7407
+ this.$redactorModal.find('.redactor_modal_action_btn').click();
7408
+ e.preventDefault();
6937
7409
  }
7410
+ }, this));
6938
7411
 
6939
- }, this);
7412
+ return this.$redactorModal;
6940
7413
 
6941
- $(document).keyup(this.hdlModalClose);
6942
- this.$editor.keyup(this.hdlModalClose);
7414
+ },
7415
+ modalShowOnDesktop: function()
7416
+ {
7417
+ this.$redactorModal.css({
7418
+ position: 'fixed',
7419
+ top: '-2000px',
7420
+ left: '50%',
7421
+ width: this.$redactorModalWidth + 'px',
7422
+ marginLeft: '-' + (this.$redactorModalWidth / 2) + 'px'
7423
+ }).show();
6943
7424
 
6944
- // set content
7425
+ this.modalSaveBodyOveflow = $(document.body).css('overflow');
7426
+ $(document.body).css('overflow', 'hidden');
7427
+
7428
+ setTimeout($.proxy(function()
7429
+ {
7430
+ var height = this.$redactorModal.outerHeight();
7431
+ this.$redactorModal.css({
7432
+ top: '50%',
7433
+ height: 'auto',
7434
+ minHeight: 'auto',
7435
+ marginTop: '-' + (height + 10) / 2 + 'px'
7436
+ });
7437
+ }, this), 15);
7438
+ },
7439
+ modalShowOnMobile: function()
7440
+ {
7441
+ this.$redactorModal.css({
7442
+ position: 'fixed',
7443
+ width: '100%',
7444
+ height: '100%',
7445
+ top: '0',
7446
+ left: '0',
7447
+ margin: '0',
7448
+ minHeight: '300px'
7449
+ }).show();
7450
+ },
7451
+ modalSetContent: function(content)
7452
+ {
6945
7453
  this.modalcontent = false;
6946
7454
  if (content.indexOf('#') == 0)
6947
7455
  {
@@ -6954,129 +7462,81 @@
6954
7462
  {
6955
7463
  $('#redactor_modal_inner').empty().append(content);
6956
7464
  }
6957
-
6958
- $redactorModal.find('#redactor_modal_header').html(title);
6959
-
6960
- // draggable
6961
- if (typeof $.fn.draggable !== 'undefined')
6962
- {
6963
- $redactorModal.draggable({ handle: '#redactor_modal_header' });
6964
- $redactorModal.find('#redactor_modal_header').css('cursor', 'move');
6965
- }
6966
-
6967
- var $redactor_tabs = $('#redactor_tabs');
6968
-
6969
- // tabs
6970
- if ($redactor_tabs.length )
6971
- {
6972
- var that = this;
6973
- $redactor_tabs.find('a').each(function(i, s)
6974
- {
6975
- i++;
6976
- $(s).on('click', function(e)
6977
- {
6978
- e.preventDefault();
6979
-
6980
- $redactor_tabs.find('a').removeClass('redactor_tabs_act');
6981
- $(this).addClass('redactor_tabs_act');
6982
- $('.redactor_tab').hide();
6983
- $('#redactor_tab' + i ).show();
6984
- $('#redactor_tab_selected').val(i);
6985
-
6986
- if (that.isMobile() === false)
6987
- {
6988
- var height = $redactorModal.outerHeight();
6989
- $redactorModal.css('margin-top', '-' + (height + 10) / 2 + 'px');
6990
- }
6991
- });
6992
- });
6993
- }
6994
-
6995
- $redactorModal.find('.redactor_btn_modal_close').on('click', $.proxy(this.modalClose, this));
6996
-
6997
- var buttons = $redactorModal.find('footer button');
7465
+ },
7466
+ modalSetTitle: function(title)
7467
+ {
7468
+ this.$redactorModal.find('#redactor_modal_header').html(title);
7469
+ },
7470
+ modalSetButtonsWidth: function()
7471
+ {
7472
+ var buttons = this.$redactorModal.find('footer button').not('.redactor_modal_btn_hidden');
6998
7473
  var buttonsSize = buttons.size();
6999
7474
  if (buttonsSize > 0)
7000
7475
  {
7001
- $(buttons).css('width', (width/buttonsSize) + 'px')
7002
- }
7003
-
7004
- // save scroll
7005
- if (this.opts.autoresize === true)
7006
- {
7007
- this.saveModalScroll = this.document.body.scrollTop;
7008
- }
7009
- else
7010
- {
7011
- this.saveModalScroll = this.$editor.scrollTop();
7476
+ $(buttons).css('width', (this.$redactorModalWidth/buttonsSize) + 'px')
7012
7477
  }
7013
-
7014
- if (this.isMobile() === false)
7478
+ },
7479
+ modalOnCloseButton: function()
7480
+ {
7481
+ this.$redactorModal.find('.redactor_btn_modal_close').on('click', $.proxy(this.modalClose, this));
7482
+ },
7483
+ modalSetOverlay: function()
7484
+ {
7485
+ if (this.opts.modalOverlay)
7015
7486
  {
7016
- $redactorModal.css({
7017
- position: 'fixed',
7018
- top: '-2000px',
7019
- left: '50%',
7020
- width: width + 'px',
7021
- marginLeft: '-' + (width / 2) + 'px'
7022
- }).show();
7023
-
7024
- this.modalSaveBodyOveflow = $(document.body).css('overflow');
7025
- $(document.body).css('overflow', 'hidden');
7487
+ this.$redactorModalOverlay = $('#redactor_modal_overlay');
7488
+ if (!this.$redactorModalOverlay.length)
7489
+ {
7490
+ this.$redactorModalOverlay = $('<div id="redactor_modal_overlay" style="display: none;"></div>');
7491
+ $('body').prepend(this.$redactorModalOverlay);
7492
+ }
7026
7493
 
7494
+ this.$redactorModalOverlay.show().on('click', $.proxy(this.modalClose, this));
7027
7495
  }
7028
- else
7496
+ },
7497
+ modalSetDraggable: function()
7498
+ {
7499
+ if (typeof $.fn.draggable !== 'undefined')
7029
7500
  {
7030
- $redactorModal.css({
7031
- position: 'fixed',
7032
- width: '100%',
7033
- height: '100%',
7034
- top: '0',
7035
- left: '0',
7036
- margin: '0',
7037
- minHeight: '300px'
7038
- }).show();
7501
+ this.$redactorModal.draggable({ handle: '#redactor_modal_header' });
7502
+ this.$redactorModal.find('#redactor_modal_header').css('cursor', 'move');
7039
7503
  }
7504
+ },
7505
+ modalLoadTabs: function()
7506
+ {
7507
+ var $redactor_tabs = $('#redactor_tabs');
7508
+ if (!$redactor_tabs.length) return false;
7040
7509
 
7041
- // modal actions callback
7042
- if (typeof callback === 'function')
7510
+ var that = this;
7511
+ $redactor_tabs.find('a').each(function(i, s)
7043
7512
  {
7044
- callback();
7045
- }
7046
-
7047
- // modal shown callback
7048
- setTimeout($.proxy(function()
7049
- {
7050
- this.callback('modalOpened');
7513
+ i++;
7514
+ $(s).on('click', function(e)
7515
+ {
7516
+ e.preventDefault();
7051
7517
 
7052
- }, this), 11);
7518
+ $redactor_tabs.find('a').removeClass('redactor_tabs_act');
7519
+ $(this).addClass('redactor_tabs_act');
7520
+ $('.redactor_tab').hide();
7521
+ $('#redactor_tab' + i ).show();
7522
+ $('#redactor_tab_selected').val(i);
7053
7523
 
7054
- // fix bootstrap modal focus
7055
- $(document).off('focusin.modal');
7524
+ if (that.isMobile() === false)
7525
+ {
7526
+ var height = that.$redactorModal.outerHeight();
7527
+ that.$redactorModal.css('margin-top', '-' + (height + 10) / 2 + 'px');
7528
+ }
7529
+ });
7530
+ });
7056
7531
 
7057
- if (this.isMobile() === false)
7532
+ },
7533
+ modalCloseHandler: function(e)
7534
+ {
7535
+ if (e.keyCode === this.keyCode.ESC)
7058
7536
  {
7059
- setTimeout(function()
7060
- {
7061
- var height = $redactorModal.outerHeight();
7062
- $redactorModal.css({
7063
- top: '50%',
7064
- height: 'auto',
7065
- minHeight: 'auto',
7066
- marginTop: '-' + (height + 10) / 2 + 'px'
7067
- });
7068
- }, 10);
7537
+ this.modalClose();
7538
+ return false;
7069
7539
  }
7070
-
7071
- $redactorModal.find('input[type=text]').keypress(function(e)
7072
- {
7073
- if (e.which === 13 )
7074
- {
7075
- $redactorModal.find('.redactor_modal_action_btn').click();
7076
- e.preventDefault();
7077
- }
7078
- });
7079
-
7080
7540
  },
7081
7541
  modalClose: function()
7082
7542
  {
@@ -7098,8 +7558,8 @@
7098
7558
  $('#redactor_modal_overlay').hide().off('click', this.modalClose);
7099
7559
  }
7100
7560
 
7101
- $(document).unbind('keyup', this.hdlModalClose);
7102
- this.$editor.unbind('keyup', this.hdlModalClose);
7561
+ $(document).off('keyup', this.modalCloseHandler);
7562
+ this.$editor.off('keyup', this.modalCloseHandler);
7103
7563
 
7104
7564
  this.selectionRestore();
7105
7565
 
@@ -7161,11 +7621,12 @@
7161
7621
  // Hack to pass bytes through unprocessed.
7162
7622
  if (xhr.overrideMimeType) xhr.overrideMimeType('text/plain; charset=x-user-defined');
7163
7623
 
7624
+ var that = this;
7164
7625
  xhr.onreadystatechange = function(e)
7165
7626
  {
7166
7627
  if (this.readyState == 4 && this.status == 200)
7167
7628
  {
7168
- $('#redactor-progress').fadeIn();
7629
+ that.showProgressBar();
7169
7630
  callback(decodeURIComponent(this.responseText));
7170
7631
  }
7171
7632
  else if(this.readyState == 4 && this.status != 200)
@@ -7210,7 +7671,7 @@
7210
7671
  {
7211
7672
  //setProgress(100, 'Upload completed.');
7212
7673
 
7213
- $('#redactor-progress, #redactor-progress-drag').hide();
7674
+ this.hideProgressBar();
7214
7675
 
7215
7676
  var s3image = url.split('?');
7216
7677
 
@@ -7320,7 +7781,7 @@
7320
7781
  },
7321
7782
  uploadSubmit: function(e)
7322
7783
  {
7323
- $('#redactor-progress').fadeIn();
7784
+ this.showProgressBar();
7324
7785
  this.uploadForm(this.element, this.uploadFrame());
7325
7786
  },
7326
7787
  uploadFrame: function()
@@ -7401,7 +7862,7 @@
7401
7862
  // Success
7402
7863
  if (this.uploadOptions.success)
7403
7864
  {
7404
- $('#redactor-progress').hide();
7865
+ this.hideProgressBar();
7405
7866
 
7406
7867
  if (typeof d !== 'undefined')
7407
7868
  {
@@ -7477,12 +7938,12 @@
7477
7938
  e.preventDefault();
7478
7939
 
7479
7940
  this.dropareabox.removeClass('hover').addClass('drop');
7480
-
7481
- this.dragUploadAjax(this.draguploadOptions.url, e.dataTransfer.files[0], false, false, false, this.draguploadOptions.uploadParam);
7941
+ this.showProgressBar();
7942
+ this.dragUploadAjax(this.draguploadOptions.url, e.dataTransfer.files[0], false, e, this.draguploadOptions.uploadParam);
7482
7943
 
7483
7944
  }, this );
7484
7945
  },
7485
- dragUploadAjax: function(url, file, directupload, progress, e, uploadParam)
7946
+ dragUploadAjax: function(url, file, directupload, e, uploadParam)
7486
7947
  {
7487
7948
  if (!directupload)
7488
7949
  {
@@ -7538,13 +7999,10 @@
7538
7999
 
7539
8000
  var json = (typeof data === 'string' ? $.parseJSON(data) : data);
7540
8001
 
8002
+ this.hideProgressBar();
8003
+
7541
8004
  if (directupload)
7542
8005
  {
7543
- progress.fadeOut('slow', function()
7544
- {
7545
- $(this).remove();
7546
- });
7547
-
7548
8006
  var $img = $('<img>');
7549
8007
  $img.attr('src', json.filelink).attr('id', 'drag-image-marker');
7550
8008
 
@@ -7631,6 +8089,21 @@
7631
8089
 
7632
8090
  return html == '';
7633
8091
  },
8092
+ getInternetExplorerVersion: function()
8093
+ {
8094
+ var rv = false;
8095
+ if (navigator.appName == 'Microsoft Internet Explorer')
8096
+ {
8097
+ var ua = navigator.userAgent;
8098
+ var re = new RegExp("MSIE ([0-9]{1,}[\.0-9]{0,})");
8099
+ if (re.exec(ua) != null)
8100
+ {
8101
+ rv = parseFloat(RegExp.$1);
8102
+ }
8103
+ }
8104
+
8105
+ return rv;
8106
+ },
7634
8107
  isIe11: function()
7635
8108
  {
7636
8109
  return !!navigator.userAgent.match(/Trident\/7\./);
@@ -7745,11 +8218,9 @@
7745
8218
  // LINKIFY
7746
8219
  $.Redactor.fn.formatLinkify = function(protocol, convertLinks, convertImageLinks, convertVideoLinks, linkSize)
7747
8220
  {
7748
- var url1 = /(^|&lt;|\s)(www\..+?\..+?)([.),]?)(\s|\.\s+|\)|&gt;|$)/,
7749
- url2 = /(^|&lt;|\s)(((https?|ftp):\/\/|mailto:).+?)([.),]?)(\s|\.\s+|\)|&gt;|$)/,
7750
- urlImage = /(https?:\/\/.*\.(?:png|jpg|jpeg|gif))/gi,
7751
- urlYoutube = /https?:\/\/(?:[0-9A-Z-]+\.)?(?:youtu\.be\/|youtube\.com\S*[^\w\-\s])([\w\-]{11})(?=[^\w\-]|$)(?![?=&+%\w.-]*(?:['"][^<>]*>|<\/a>))[?=&+%\w.-]*/ig,
7752
- urlVimeo = /https?:\/\/(www\.)?vimeo.com\/(\d+)($|\/)/;
8221
+ var url = /(((https?|ftps?):\/\/)|www[.][^\s])(.+?\..+?)([.),]?)(\s|\.\s+|\)|$)/gi,
8222
+ rProtocol = /(https?|ftp):\/\//i,
8223
+ urlImage = /(https?:\/\/.*\.(?:png|jpg|jpeg|gif))/gi;
7753
8224
 
7754
8225
  var childNodes = (this.$editor ? this.$editor.get(0) : this).childNodes, i = childNodes.length;
7755
8226
  while (i--)
@@ -7765,14 +8236,14 @@
7765
8236
  var iframeStart = '<iframe width="500" height="281" src="',
7766
8237
  iframeEnd = '" frameborder="0" allowfullscreen></iframe>';
7767
8238
 
7768
- if (html.match(urlYoutube))
8239
+ if (html.match(reUrlYoutube))
7769
8240
  {
7770
- html = html.replace(urlYoutube, iframeStart + '//www.youtube.com/embed/$1' + iframeEnd);
8241
+ html = html.replace(reUrlYoutube, iframeStart + '//www.youtube.com/embed/$1' + iframeEnd);
7771
8242
  $(n).after(html).remove();
7772
8243
  }
7773
- else if (html.match(urlVimeo))
8244
+ else if (html.match(reUrlVimeo))
7774
8245
  {
7775
- html = html.replace(urlVimeo, iframeStart + '//player.vimeo.com/video/$2' + iframeEnd);
8246
+ html = html.replace(reUrlVimeo, iframeStart + '//player.vimeo.com/video/$2' + iframeEnd);
7776
8247
  $(n).after(html).remove();
7777
8248
  }
7778
8249
  }
@@ -7786,74 +8257,32 @@
7786
8257
  }
7787
8258
 
7788
8259
  // link
7789
- if (convertLinks && html && (html.match(url1) || html.match(url2)))
8260
+ if (convertLinks && html && html.match(url))
7790
8261
  {
7791
- var found = true;
7792
- var first = true;
8262
+ var matches = html.match(url);
7793
8263
 
7794
- while (found)
8264
+ for (var i in matches)
7795
8265
  {
7796
- var href;
7797
- var url = url1;
7798
- var href1 = url1.exec(html);
7799
- var href2 = url2.exec(html);
8266
+ var href = matches[i];
8267
+ var text = href;
7800
8268
 
7801
- if (href1 && href1[2] && href2 && href2[2])
7802
- {
7803
- //process whichever came first sequentially *first*
7804
- var index1 = html.indexOf(href1[2]);
7805
- var index2 = html.indexOf(href2[2]);
7806
- if (index1 < index2)
7807
- {
7808
- href = href1;
7809
- url = url1;
7810
- }
7811
- else
7812
- {
7813
- href = href2;
7814
- url = url2
7815
- }
7816
- }
7817
- else if (href1 && href1[2])
7818
- {
7819
- href = href1;
7820
- url = url1
7821
- }
7822
- else if (href2 && href2[2])
7823
- {
7824
- href = href2;
7825
- url = url2
7826
- }
8269
+ var space = '';
8270
+ if (href.match(/\s$/) !== null) space = ' ';
7827
8271
 
7828
- found = (href && href.length);
7829
- if (found)
7830
- {
7831
- href = href[2];
7832
- }
8272
+ var addProtocol = protocol;
8273
+ if (href.match(rProtocol) !== null) addProtocol = '';
7833
8274
 
7834
- if (found && href && href.length > linkSize)
7835
- {
7836
- href = href.substring(0, linkSize) + '...';
7837
- }
8275
+ if (text.length > linkSize) text = text.substring(0, linkSize) + '...';
7838
8276
 
7839
- if (first)
7840
- {
7841
- html = html.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
7842
- }
8277
+ text = text.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;')
7843
8278
 
7844
- if (found && href)
7845
- {
7846
- if (url == url1)
7847
- {
7848
- html = html.replace(url1, '$1<a href="' + protocol + '$2">' + $.trim(href) + '</a>$3$4')
7849
- }
7850
- else
7851
- {
7852
- html = html.replace(url2, '$1<a href="$2">' + $.trim(href) + '</a>$5$6');
7853
- }
7854
- }
8279
+ /*
8280
+ To handle URLs which may have $ characters in them, need to escape $ -> $$ to prevent $1 from getting treated as a backreference.
8281
+ See http://gotofritz.net/blog/code-snippets/escaping-in-replace-strings-in-javascript/
8282
+ */
8283
+ var escapedBackReferences = text.replace('$', '$$$');
7855
8284
 
7856
- first = false;
8285
+ html = html.replace(href, '<a href=\"' + addProtocol + $.trim(href) + '\">' + $.trim(escapedBackReferences) + '</a>' + space);
7857
8286
  }
7858
8287
 
7859
8288
  $(n).after(html).remove();