godmin-redactor 1.0.1 → 1.0.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 7d1502d0bf0501d2055f37cdbe4a1a20f3632927
4
- data.tar.gz: 36e8ea25f9c002f1ec99b6c1f39c41315344ee4f
3
+ metadata.gz: 746d8e6fbdef09a044815e075466dde6835d6f09
4
+ data.tar.gz: 1ead471ad25a0eb497ce86832af2ff0da436427c
5
5
  SHA512:
6
- metadata.gz: 38acac7271672953f1ce61e524c45582266ec619ace9687d595852101216bc2a656d019ce1dde4176e9dea06cbbe597a71c8c7117005c966070ce1fea7da0cdd
7
- data.tar.gz: c92b1453f907882ed23e6eef94522b0acea5313e6dd4515a366b1d42e26b1494e3ae41535715513ebecdf2674d0b25dfff53ae4403436af29b08aa3d12f47bfd
6
+ metadata.gz: 5379c58b111a937492e9a4faad39859f36d1b0c17013b7d0de970b0770604895674ef9dc3a204c84711c911eefd198a5954fb35e8327fdbc03488a48931b90c1
7
+ data.tar.gz: 6ef4c885723a269d439bb7af2f260e4794182e4299d66ba51fdd2ad5773d674f13af5063941f517376dc2d8b03befc1e861eb6e48976b44143c9148464d8e2df
@@ -1,5 +1,16 @@
1
1
  # Changelog
2
2
 
3
+ ### 1.0.2 - 2017-11-02
4
+ Features
5
+ - Update to [Redactor version 10.2.5](http://imperavi.com/redactor/log/)
6
+
7
+ Issues
8
+ - Fixes Redactor bug with Chrome 58 (https://github.com/varvet/godmin-redactor/pull/8)
9
+
10
+ ### 1.0.1 - 2016-03-10
11
+ Issues
12
+ - Multiple redactor boxes on same page (https://github.com/varvet/godmin-redactor/pull/6)
13
+
3
14
  ### 1.0.0 - 2016-02-01
4
15
  Features
5
16
  - Compatible with Godmin 1.0
@@ -1,5 +1,5 @@
1
1
  module Godmin
2
2
  module Redactor
3
- VERSION = "1.0.1"
3
+ VERSION = "1.0.2"
4
4
  end
5
5
  end
@@ -1,6 +1,6 @@
1
1
  /*
2
- Redactor v10.1.1
3
- Updated: April 28, 2015
2
+ Redactor 10.2.5
3
+ Updated: October 1, 2015
4
4
 
5
5
  http://imperavi.com/redactor/
6
6
 
@@ -12,6 +12,7 @@
12
12
 
13
13
  (function($)
14
14
  {
15
+
15
16
  'use strict';
16
17
 
17
18
  if (!Function.prototype.bind)
@@ -91,13 +92,13 @@
91
92
 
92
93
  // Functionality
93
94
  $.Redactor = Redactor;
94
- $.Redactor.VERSION = '10.1.1';
95
+ $.Redactor.VERSION = '10.2.5';
95
96
  $.Redactor.modules = ['alignment', 'autosave', 'block', 'buffer', 'build', 'button',
96
97
  'caret', 'clean', 'code', 'core', 'dropdown', 'file', 'focus',
97
98
  'image', 'indent', 'inline', 'insert', 'keydown', 'keyup',
98
- 'lang', 'line', 'link', 'list', 'modal', 'observe', 'paragraphize',
99
+ 'lang', 'line', 'link', 'linkify', 'list', 'modal', 'observe', 'paragraphize',
99
100
  'paste', 'placeholder', 'progress', 'selection', 'shortcuts',
100
- 'tabifier', 'tidy', 'toolbar', 'upload', 'utils', 'linkify'];
101
+ 'tabifier', 'tidy', 'toolbar', 'upload', 'utils'];
101
102
 
102
103
  $.Redactor.opts = {
103
104
 
@@ -198,7 +199,8 @@
198
199
 
199
200
  removeComments: false,
200
201
  replaceTags: [
201
- ['strike', 'del']
202
+ ['strike', 'del'],
203
+ ['b', 'strong']
202
204
  ],
203
205
  replaceStyles: [
204
206
  ['font-weight:\\s?bold', "strong"],
@@ -251,7 +253,10 @@
251
253
  inlineTags: ['strong', 'b', 'u', 'em', 'i', 'code', 'del', 'ins', 'samp', 'kbd', 'sup', 'sub', 'mark', 'var', 'cite', 'small'],
252
254
  alignmentTags: ['P', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'DL', 'DT', 'DD', 'DIV', 'TD', 'BLOCKQUOTE', 'OUTPUT', 'FIGCAPTION', 'ADDRESS', 'SECTION', 'HEADER', 'FOOTER', 'ASIDE', 'ARTICLE'],
253
255
  blockLevelElements: ['PRE', 'UL', 'OL', 'LI'],
254
-
256
+ highContrast: false,
257
+ observe: {
258
+ dropdowns: []
259
+ },
255
260
 
256
261
  // lang
257
262
  langs: {
@@ -327,7 +332,6 @@
327
332
  filename: 'Name (optional)',
328
333
  edit: 'Edit',
329
334
  upload_label: 'Drop file here or '
330
-
331
335
  }
332
336
  },
333
337
 
@@ -349,6 +353,7 @@
349
353
  keyCode: {
350
354
  BACKSPACE: 8,
351
355
  DELETE: 46,
356
+ UP: 38,
352
357
  DOWN: 40,
353
358
  ENTER: 13,
354
359
  SPACE: 32,
@@ -450,7 +455,6 @@
450
455
  this[module][methods[z]] = this[module][methods[z]].bind(this);
451
456
  }
452
457
  },
453
-
454
458
  alignment: function()
455
459
  {
456
460
  return {
@@ -473,15 +477,18 @@
473
477
  set: function(type)
474
478
  {
475
479
  // focus
476
- if (!this.utils.browser('msie')) this.$editor.focus();
477
-
478
- this.buffer.set();
479
- this.selection.save();
480
+ if (!this.utils.browser('msie') && !this.opts.linebreaks)
481
+ {
482
+ this.$editor.focus();
483
+ }
480
484
 
481
485
  // get blocks
482
486
  this.alignment.blocks = this.selection.getBlocks();
483
487
  this.alignment.type = type;
484
488
 
489
+ this.buffer.set();
490
+ this.selection.save();
491
+
485
492
  // set alignment
486
493
  if (this.alignment.isLinebreaksOrNoBlocks())
487
494
  {
@@ -558,10 +565,11 @@
558
565
  },
559
566
  load: function()
560
567
  {
568
+ if (!this.opts.autosave) return;
569
+
561
570
  this.autosave.source = this.code.get();
562
571
 
563
572
  if (this.autosave.html === this.autosave.source) return;
564
- //if (this.utils.isEmpty(this.autosave.source)) return;
565
573
 
566
574
  // data
567
575
  var data = {};
@@ -653,6 +661,23 @@
653
661
  // focus
654
662
  if (!this.utils.browser('msie')) this.$editor.focus();
655
663
 
664
+ var html = $.trim(this.$editor.html());
665
+ this.block.isEmpty = this.utils.isEmpty(html);
666
+
667
+ // FF focus
668
+ if (this.utils.browser('mozilla') && !this.focus.isFocused())
669
+ {
670
+ if (this.block.isEmpty)
671
+ {
672
+ var $first;
673
+ if (!this.opts.linebreaks)
674
+ {
675
+ $first = this.$editor.children().first();
676
+ this.caret.setEnd($first);
677
+ }
678
+ }
679
+ }
680
+
656
681
  this.block.blocks = this.selection.getBlocks();
657
682
 
658
683
  this.block.blocksSize = this.block.blocks.length;
@@ -666,10 +691,12 @@
666
691
 
667
692
  this.selection.restore();
668
693
  this.code.sync();
694
+ this.observe.load();
669
695
 
670
696
  },
671
697
  set: function(tag)
672
698
  {
699
+
673
700
  this.selection.get();
674
701
  this.block.containerTag = this.range.commonAncestorContainer.tagName;
675
702
 
@@ -684,6 +711,15 @@
684
711
  },
685
712
  setCollapsed: function(tag)
686
713
  {
714
+ if (this.opts.linebreaks && this.block.isEmpty && tag != 'p')
715
+ {
716
+ var node = document.createElement(tag);
717
+ this.$editor.html(node);
718
+ this.caret.setEnd(node);
719
+
720
+ return;
721
+ }
722
+
687
723
  var block = this.block.blocks[0];
688
724
  if (block === false) return;
689
725
 
@@ -698,7 +734,6 @@
698
734
  var isContainerTable = (this.block.containerTag == 'TD' || this.block.containerTag == 'TH');
699
735
  if (isContainerTable && !this.opts.linebreaks)
700
736
  {
701
-
702
737
  document.execCommand('formatblock', false, '<' + tag + '>');
703
738
 
704
739
  block = this.selection.getBlock();
@@ -709,7 +744,7 @@
709
744
  {
710
745
  if (this.opts.linebreaks && tag == 'p')
711
746
  {
712
- $(block).prepend('<br>').append('<br>');
747
+ $(block).append('<br>');
713
748
  this.utils.replaceWithContents(block);
714
749
  }
715
750
  else
@@ -730,7 +765,7 @@
730
765
  // blockquote off
731
766
  if (this.opts.linebreaks)
732
767
  {
733
- $(block).prepend('<br>').append('<br>');
768
+ $(block).append('<br>');
734
769
  this.utils.replaceWithContents(block);
735
770
  }
736
771
  else
@@ -744,15 +779,16 @@
744
779
  this.block.toggle($(block));
745
780
  }
746
781
 
782
+
747
783
  if (typeof this.block.type == 'undefined' && typeof this.block.value == 'undefined')
748
784
  {
749
785
  $(block).removeAttr('class').removeAttr('style');
750
786
  }
751
-
752
787
  },
753
788
  setMultiple: function(tag)
754
789
  {
755
790
  var block = this.block.blocks[0];
791
+
756
792
  var isContainerTable = (this.block.containerTag == 'TD' || this.block.containerTag == 'TH');
757
793
 
758
794
  if (block !== false && this.block.blocksSize === 1)
@@ -762,7 +798,7 @@
762
798
  // blockquote off
763
799
  if (this.opts.linebreaks)
764
800
  {
765
- $(block).prepend('<br>').append('<br>');
801
+ $(block).append('<br>');
766
802
  this.utils.replaceWithContents(block);
767
803
  }
768
804
  else
@@ -809,7 +845,6 @@
809
845
  }
810
846
  else
811
847
  {
812
-
813
848
  if (this.opts.linebreaks || tag != 'p')
814
849
  {
815
850
  if (tag == 'blockquote')
@@ -848,7 +883,6 @@
848
883
 
849
884
  }
850
885
 
851
-
852
886
  this.block.formatWrap(tag);
853
887
  }
854
888
  else
@@ -1011,11 +1045,6 @@
1011
1045
 
1012
1046
  var $elements = $formatted.find(this.opts.blockLevelElements.join(',') + ', td, table, thead, tbody, tfoot, th, tr');
1013
1047
 
1014
- if ((this.opts.linebreaks && tag == 'p') || tag == 'pre' || tag == 'blockquote')
1015
- {
1016
- $elements.append('<br />');
1017
- }
1018
-
1019
1048
  $elements.contents().unwrap();
1020
1049
 
1021
1050
  if (tag != 'p' && tag != 'blockquote') $formatted.find('img').remove();
@@ -1042,6 +1071,17 @@
1042
1071
  this.utils.replaceWithContents($formatted);
1043
1072
  }
1044
1073
 
1074
+ if (this.opts.linebreaks)
1075
+ {
1076
+ var $next = $formatted.next().next();
1077
+ if ($next.size() != 0 && $next[0].tagName === 'BR')
1078
+ {
1079
+ $next.remove();
1080
+ }
1081
+ }
1082
+
1083
+
1084
+
1045
1085
  },
1046
1086
  formatTableWrapping: function($formatted)
1047
1087
  {
@@ -1201,6 +1241,8 @@
1201
1241
  build: function()
1202
1242
  {
1203
1243
  return {
1244
+ focused: false,
1245
+ blured: true,
1204
1246
  run: function()
1205
1247
  {
1206
1248
  this.build.createContainerBox();
@@ -1215,7 +1257,7 @@
1215
1257
  },
1216
1258
  createContainerBox: function()
1217
1259
  {
1218
- this.$box = $('<div class="redactor-box" />');
1260
+ this.$box = $('<div class="redactor-box" role="application" />');
1219
1261
  },
1220
1262
  createTextarea: function()
1221
1263
  {
@@ -1272,6 +1314,7 @@
1272
1314
  callEditor: function()
1273
1315
  {
1274
1316
  this.build.disableMozillaEditing();
1317
+ this.build.disableIeLinks();
1275
1318
  this.build.setEvents();
1276
1319
  this.build.setHelpers();
1277
1320
 
@@ -1311,7 +1354,8 @@
1311
1354
  {
1312
1355
  e.preventDefault();
1313
1356
 
1314
- if (!this.opts.dragImageUpload || !this.opts.dragFileUpload) return;
1357
+ if (!this.opts.dragImageUpload && !this.opts.dragFileUpload) return;
1358
+ if (this.opts.imageUpload === null && this.opts.fileUpload === null) return;
1315
1359
 
1316
1360
  var files = e.dataTransfer.files;
1317
1361
  this.upload.directUpload(files[0], e);
@@ -1325,6 +1369,12 @@
1325
1369
  setEvents: function()
1326
1370
  {
1327
1371
  // drop
1372
+ this.$editor.on('dragover.redactor dragenter.redactor', function(e)
1373
+ {
1374
+ e.preventDefault();
1375
+ e.stopPropagation();
1376
+ });
1377
+
1328
1378
  this.$editor.on('drop.redactor', $.proxy(function(e)
1329
1379
  {
1330
1380
  e = e.originalEvent || e;
@@ -1383,30 +1433,50 @@
1383
1433
  }
1384
1434
 
1385
1435
  // focus
1386
- if ($.isFunction(this.opts.focusCallback))
1436
+ this.$editor.on('focus.redactor', $.proxy(function(e)
1387
1437
  {
1388
- this.$editor.on('focus.redactor', $.proxy(this.opts.focusCallback, this));
1389
- }
1438
+ if ($.isFunction(this.opts.focusCallback))
1439
+ {
1440
+ this.core.setCallback('focus', e);
1441
+ }
1442
+
1443
+ this.build.focused = true;
1444
+ this.build.blured = false;
1445
+
1446
+ if (this.selection.getCurrent() === false)
1447
+ {
1448
+ this.selection.get();
1449
+ this.range.setStart(this.$editor[0], 0);
1450
+ this.range.setEnd(this.$editor[0], 0);
1451
+ this.selection.addRange();
1452
+ }
1453
+
1454
+
1455
+ }, this));
1390
1456
 
1391
- var clickedElement;
1392
- $(document).on('mousedown', function(e) { clickedElement = e.target; });
1393
1457
 
1394
1458
  // blur
1395
- this.$editor.on('blur.redactor', $.proxy(function(e)
1459
+ $(document).on('mousedown.redactor-blur.' + this.uuid, $.proxy(function(e)
1396
1460
  {
1461
+ if (this.start) return;
1397
1462
  if (this.rtePaste) return;
1398
- if (!this.build.isBlured(clickedElement)) return;
1463
+
1464
+ if ($(e.target).closest('.redactor-editor, .redactor-toolbar, .redactor-dropdown').size() !== 0)
1465
+ {
1466
+ return;
1467
+ }
1399
1468
 
1400
1469
  this.utils.disableSelectAll();
1401
- if ($.isFunction(this.opts.blurCallback)) this.core.setCallback('blur', e);
1470
+ if (!this.build.blured && $.isFunction(this.opts.blurCallback))
1471
+ {
1472
+ this.core.setCallback('blur', e);
1473
+ }
1474
+
1475
+ this.build.focused = false;
1476
+ this.build.blured = true;
1402
1477
 
1403
1478
  }, this));
1404
- },
1405
- isBlured: function(clickedElement)
1406
- {
1407
- var $el = $(clickedElement);
1408
1479
 
1409
- return (!$el.hasClass('redactor-toolbar, redactor-dropdown') && !$el.is('#redactor-modal') && $el.parents('.redactor-toolbar, .redactor-dropdown, #redactor-modal').length === 0);
1410
1480
  },
1411
1481
  setHelpers: function()
1412
1482
  {
@@ -1427,21 +1497,17 @@
1427
1497
  plugins: function()
1428
1498
  {
1429
1499
  if (!this.opts.plugins) return;
1430
- if (!RedactorPlugins) return;
1431
1500
 
1432
1501
  $.each(this.opts.plugins, $.proxy(function(i, s)
1433
1502
  {
1434
- if (typeof RedactorPlugins[s] === 'undefined') return;
1503
+ var func = (typeof RedactorPlugins !== 'undefined' && typeof RedactorPlugins[s] !== 'undefined') ? RedactorPlugins : Redactor.fn;
1435
1504
 
1436
- if ($.inArray(s, $.Redactor.modules) !== -1)
1505
+ if (!$.isFunction(func[s]))
1437
1506
  {
1438
- $.error('Plugin name "' + s + '" matches the name of the Redactor\'s module.');
1439
1507
  return;
1440
1508
  }
1441
1509
 
1442
- if (!$.isFunction(RedactorPlugins[s])) return;
1443
-
1444
- this[s] = RedactorPlugins[s]();
1510
+ this[s] = func[s]();
1445
1511
 
1446
1512
  // get methods
1447
1513
  var methods = this.getModuleMethods(this[s]);
@@ -1453,7 +1519,10 @@
1453
1519
  this[s][methods[z]] = this[s][methods[z]].bind(this);
1454
1520
  }
1455
1521
 
1456
- if ($.isFunction(this[s].init)) this[s].init();
1522
+ if ($.isFunction(this[s].init))
1523
+ {
1524
+ this[s].init();
1525
+ }
1457
1526
 
1458
1527
 
1459
1528
  }, this));
@@ -1468,6 +1537,13 @@
1468
1537
  document.execCommand('enableObjectResizing', false, false);
1469
1538
  document.execCommand('enableInlineTableEditing', false, false);
1470
1539
  } catch (e) {}
1540
+ },
1541
+ disableIeLinks: function()
1542
+ {
1543
+ if (!this.utils.browser('msie')) return;
1544
+
1545
+ // IE prevent converting links
1546
+ document.execCommand("AutoUrlDetect", false, false);
1471
1547
  }
1472
1548
  };
1473
1549
  },
@@ -1476,7 +1552,7 @@
1476
1552
  return {
1477
1553
  build: function(btnName, btnObject)
1478
1554
  {
1479
- var $button = $('<a href="#" class="re-icon re-' + btnName + '" rel="' + btnName + '" />').attr('tabindex', '-1');
1555
+ var $button = $('<a href="#" class="re-icon re-' + btnName + '" rel="' + btnName + '" />').attr({'role': 'button', 'aria-label': btnObject.title, 'tabindex': '-1'});
1480
1556
 
1481
1557
  // click
1482
1558
  if (btnObject.func || btnObject.command || btnObject.dropdown)
@@ -1487,6 +1563,8 @@
1487
1563
  // dropdown
1488
1564
  if (btnObject.dropdown)
1489
1565
  {
1566
+ $button.addClass('redactor-toolbar-link-dropdown').attr('aria-haspopup', true);
1567
+
1490
1568
  var $dropdown = $('<div class="redactor-dropdown redactor-dropdown-' + this.uuid + ' redactor-dropdown-box-' + btnName + '" style="display: none;">');
1491
1569
  $button.data('dropdown', $dropdown);
1492
1570
  this.dropdown.build(btnName, $dropdown, btnObject.dropdown);
@@ -1526,20 +1604,24 @@
1526
1604
  },
1527
1605
  createTooltip: function($button, name, title)
1528
1606
  {
1529
- var $tooltip = $('<span>').addClass('redactor-toolbar-tooltip redactor-toolbar-tooltip-' + name).hide().html(title);
1607
+ var $tooltip = $('<span>').addClass('redactor-toolbar-tooltip redactor-toolbar-tooltip-' + this.uuid + ' redactor-toolbar-tooltip-' + name).hide().html(title);
1530
1608
  $tooltip.appendTo('body');
1531
1609
 
1532
1610
  $button.on('mouseover', function()
1533
1611
  {
1534
- if ($(this).hasClass('redactor-button-disabled')) return;
1612
+ if ($(this).hasClass('redactor-button-disabled'))
1613
+ {
1614
+ return;
1615
+ }
1535
1616
 
1536
1617
  var pos = $button.offset();
1537
1618
 
1538
- $tooltip.show();
1539
1619
  $tooltip.css({
1540
1620
  top: (pos.top + $button.innerHeight()) + 'px',
1541
1621
  left: (pos.left + $button.innerWidth()/2 - $tooltip.innerWidth()/2) + 'px'
1542
1622
  });
1623
+ $tooltip.show();
1624
+
1543
1625
  });
1544
1626
 
1545
1627
  $button.on('mouseout', function()
@@ -1554,6 +1636,8 @@
1554
1636
 
1555
1637
  e.preventDefault();
1556
1638
 
1639
+ $(document).find('.redactor-toolbar-tooltip').hide();
1640
+
1557
1641
  if (this.utils.browser('msie')) e.returnValue = false;
1558
1642
 
1559
1643
  if (type == 'command') this.inline.format(callback);
@@ -1601,11 +1685,11 @@
1601
1685
  },
1602
1686
  setActiveInVisual: function()
1603
1687
  {
1604
- this.$toolbar.find('a.re-icon').not('a.re-html').removeClass('redactor-button-disabled');
1688
+ this.$toolbar.find('a.re-icon').not('a.re-html, a.re-fullscreen').removeClass('redactor-button-disabled');
1605
1689
  },
1606
1690
  setInactiveInCode: function()
1607
1691
  {
1608
- this.$toolbar.find('a.re-icon').not('a.re-html').addClass('redactor-button-disabled');
1692
+ this.$toolbar.find('a.re-icon').not('a.re-html, a.re-fullscreen').addClass('redactor-button-disabled');
1609
1693
  },
1610
1694
  changeIcon: function(key, classname)
1611
1695
  {
@@ -1623,6 +1707,8 @@
1623
1707
  },
1624
1708
  addCallback: function($btn, callback)
1625
1709
  {
1710
+ if ($btn == "buffer") return;
1711
+
1626
1712
  var type = (callback == 'dropdown') ? 'dropdown' : 'func';
1627
1713
  var key = $btn.attr('rel');
1628
1714
  $btn.on('touchstart click', $.proxy(function(e)
@@ -1634,6 +1720,8 @@
1634
1720
  },
1635
1721
  addDropdown: function($btn, dropdown)
1636
1722
  {
1723
+ $btn.addClass('redactor-toolbar-link-dropdown').attr('aria-haspopup', true);
1724
+
1637
1725
  var key = $btn.attr('rel');
1638
1726
  this.button.addCallback($btn, 'dropdown');
1639
1727
 
@@ -1649,6 +1737,8 @@
1649
1737
  {
1650
1738
  if (!this.opts.toolbar) return;
1651
1739
 
1740
+ if (this.button.isMobileUndoRedo(key)) return "buffer";
1741
+
1652
1742
  var btn = this.button.build(key, { title: title });
1653
1743
  btn.addClass('redactor-btn-image');
1654
1744
 
@@ -1660,6 +1750,8 @@
1660
1750
  {
1661
1751
  if (!this.opts.toolbar) return;
1662
1752
 
1753
+ if (this.button.isMobileUndoRedo(key)) return "buffer";
1754
+
1663
1755
  var btn = this.button.build(key, { title: title });
1664
1756
  btn.addClass('redactor-btn-image');
1665
1757
  this.$toolbar.prepend($('<li>').append(btn));
@@ -1670,6 +1762,8 @@
1670
1762
  {
1671
1763
  if (!this.opts.toolbar) return;
1672
1764
 
1765
+ if (this.button.isMobileUndoRedo(key)) return "buffer";
1766
+
1673
1767
  var btn = this.button.build(key, { title: title });
1674
1768
  btn.addClass('redactor-btn-image');
1675
1769
  var $btn = this.button.get(afterkey);
@@ -1683,6 +1777,8 @@
1683
1777
  {
1684
1778
  if (!this.opts.toolbar) return;
1685
1779
 
1780
+ if (this.button.isMobileUndoRedo(key)) return "buffer";
1781
+
1686
1782
  var btn = this.button.build(key, { title: title });
1687
1783
  btn.addClass('redactor-btn-image');
1688
1784
  var $btn = this.button.get(beforekey);
@@ -1695,6 +1791,10 @@
1695
1791
  remove: function(key)
1696
1792
  {
1697
1793
  this.button.get(key).remove();
1794
+ },
1795
+ isMobileUndoRedo: function(key)
1796
+ {
1797
+ return (key == "undo" || key == "redo") && !this.utils.isDesktop();
1698
1798
  }
1699
1799
  };
1700
1800
  },
@@ -1718,7 +1818,14 @@
1718
1818
  },
1719
1819
  setEnd: function(node)
1720
1820
  {
1821
+ node = node[0] || node;
1822
+ if (node.lastChild.nodeType == 1)
1823
+ {
1824
+ return this.caret.setAfter(node.lastChild);
1825
+ }
1826
+
1721
1827
  this.caret.set(node, 1, node, 1);
1828
+
1722
1829
  },
1723
1830
  set: function(orgn, orgo, focn, foco)
1724
1831
  {
@@ -1912,7 +2019,7 @@
1912
2019
  // replace special characters in links
1913
2020
  html = html.replace(/<a href="(.*?[^>]?)®(.*?[^>]?)">/gi, '<a href="$1&reg$2">');
1914
2021
 
1915
- if (this.opts.replaceDivs) html = this.clean.replaceDivs(html);
2022
+ if (this.opts.replaceDivs && !this.opts.linebreaks) html = this.clean.replaceDivs(html);
1916
2023
  if (this.opts.linebreaks) html = this.clean.replaceParagraphsToBr(html);
1917
2024
 
1918
2025
  // save form tag
@@ -1933,10 +2040,11 @@
1933
2040
 
1934
2041
  html = $div.html();
1935
2042
  }
2043
+
1936
2044
  $div.remove();
1937
2045
 
1938
2046
  // remove font tag
1939
- html = html.replace(/<font(.*?[^<])>/gi, '');
2047
+ html = html.replace(/<font(.*?)>/gi, '');
1940
2048
  html = html.replace(/<\/font>/gi, '');
1941
2049
 
1942
2050
  // tidy html
@@ -1951,12 +2059,14 @@
1951
2059
  // convert inline tags
1952
2060
  html = this.clean.convertInline(html);
1953
2061
 
2062
+ html = html.replace(/&amp;/g, '&');
2063
+
1954
2064
  return html;
1955
2065
  },
1956
2066
  onSync: function(html)
1957
2067
  {
1958
2068
  // remove spaces
1959
- html = html.replace(/[\u200B-\u200D\uFEFF]/g, '');
2069
+ html = html.replace(/\u200B/g, '');
1960
2070
  html = html.replace(/&#x200b;/gi, '');
1961
2071
 
1962
2072
  if (this.opts.cleanSpaces)
@@ -1988,17 +2098,41 @@
1988
2098
  html = html.replace(new RegExp(i, 'g'), s);
1989
2099
  });
1990
2100
 
1991
- // remove br in the of li
2101
+ // remove last br in FF
2102
+ if (this.utils.browser('mozilla'))
2103
+ {
2104
+ html = html.replace(/<br\s?\/?>$/gi, '');
2105
+ }
2106
+
2107
+ // remove br in|of li tags
1992
2108
  html = html.replace(new RegExp('<br\\s?/?></li>', 'gi'), '</li>');
1993
2109
  html = html.replace(new RegExp('</li><br\\s?/?>', 'gi'), '</li>');
2110
+
2111
+ // remove empty attributes
2112
+ html = html.replace(/<(.*?)rel="\s*?"(.*?[^>]?)>/gi, '<$1$2">');
2113
+ html = html.replace(/<(.*?)style="\s*?"(.*?[^>]?)>/gi, '<$1$2">');
2114
+ html = html.replace(/="">/gi, '>');
2115
+ html = html.replace(/""">/gi, '">');
2116
+ html = html.replace(/"">/gi, '">');
2117
+
1994
2118
  // remove verified
1995
- html = html.replace(/<div(.*?[^>]) data-tagblock="redactor"(.*?[^>])>/gi, '<div$1$2>');
2119
+ html = html.replace(/<div(.*?)data-tagblock="redactor"(.*?[^>])>/gi, '<div$1$2>');
1996
2120
  html = html.replace(/<(.*?) data-verified="redactor"(.*?[^>])>/gi, '<$1$2>');
1997
- html = html.replace(/<span(.*?[^>])\srel="(.*?[^>])"(.*?[^>])>/gi, '<span$1$3>');
1998
- html = html.replace(/<img(.*?[^>])\srel="(.*?[^>])"(.*?[^>])>/gi, '<img$1$3>');
1999
- html = html.replace(/<img(.*?[^>])\sstyle="" (.*?[^>])>'/gi, '<img$1 $2>');
2000
- html = html.replace(/<img(.*?[^>])\sstyle (.*?[^>])>'/gi, '<img$1 $2>');
2121
+
2122
+ var $div = $("<div/>").html($.parseHTML(html, document, true));
2123
+ $div.find("span").removeAttr("rel");
2124
+
2125
+ $div.find('pre .redactor-invisible-space').each(function()
2126
+ {
2127
+ $(this).contents().unwrap();
2128
+ });
2129
+
2130
+ html = $div.html();
2131
+
2132
+ // remove rel attribute from img
2133
+ html = html.replace(/<img(.*?[^>])rel="(.*?[^>])"(.*?[^>])>/gi, '<img$1$3>');
2001
2134
  html = html.replace(/<span class="redactor-invisible-space">(.*?)<\/span>/gi, '$1');
2135
+
2002
2136
  html = html.replace(/ data-save-url="(.*?[^>])"/gi, '');
2003
2137
 
2004
2138
  // remove image resize
@@ -2007,7 +2141,7 @@
2007
2141
  html = html.replace(/<span(.*?)id="redactor-image-editter"(.*?[^>])>(.*?)<\/span>/gi, '');
2008
2142
 
2009
2143
  // remove font tag
2010
- html = html.replace(/<font(.*?[^<])>/gi, '');
2144
+ html = html.replace(/<font(.*?)>/gi, '');
2011
2145
  html = html.replace(/<\/font>/gi, '');
2012
2146
 
2013
2147
  // tidy html
@@ -2025,16 +2159,17 @@
2025
2159
  html = html.replace(new RegExp('<(.*?) data-verified="redactor"(.*?[^>])>', 'gi'), '<$1$2>');
2026
2160
  html = html.replace(new RegExp('<(.*?) data-verified="redactor">', 'gi'), '<$1>');
2027
2161
 
2162
+ html = html.replace(/&amp;/g, '&');
2163
+
2028
2164
  return html;
2029
2165
  },
2030
2166
  onPaste: function(html, setMode)
2031
2167
  {
2032
2168
  html = $.trim(html);
2033
-
2034
2169
  html = html.replace(/\$/g, '&#36;');
2035
2170
 
2036
2171
  // convert dirty spaces
2037
- html = html.replace(/<span class="s1">/gi, '<span>');
2172
+ html = html.replace(/<span class="s[0-9]">/gi, '<span>');
2038
2173
  html = html.replace(/<span class="Apple-converted-space">&nbsp;<\/span>/gi, ' ');
2039
2174
  html = html.replace(/<span class="Apple-tab-span"[^>]*>\t<\/span>/gi, '\t');
2040
2175
  html = html.replace(/<span[^>]*>(\s|&nbsp;)<\/span>/gi, ' ');
@@ -2136,20 +2271,108 @@
2136
2271
  // style
2137
2272
  html = html.replace(/<style[^>]*>[\s\S]*?<\/style>/gi, '');
2138
2273
 
2139
- if (/(class=\"?Mso|style=\"[^\"]*\bmso\-|w:WordDocument)/.test(html))
2274
+ // op
2275
+ html = html.replace(/<o\:p[^>]*>[\s\S]*?<\/o\:p>/gi, '');
2276
+
2277
+ if (html.match(/class="?Mso|style="[^"]*\bmso-|style='[^'']*\bmso-|w:WordDocument/i))
2140
2278
  {
2279
+ // comments
2280
+ html = html.replace(/<!--[\s\S]+?-->/gi, '');
2281
+
2282
+ // scripts
2283
+ html = html.replace(/<(!|script[^>]*>.*?<\/script(?=[>\s])|\/?(\?xml(:\w+)?|img|meta|link|style|\w:\w+)(?=[\s\/>]))[^>]*>/gi, '');
2284
+
2285
+ // Convert <s> into <strike>
2286
+ html = html.replace(/<(\/?)s>/gi, "<$1strike>");
2287
+
2288
+ // Replace nbsp entites to char since it's easier to handle
2289
+ html = html.replace(/ /gi, ' ');
2290
+
2291
+ // Convert <span style="mso-spacerun:yes">___</span> to string of alternating
2292
+ // breaking/non-breaking spaces of same length
2293
+ html = html.replace(/<span\s+style\s*=\s*"\s*mso-spacerun\s*:\s*yes\s*;?\s*"\s*>([\s\u00a0]*)<\/span>/gi, function(str, spaces) {
2294
+ return (spaces.length > 0) ? spaces.replace(/./, " ").slice(Math.floor(spaces.length/2)).split("").join("\u00a0") : '';
2295
+ });
2296
+
2141
2297
  html = this.clean.onPasteIeFixLinks(html);
2142
2298
 
2143
2299
  // shapes
2144
2300
  html = html.replace(/<img(.*?)v:shapes=(.*?)>/gi, '');
2145
2301
  html = html.replace(/src="file\:\/\/(.*?)"/, 'src=""');
2146
2302
 
2147
- // list
2148
- html = html.replace(/<p(.*?)class="MsoListParagraphCxSpFirst"([\w\W]*?)<\/p>/gi, '<ul><li$2</li>');
2149
- html = html.replace(/<p(.*?)class="MsoListParagraphCxSpMiddle"([\w\W]*?)<\/p>/gi, '<li$2</li>');
2150
- html = html.replace(/<p(.*?)class="MsoListParagraphCxSpLast"([\w\W]*?)<\/p>/gi, '<li$2</li></ul>');
2151
- // one line
2152
- html = html.replace(/<p(.*?)class="MsoListParagraph"([\w\W]*?)<\/p>/gi, '<ul><li$2</li></ul>');
2303
+ // lists
2304
+ var $div = $("<div/>").html(html);
2305
+
2306
+ var lastList = false;
2307
+ var lastLevel = 1;
2308
+ var listsIds = [];
2309
+
2310
+ $div.find("p[style]").each(function()
2311
+ {
2312
+ var matches = $(this).attr('style').match(/mso\-list\:l([0-9]+)\slevel([0-9]+)/);
2313
+
2314
+ if (matches)
2315
+ {
2316
+ var currentList = parseInt(matches[1]);
2317
+ var currentLevel = parseInt(matches[2]);
2318
+ var listType = $(this).html().match(/^[\w]+\./) ? "ol" : "ul";
2319
+
2320
+ var $li = $("<li/>").html($(this).html());
2321
+
2322
+ $li.html($li.html().replace(/^([\w\.]+)</, '<'));
2323
+ $li.find("span:first").remove();
2324
+
2325
+ if (currentLevel == 1 && $.inArray(currentList, listsIds) == -1)
2326
+ {
2327
+ var $list = $("<" + listType + "/>").attr({"data-level": currentLevel,
2328
+ "data-list": currentList})
2329
+ .html($li);
2330
+
2331
+ $(this).replaceWith($list);
2332
+
2333
+ lastList = currentList;
2334
+ listsIds.push(currentList);
2335
+ }
2336
+ else
2337
+ {
2338
+ if (currentLevel > lastLevel)
2339
+ {
2340
+ var $prevList = $div.find('[data-level="' + lastLevel + '"][data-list="' + lastList + '"]');
2341
+
2342
+ var $lastList = $prevList;
2343
+
2344
+ for(var i = lastLevel; i < currentLevel; i++)
2345
+ {
2346
+ $list = $("<" + listType + "/>");
2347
+
2348
+ $list.appendTo($lastList.find("li").last());
2349
+
2350
+ $lastList = $list;
2351
+ }
2352
+
2353
+ $lastList.attr({"data-level": currentLevel,
2354
+ "data-list": currentList})
2355
+ .html($li);
2356
+
2357
+ }
2358
+ else
2359
+ {
2360
+ var $prevList = $div.find('[data-level="' + currentLevel + '"][data-list="' + currentList + '"]').last();
2361
+
2362
+ $prevList.append($li);
2363
+ }
2364
+
2365
+ lastLevel = currentLevel;
2366
+ lastList = currentList;
2367
+
2368
+ $(this).remove();
2369
+ }
2370
+ }
2371
+ });
2372
+
2373
+ $div.find('[data-level][data-list]').removeAttr('data-level data-list');
2374
+ html = $div.html();
2375
+
2153
2376
  // remove ms word's bullet
2154
2377
  html = html.replace(/·/g, '');
2155
2378
  html = html.replace(/<p class="Mso(.*?)"/gi, '<p');
@@ -2168,12 +2391,6 @@
2168
2391
  html = html.replace(/<p>\n?<li>/gi, '<li>');
2169
2392
  }
2170
2393
 
2171
- // remove nbsp
2172
- if (this.opts.cleanSpaces)
2173
- {
2174
- html = html.replace(/(\s|&nbsp;)+/g, ' ');
2175
- }
2176
-
2177
2394
  return html;
2178
2395
  },
2179
2396
  onPasteExtra: function(html)
@@ -2321,8 +2538,8 @@
2321
2538
  if (!matchBlocks && (matchContainers === null || (matchContainers && matchContainers.length <= 1)))
2322
2539
  {
2323
2540
  var matchBR = html.match(/<br\s?\/?>/gi);
2324
- var matchIMG = html.match(/<img(.*?[^>])>/gi);
2325
- if (!matchBR && !matchIMG)
2541
+ //var matchIMG = html.match(/<img(.*?[^>])>/gi);
2542
+ if (!matchBR)
2326
2543
  {
2327
2544
  this.clean.singleLine = true;
2328
2545
  html = html.replace(/<\/?(p|div)(.*?)>/gi, '');
@@ -2346,11 +2563,14 @@
2346
2563
  html = this.clean.savePreFormatting(html);
2347
2564
  html = this.clean.saveCodeFormatting(html);
2348
2565
 
2566
+ html = this.clean.restoreSelectionMarker(html);
2567
+
2349
2568
  return html;
2350
2569
  },
2351
2570
  savePreFormatting: function(html)
2352
2571
  {
2353
2572
  var pre = html.match(/<pre(.*?)>([\w\W]*?)<\/pre>/gi);
2573
+
2354
2574
  if (pre !== null)
2355
2575
  {
2356
2576
  $.each(pre, $.proxy(function(i,s)
@@ -2379,26 +2599,30 @@
2379
2599
  },
2380
2600
  saveCodeFormatting: function(html)
2381
2601
  {
2382
- var code = html.match(/<code(.*?[^>])>(.*?)<\/code>/gi);
2602
+ var code = html.match(/<code(.*?)>([\w\W]*?)<\/code>/gi);
2603
+
2383
2604
  if (code !== null)
2384
2605
  {
2385
2606
  $.each(code, $.proxy(function(i,s)
2386
2607
  {
2387
- var arr = s.match(/<code(.*?[^>])>(.*?)<\/code>/i);
2608
+ var arr = s.match(/<code(.*?)>([\w\W]*?)<\/code>/i);
2388
2609
 
2389
2610
  arr[2] = arr[2].replace(/&nbsp;/g, ' ');
2390
2611
  arr[2] = this.clean.encodeEntities(arr[2]);
2391
-
2392
- // $ fix
2393
2612
  arr[2] = arr[2].replace(/\$/g, '&#36;');
2394
2613
 
2395
2614
  html = html.replace(s, '<code' + arr[1] + '>' + arr[2] + '</code>');
2396
-
2397
2615
  }, this));
2398
2616
  }
2399
2617
 
2400
2618
  return html;
2401
2619
  },
2620
+ restoreSelectionMarker: function(html)
2621
+ {
2622
+ html = html.replace(/&lt;span id=&quot;selection-marker-([0-9])&quot; class=&quot;redactor-selection-marker&quot; data-verified=&quot;redactor&quot;&gt;​&lt;\/span&gt;/g, '<span id="selection-marker-$1" class="redactor-selection-marker" data-verified="redactor">​</span>');
2623
+
2624
+ return html;
2625
+ },
2402
2626
  getTextFromHtml: function(html)
2403
2627
  {
2404
2628
  html = html.replace(/<br\s?\/?>|<\/H[1-6]>|<\/p>|<\/div>|<\/li>|<\/td>/gi, '\n');
@@ -2412,6 +2636,8 @@
2412
2636
  getPlainText: function(html, paragraphize)
2413
2637
  {
2414
2638
  html = this.clean.getTextFromHtml(html);
2639
+ html = html.replace(/\n\s*\n/g, "\n");
2640
+ html = html.replace(/\n\n/g, "\n");
2415
2641
  html = html.replace(/\n/g, '<br />');
2416
2642
 
2417
2643
  if (this.opts.paragraphize && typeof paragraphize == 'undefined' && !this.utils.browser('mozilla'))
@@ -2505,12 +2731,7 @@
2505
2731
  },
2506
2732
  cleanEmptyParagraph: function()
2507
2733
  {
2508
- var p = this.$editor.find("p").first();
2509
2734
 
2510
- if (this.utils.isEmpty(p.html()))
2511
- {
2512
- p.remove();
2513
- }
2514
2735
  },
2515
2736
  setVerified: function(html)
2516
2737
  {
@@ -2585,7 +2806,7 @@
2585
2806
  html = html.replace(/[\s\n]*$/g, ' ');
2586
2807
  html = html.replace( />\s{2,}</g, '> <'); // between inline tags can be only one space
2587
2808
  html = html.replace(/\n\n/g, "\n");
2588
- html = html.replace(/[\u200B-\u200D\uFEFF]/g, '');
2809
+ html = html.replace(/\u200B/g, '');
2589
2810
 
2590
2811
  return html;
2591
2812
  },
@@ -2643,6 +2864,12 @@
2643
2864
  // clean
2644
2865
  html = this.clean.onSet(html);
2645
2866
 
2867
+
2868
+ if (this.utils.browser('msie'))
2869
+ {
2870
+ html = html.replace(/<span(.*?)id="selection-marker-(1|2)"(.*?)><\/span>/gi, '');
2871
+ }
2872
+
2646
2873
  this.$editor.html(html);
2647
2874
  this.code.sync();
2648
2875
 
@@ -2656,6 +2883,9 @@
2656
2883
  {
2657
2884
  var code = this.$textarea.val();
2658
2885
 
2886
+ if (this.opts.replaceDivs) code = this.clean.replaceDivs(code);
2887
+ if (this.opts.linebreaks) code = this.clean.replaceParagraphsToBr(code);
2888
+
2659
2889
  // indent code
2660
2890
  code = this.tabifier.get(code);
2661
2891
 
@@ -2670,7 +2900,7 @@
2670
2900
  var html = this.$editor.html();
2671
2901
 
2672
2902
  // is there a need to synchronize
2673
- if (this.code.syncCode && this.code.syncCode == html)
2903
+ if (this.code.syncCode && this.code.syncCode == html || (this.start && html == '' ))
2674
2904
  {
2675
2905
  // do not sync
2676
2906
  return;
@@ -2728,6 +2958,8 @@
2728
2958
  },
2729
2959
  showCode: function()
2730
2960
  {
2961
+ this.selection.save();
2962
+
2731
2963
  this.code.offset = this.caret.getOffset();
2732
2964
  var scroll = $(window).scrollTop();
2733
2965
 
@@ -2737,11 +2969,34 @@
2737
2969
  this.$editor.hide();
2738
2970
 
2739
2971
  var html = this.$textarea.val();
2972
+
2740
2973
  this.modified = this.clean.removeSpaces(html);
2741
2974
 
2742
2975
  // indent code
2743
2976
  html = this.tabifier.get(html);
2744
2977
 
2978
+ // caret position sync
2979
+ var start = 0, end = 0;
2980
+ var $editorDiv = $("<div/>").append($.parseHTML(this.clean.onSync(this.$editor.html()), document, true));
2981
+ var $selectionMarkers = $editorDiv.find("span.redactor-selection-marker");
2982
+
2983
+ if ($selectionMarkers.length > 0)
2984
+ {
2985
+ var editorHtml = this.tabifier.get($editorDiv.html()).replace(/&amp;/g, '&');
2986
+
2987
+ if ($selectionMarkers.length == 1)
2988
+ {
2989
+ start = this.utils.strpos(editorHtml, $editorDiv.find("#selection-marker-1").prop("outerHTML"));
2990
+ end = start;
2991
+ }
2992
+ else if ($selectionMarkers.length == 2)
2993
+ {
2994
+ start = this.utils.strpos(editorHtml, $editorDiv.find("#selection-marker-1").prop("outerHTML"));
2995
+ end = this.utils.strpos(editorHtml, $editorDiv.find("#selection-marker-2").prop("outerHTML")) - $editorDiv.find("#selection-marker-1").prop("outerHTML").toString().length;
2996
+ }
2997
+ }
2998
+
2999
+ this.selection.removeMarkers();
2745
3000
  this.$textarea.val(html);
2746
3001
 
2747
3002
  if (this.opts.codemirror)
@@ -2750,8 +3005,21 @@
2750
3005
  {
2751
3006
  $(el).show();
2752
3007
  el.CodeMirror.setValue(html);
2753
- el.CodeMirror.setSize(width, height);
3008
+ el.CodeMirror.setSize('100%', height);
2754
3009
  el.CodeMirror.refresh();
3010
+
3011
+ if (start == end)
3012
+ {
3013
+ el.CodeMirror.setCursor(el.CodeMirror.posFromIndex(start).line, el.CodeMirror.posFromIndex(end).ch);
3014
+ }
3015
+ else
3016
+ {
3017
+ el.CodeMirror.setSelection({line: el.CodeMirror.posFromIndex(start).line,
3018
+ ch: el.CodeMirror.posFromIndex(start).ch},
3019
+ {line: el.CodeMirror.posFromIndex(end).line,
3020
+ ch: el.CodeMirror.posFromIndex(end).ch});
3021
+ }
3022
+
2755
3023
  el.CodeMirror.focus();
2756
3024
  });
2757
3025
  }
@@ -2764,7 +3032,7 @@
2764
3032
 
2765
3033
  if (this.$textarea[0].setSelectionRange)
2766
3034
  {
2767
- this.$textarea[0].setSelectionRange(0, 0);
3035
+ this.$textarea[0].setSelectionRange(start, end);
2768
3036
  }
2769
3037
 
2770
3038
  this.$textarea[0].scrollTop = 0;
@@ -2782,21 +3050,58 @@
2782
3050
 
2783
3051
  if (this.opts.visual) return;
2784
3052
 
3053
+ var start = 0, end = 0;
3054
+
2785
3055
  if (this.opts.codemirror)
2786
3056
  {
3057
+ var selection;
3058
+
2787
3059
  this.$textarea.next('.CodeMirror').each(function(i, el)
2788
3060
  {
3061
+ selection = el.CodeMirror.listSelections();
3062
+
3063
+ start = el.CodeMirror.indexFromPos(selection[0].anchor);
3064
+ end = el.CodeMirror.indexFromPos(selection[0].head);
3065
+
2789
3066
  html = el.CodeMirror.getValue();
2790
3067
  });
2791
3068
  }
2792
3069
  else
2793
3070
  {
3071
+ start = this.$textarea.get(0).selectionStart;
3072
+ end = this.$textarea.get(0).selectionEnd;
3073
+
2794
3074
  html = this.$textarea.hide().val();
2795
3075
  }
2796
3076
 
3077
+ // if selection starts from end
3078
+ if (start > end && end > 0)
3079
+ {
3080
+ var tempStart = end;
3081
+ var tempEnd = start;
3082
+
3083
+ start = tempStart;
3084
+ end = tempEnd;
3085
+ }
3086
+
3087
+ start = this.code.enlargeOffset(html, start);
3088
+ end = this.code.enlargeOffset(html, end);
3089
+
3090
+ html = html.substr(0, start) + this.selection.getMarkerAsHtml(1) + html.substr(start);
3091
+
3092
+ if (end > start)
3093
+ {
3094
+ var markerLength = this.selection.getMarkerAsHtml(1).toString().length;
3095
+
3096
+ html = html.substr(0, end + markerLength) + this.selection.getMarkerAsHtml(2) + html.substr(end + markerLength);
3097
+ }
3098
+
3099
+
3100
+
2797
3101
  if (this.modified !== this.clean.removeSpaces(html))
2798
3102
  {
2799
3103
  this.code.set(html);
3104
+
2800
3105
  }
2801
3106
 
2802
3107
  if (this.opts.codemirror)
@@ -2811,13 +3116,12 @@
2811
3116
  this.placeholder.remove();
2812
3117
  }
2813
3118
 
2814
- this.caret.setOffset(this.code.offset);
3119
+ this.selection.restore();
2815
3120
 
2816
3121
  this.$textarea.off('keydown.redactor-textarea-indenting');
2817
3122
 
2818
3123
  this.button.setActiveInVisual();
2819
3124
  this.button.setInactive('html');
2820
-
2821
3125
  this.observe.load();
2822
3126
  this.opts.visual = true;
2823
3127
  this.core.setCallback('visual', html);
@@ -2832,6 +3136,35 @@
2832
3136
  $el.get(0).selectionStart = $el.get(0).selectionEnd = start + 1;
2833
3137
 
2834
3138
  return false;
3139
+ },
3140
+ enlargeOffset: function(html, offset)
3141
+ {
3142
+ var htmlLength = html.length;
3143
+ var c = 0;
3144
+
3145
+ if (html[offset] == '>')
3146
+ {
3147
+ c++;
3148
+ }
3149
+ else
3150
+ {
3151
+ for(var i = offset; i <= htmlLength; i++)
3152
+ {
3153
+ c++;
3154
+
3155
+ if (html[i] == '>')
3156
+ {
3157
+ break;
3158
+ }
3159
+ else if (html[i] == '<' || i == htmlLength)
3160
+ {
3161
+ c = 0;
3162
+ break;
3163
+ }
3164
+ }
3165
+ }
3166
+
3167
+ return offset + c;
2835
3168
  }
2836
3169
  };
2837
3170
  },
@@ -2872,24 +3205,52 @@
2872
3205
  },
2873
3206
  setCallback: function(type, e, data)
2874
3207
  {
2875
- var callback = this.opts[type + 'Callback'];
2876
- if ($.isFunction(callback))
2877
- {
2878
- return (typeof data == 'undefined') ? callback.call(this, e) : callback.call(this, e, data);
2879
- }
2880
- else
3208
+ var eventName = type + 'Callback';
3209
+ var eventNamespace = 'redactor';
3210
+ var callback = this.opts[eventName];
3211
+
3212
+ if (this.$textarea)
2881
3213
  {
2882
- return (typeof data == 'undefined') ? e : data;
2883
- }
2884
- },
2885
- destroy: function()
2886
- {
3214
+ var returnValue = false;
3215
+ var events = $._data(this.$textarea[0], 'events');
3216
+
3217
+ if (typeof events != 'undefined' && typeof events[eventName] != 'undefined')
3218
+ {
3219
+ $.each(events[eventName], $.proxy(function(key, value)
3220
+ {
3221
+ if (value['namespace'] == eventNamespace)
3222
+ {
3223
+ var data = (typeof data == 'undefined') ? [e] : [e, data];
3224
+
3225
+ returnValue = (typeof data == 'undefined') ? value.handler.call(this, e) : value.handler.call(this, e, data);
3226
+ }
3227
+ }, this));
3228
+ }
3229
+
3230
+ if (returnValue) return returnValue;
3231
+ }
3232
+
3233
+ if ($.isFunction(callback))
3234
+ {
3235
+ return (typeof data == 'undefined') ? callback.call(this, e) : callback.call(this, e, data);
3236
+ }
3237
+ else
3238
+ {
3239
+ return (typeof data == 'undefined') ? e : data;
3240
+ }
3241
+ },
3242
+ destroy: function()
3243
+ {
3244
+ this.opts.destroyed = true;
3245
+
2887
3246
  this.core.setCallback('destroy');
2888
3247
 
2889
3248
  // off events and remove data
2890
3249
  this.$element.off('.redactor').removeData('redactor');
2891
3250
  this.$editor.off('.redactor');
2892
3251
 
3252
+ $(document).off('mousedown.redactor-blur.' + this.uuid);
3253
+ $(document).off('mousedown.redactor.' + this.uuid);
2893
3254
  $(document).off('click.redactor-image-delete.' + this.uuid);
2894
3255
  $(document).off('click.redactor-image-resize-hide.' + this.uuid);
2895
3256
  $(document).off('touchstart.redactor.' + this.uuid + ' click.redactor.' + this.uuid);
@@ -2902,17 +3263,19 @@
2902
3263
 
2903
3264
  var html = this.code.get();
2904
3265
 
2905
- // dropdowns off
2906
- this.$toolbar.find('a').each(function()
3266
+ if (this.opts.toolbar)
2907
3267
  {
2908
- var $el = $(this);
2909
- if ($el.data('dropdown'))
3268
+ // dropdowns off
3269
+ this.$toolbar.find('a').each(function()
2910
3270
  {
2911
- $el.data('dropdown').remove();
2912
- $el.data('dropdown', {});
2913
- }
2914
- });
2915
-
3271
+ var $el = $(this);
3272
+ if ($el.data('dropdown'))
3273
+ {
3274
+ $el.data('dropdown').remove();
3275
+ $el.data('dropdown', {});
3276
+ }
3277
+ });
3278
+ }
2916
3279
 
2917
3280
  if (this.build.isTextarea())
2918
3281
  {
@@ -2935,11 +3298,10 @@
2935
3298
  if (this.$modalOverlay) this.$modalOverlay.remove();
2936
3299
 
2937
3300
  // buttons tooltip
2938
- $('.redactor-toolbar-tooltip').remove();
3301
+ $('.redactor-toolbar-tooltip-' + this.uuid).remove();
2939
3302
 
2940
3303
  // autosave
2941
3304
  clearInterval(this.autosaveInterval);
2942
-
2943
3305
  }
2944
3306
  };
2945
3307
  },
@@ -2988,12 +3350,11 @@
2988
3350
  };
2989
3351
 
2990
3352
  }, this));
2991
-
2992
3353
  }
2993
3354
 
2994
3355
  $.each(dropdownObject, $.proxy(function(btnName, btnObject)
2995
3356
  {
2996
- var $item = $('<a href="#" class="redactor-dropdown-' + btnName + '">' + btnObject.title + '</a>');
3357
+ var $item = $('<a href="#" class="redactor-dropdown-' + btnName + '" role="button">' + btnObject.title + '</a>');
2997
3358
  if (name == 'formatting') $item.addClass('redactor-formatting-' + btnName);
2998
3359
 
2999
3360
  $item.on('click', $.proxy(function(e)
@@ -3013,11 +3374,15 @@
3013
3374
  callback = btnObject.dropdown;
3014
3375
  }
3015
3376
 
3377
+ if ($(e.target).hasClass('redactor-dropdown-link-inactive')) return;
3378
+
3016
3379
  this.button.onClick(e, btnName, type, callback);
3017
3380
  this.dropdown.hideAll();
3018
3381
 
3019
3382
  }, this));
3020
3383
 
3384
+ this.observe.addDropdown($item, btnName, btnObject);
3385
+
3021
3386
  $dropdown.append($item);
3022
3387
 
3023
3388
  }, this));
@@ -3035,10 +3400,9 @@
3035
3400
  // Always re-append it to the end of <body> so it always has the highest sub-z-index.
3036
3401
  var $dropdown = $button.data('dropdown').appendTo(document.body);
3037
3402
 
3038
- // ios keyboard hide
3039
- if (this.utils.isMobile() && !this.utils.browser('msie'))
3403
+ if (this.opts.highContrast)
3040
3404
  {
3041
- document.activeElement.blur();
3405
+ $dropdown.addClass("redactor-dropdown-contrast");
3042
3406
  }
3043
3407
 
3044
3408
  if ($button.hasClass('dropact'))
@@ -3048,6 +3412,8 @@
3048
3412
  else
3049
3413
  {
3050
3414
  this.dropdown.hideAll();
3415
+ this.observe.dropdowns();
3416
+
3051
3417
  this.core.setCallback('dropdownShow', { dropdown: $dropdown, key: key, button: $button });
3052
3418
 
3053
3419
  this.button.setActive(key);
@@ -3084,45 +3450,57 @@
3084
3450
  }
3085
3451
 
3086
3452
  this.core.setCallback('dropdownShown', { dropdown: $dropdown, key: key, button: $button });
3087
- }
3088
-
3089
- $(document).one('click', $.proxy(this.dropdown.hide, this));
3090
- this.$editor.one('click', $.proxy(this.dropdown.hide, this));
3091
-
3092
- // disable scroll whan dropdown scroll
3093
- var $body = $(document.body);
3094
- var width = $body.width();
3095
3453
 
3096
- $dropdown.on('mouseover', function() {
3097
-
3098
- $body.addClass('body-redactor-hidden');
3099
- $body.css('margin-right', ($body.width() - width) + 'px');
3100
-
3101
- });
3102
-
3103
- $dropdown.on('mouseout', function() {
3454
+ this.$dropdown = $dropdown;
3455
+ }
3104
3456
 
3105
- $body.removeClass('body-redactor-hidden').css('margin-right', 0);
3106
3457
 
3107
- });
3458
+ $(document).one('click.redactor-dropdown', $.proxy(this.dropdown.hide, this));
3459
+ this.$editor.one('click.redactor-dropdown', $.proxy(this.dropdown.hide, this));
3460
+ $(document).one('keyup.redactor-dropdown', $.proxy(this.dropdown.closeHandler, this));
3108
3461
 
3462
+ // disable scroll whan dropdown scroll
3463
+ $dropdown.on('mouseover.redactor-dropdown', $.proxy(this.utils.disableBodyScroll, this)).on('mouseout.redactor-dropdown', $.proxy(this.utils.enableBodyScroll, this));
3109
3464
 
3110
3465
  e.stopPropagation();
3111
3466
  },
3467
+ closeHandler: function(e)
3468
+ {
3469
+ if (e.which != this.keyCode.ESC) return;
3470
+
3471
+ this.dropdown.hideAll();
3472
+ this.$editor.focus();
3473
+ },
3112
3474
  hideAll: function()
3113
3475
  {
3114
3476
  this.$toolbar.find('a.dropact').removeClass('redactor-act').removeClass('dropact');
3115
3477
 
3116
- $(document.body).removeClass('body-redactor-hidden').css('margin-right', 0);
3478
+ this.utils.enableBodyScroll();
3479
+
3117
3480
  $('.redactor-dropdown-' + this.uuid).hide();
3118
- this.core.setCallback('dropdownHide');
3481
+ $('.redactor-dropdown-link-selected').removeClass('redactor-dropdown-link-selected');
3482
+
3483
+
3484
+ if (this.$dropdown)
3485
+ {
3486
+ this.$dropdown.off('.redactor-dropdown');
3487
+ this.core.setCallback('dropdownHide', this.$dropdown);
3488
+
3489
+ this.$dropdown = false;
3490
+ }
3119
3491
  },
3120
3492
  hide: function (e)
3121
3493
  {
3122
3494
  var $dropdown = $(e.target);
3123
- if (!$dropdown.hasClass('dropact'))
3495
+
3496
+ if (!$dropdown.hasClass('dropact') && !$dropdown.hasClass('redactor-dropdown-link-inactive'))
3124
3497
  {
3125
- $dropdown.removeClass('dropact');
3498
+ if ($dropdown.hasClass('redactor-dropdown'))
3499
+ {
3500
+ $dropdown.removeClass('dropact');
3501
+ $dropdown.off('mouseover mouseout');
3502
+ }
3503
+
3126
3504
  this.dropdown.hideAll();
3127
3505
  }
3128
3506
  }
@@ -3240,36 +3618,31 @@
3240
3618
  },
3241
3619
  setEnd: function()
3242
3620
  {
3243
- if (this.utils.browser('mozilla') || this.utils.browser('msie'))
3621
+ var last = this.$editor.children().last();
3622
+ this.$editor.focus();
3623
+
3624
+ if (last.size() === 0) return;
3625
+ if (this.utils.isEmpty(this.$editor.html()))
3244
3626
  {
3245
- var last = this.$editor.children().last();
3246
3627
 
3247
- this.$editor.focus();
3248
- this.caret.setEnd(last);
3628
+ this.selection.get();
3629
+ this.range.collapse(true);
3630
+ this.range.setStartAfter(last[0]);
3631
+ this.range.setEnd(last[0], 0);
3632
+ this.selection.addRange();
3249
3633
  }
3250
3634
  else
3251
3635
  {
3252
3636
  this.selection.get();
3637
+ this.range.selectNodeContents(last[0]);
3638
+ this.range.collapse(false);
3639
+ this.selection.addRange();
3253
3640
 
3254
- try {
3255
- this.range.selectNodeContents(this.$editor[0]);
3256
- this.range.collapse(false);
3257
-
3258
- this.selection.addRange();
3259
- }
3260
- catch (e) {}
3261
3641
  }
3262
-
3263
3642
  },
3264
3643
  isFocused: function()
3265
3644
  {
3266
- var focusNode = document.getSelection().focusNode;
3267
- if (focusNode === null) return false;
3268
-
3269
- if (this.opts.linebreaks && $(focusNode.parentNode).hasClass('redactor-linebreaks')) return true;
3270
- else if (!this.utils.isRedactorParent(focusNode.parentNode)) return false;
3271
-
3272
- return this.$editor.is(':focus');
3645
+ return this.$editor[0] === document.activeElement;
3273
3646
  }
3274
3647
  };
3275
3648
  },
@@ -3307,6 +3680,9 @@
3307
3680
 
3308
3681
  }, this));
3309
3682
 
3683
+ // hide link's tooltip
3684
+ $('.redactor-link-tooltip').remove();
3685
+
3310
3686
  $('#redactor-image-title').val($image.attr('alt'));
3311
3687
 
3312
3688
  if (!this.opts.imageLink) $('.redactor-image-link-option').hide();
@@ -3330,6 +3706,7 @@
3330
3706
  }
3331
3707
 
3332
3708
  this.modal.show();
3709
+ $('#redactor-image-title').focus();
3333
3710
 
3334
3711
  },
3335
3712
  setFloating: function($image)
@@ -3366,12 +3743,14 @@
3366
3743
 
3367
3744
  var $link = $image.closest('a', this.$editor[0]);
3368
3745
 
3369
- $image.attr('alt', $('#redactor-image-title').val());
3746
+ var title = $('#redactor-image-title').val().replace(/(<([^>]+)>)/ig,"");
3747
+ $image.attr('alt', title);
3370
3748
 
3371
3749
  this.image.setFloating($image);
3372
3750
 
3373
3751
  // as link
3374
3752
  var link = $.trim($('#redactor-image-link').val());
3753
+ var link = link.replace(/(<([^>]+)>)/ig,"");
3375
3754
  if (link !== '')
3376
3755
  {
3377
3756
  // test url (add protocol)
@@ -3425,17 +3804,14 @@
3425
3804
  $image.on('dragstart', $.proxy(this.image.onDrag, this));
3426
3805
  }
3427
3806
 
3428
- $image.on('mousedown', $.proxy(this.image.hideResize, this));
3429
- $image.on('click.redactor touchstart', $.proxy(function(e)
3807
+ var handler = $.proxy(function(e)
3430
3808
  {
3431
- this.observe.image = $image;
3432
3809
 
3433
- if (this.$editor.find('#redactor-image-box').length !== 0) return false;
3810
+ this.observe.image = $image;
3434
3811
 
3435
3812
  this.image.resizer = this.image.loadEditableControls($image);
3436
3813
 
3437
- $(document).on('click.redactor-image-resize-hide.' + this.uuid, $.proxy(this.image.hideResize, this));
3438
- this.$editor.on('click.redactor-image-resize-hide.' + this.uuid, $.proxy(this.image.hideResize, this));
3814
+ $(document).on('mousedown.redactor-image-resize-hide.' + this.uuid, $.proxy(this.image.hideResize, this));
3439
3815
 
3440
3816
  // resize
3441
3817
  if (!this.opts.imageResizable) return;
@@ -3445,8 +3821,11 @@
3445
3821
  this.image.setResizable(e, $image);
3446
3822
  }, this));
3447
3823
 
3824
+ }, this);
3448
3825
 
3449
- }, this));
3826
+
3827
+ $image.off('mousedown.redactor').on('mousedown.redactor', $.proxy(this.image.hideResize, this));
3828
+ $image.off('click.redactor touchstart.redactor').on('click.redactor touchstart.redactor', handler);
3450
3829
  },
3451
3830
  setResizable: function(e, $image)
3452
3831
  {
@@ -3550,12 +3929,8 @@
3550
3929
  var imageBox = this.$editor.find('#redactor-image-box');
3551
3930
  if (imageBox.length === 0) return;
3552
3931
 
3553
- if (this.opts.imageEditable)
3554
- {
3555
- this.image.editter.remove();
3556
- }
3557
-
3558
- $(this.image.resizer).remove();
3932
+ $('#redactor-image-editter').remove();
3933
+ $('#redactor-image-resizer').remove();
3559
3934
 
3560
3935
  imageBox.find('img').css({
3561
3936
  marginTop: imageBox[0].style.marginTop,
@@ -3571,8 +3946,8 @@
3571
3946
  return $(this).contents();
3572
3947
  });
3573
3948
 
3574
- $(document).off('click.redactor-image-resize-hide.' + this.uuid);
3575
- this.$editor.off('click.redactor-image-resize-hide.' + this.uuid);
3949
+ $(document).off('mousedown.redactor-image-resize-hide.' + this.uuid);
3950
+
3576
3951
 
3577
3952
  if (typeof this.image.resizeHandle !== 'undefined')
3578
3953
  {
@@ -3748,7 +4123,12 @@
3748
4123
  }
3749
4124
  else if (this.opts.linebreaks)
3750
4125
  {
3751
- $image.before('<br>').after('<br>');
4126
+ if (!this.utils.isEmpty(this.code.get()))
4127
+ {
4128
+ $image.before('<br>');
4129
+ }
4130
+
4131
+ $image.after('<br>');
3752
4132
  }
3753
4133
 
3754
4134
  if (typeof json == 'string') return;
@@ -3832,18 +4212,12 @@
3832
4212
  this.selection.restore();
3833
4213
  this.code.sync();
3834
4214
  },
3835
- decreaseLists: function ()
4215
+ decreaseLists: function()
3836
4216
  {
3837
4217
  document.execCommand('outdent');
3838
4218
 
3839
4219
  var current = this.selection.getCurrent();
3840
-
3841
4220
  var $item = $(current).closest('li', this.$editor[0]);
3842
- var $parent = $item.parent();
3843
- if ($item.length !== 0 && $parent.length !== 0 && $parent[0].tagName == 'LI')
3844
- {
3845
- $parent.after($item);
3846
- }
3847
4221
 
3848
4222
  this.indent.fixEmptyIndent();
3849
4223
 
@@ -3911,6 +4285,9 @@
3911
4285
  },
3912
4286
  format: function(tag, type, value)
3913
4287
  {
4288
+ var current = this.selection.getCurrent();
4289
+ if (current && current.tagName === 'TR') return;
4290
+
3914
4291
  // Stop formatting pre and headers
3915
4292
  if (this.utils.isCurrentOrParent('PRE') || this.utils.isCurrentOrParentHeader()) return;
3916
4293
 
@@ -3922,6 +4299,7 @@
3922
4299
  if (tag == tags[i]) tag = replaced[i];
3923
4300
  }
3924
4301
 
4302
+
3925
4303
  if (this.opts.allowedTags)
3926
4304
  {
3927
4305
  if ($.inArray(tag, this.opts.allowedTags) == -1) return;
@@ -3936,7 +4314,7 @@
3936
4314
 
3937
4315
  this.buffer.set();
3938
4316
 
3939
- if (!this.utils.browser('msie'))
4317
+ if (!this.utils.browser('msie') && !this.opts.linebreaks)
3940
4318
  {
3941
4319
  this.$editor.focus();
3942
4320
  }
@@ -4012,11 +4390,17 @@
4012
4390
  }
4013
4391
 
4014
4392
  $el.replaceWith($span.html($el.contents()));
4393
+ var $parent = $span.parent();
4394
+
4395
+ // remove U tag if selected link + node
4396
+ if ($span[0].tagName === 'A' && $parent && $parent[0].tagName === 'U')
4397
+ {
4398
+ $span.parent().replaceWith($span);
4399
+ }
4015
4400
 
4016
4401
  if (tag == 'span')
4017
4402
  {
4018
- var $parent = $span.parent();
4019
- if ($parent && $parent[0].tagName == 'SPAN' && this.inline.type == 'style')
4403
+ if ($parent && $parent[0].tagName === 'SPAN' && this.inline.type === 'style')
4020
4404
  {
4021
4405
  var arr = this.inline.value.split(';');
4022
4406
 
@@ -4044,8 +4428,16 @@
4044
4428
  this.$editor.find(this.opts.inlineTags.join(', ')).each($.proxy(function(i,s)
4045
4429
  {
4046
4430
  var $el = $(s);
4431
+
4432
+
4433
+ if (s.tagName === 'U' && s.attributes.length === 0)
4434
+ {
4435
+ $el.replaceWith($el.contents());
4436
+ return;
4437
+ }
4438
+
4047
4439
  var property = $el.css('text-decoration');
4048
- if (property == 'line-through')
4440
+ if (property === 'line-through')
4049
4441
  {
4050
4442
  $el.css('text-decoration', '');
4051
4443
  this.utils.removeEmptyAttr($el, 'style');
@@ -4062,6 +4454,15 @@
4062
4454
  });
4063
4455
  }
4064
4456
 
4457
+ if (tag != 'u')
4458
+ {
4459
+ var _this = this;
4460
+ this.$editor.find('unline').each(function(i,s)
4461
+ {
4462
+ _this.utils.replaceToTag(s, 'u');
4463
+ });
4464
+ }
4465
+
4065
4466
  this.selection.restore();
4066
4467
  this.code.sync();
4067
4468
 
@@ -4121,6 +4522,14 @@
4121
4522
  });
4122
4523
  }
4123
4524
 
4525
+ if (tag != 'u')
4526
+ {
4527
+ this.$editor.find('u').each(function(i,s)
4528
+ {
4529
+ self.utils.replaceToTag(s, 'unline');
4530
+ });
4531
+ }
4532
+
4124
4533
  if (tag != 'span')
4125
4534
  {
4126
4535
  this.$editor.find(tag).each(function()
@@ -4360,7 +4769,10 @@
4360
4769
 
4361
4770
  if (typeof clean == 'undefined') clean = true;
4362
4771
 
4363
- this.$editor.focus();
4772
+ if (!this.opts.linebreaks)
4773
+ {
4774
+ this.$editor.focus();
4775
+ }
4364
4776
 
4365
4777
  html = this.clean.setVerified(html);
4366
4778
 
@@ -4455,6 +4867,7 @@
4455
4867
  {
4456
4868
  node = node[0] || node;
4457
4869
 
4870
+ var offset = this.caret.getOffset();
4458
4871
  var html = this.utils.getOuterHtml(node);
4459
4872
  html = this.clean.setVerified(html);
4460
4873
 
@@ -4474,6 +4887,8 @@
4474
4887
  this.range.collapse(false);
4475
4888
  this.selection.addRange();
4476
4889
 
4890
+ this.caret.setOffset(offset);
4891
+
4477
4892
  return node;
4478
4893
  },
4479
4894
  nodeToPoint: function(node, x, y)
@@ -4546,6 +4961,7 @@
4546
4961
  var parHtml = $(parent).html();
4547
4962
 
4548
4963
  parHtml = '<p>' + parHtml.replace(/<span class="redactor-ie-paste"><\/span>/gi, '</p>' + html + '<p>') + '</p>';
4964
+ parHtml = parHtml.replace(/<p><\/p>/gi, '');
4549
4965
  $(parent).replaceWith(parHtml);
4550
4966
  },
4551
4967
  ie11PasteFrag: function(html)
@@ -4591,8 +5007,12 @@
4591
5007
  // shortcuts setup
4592
5008
  this.shortcuts.init(e, key);
4593
5009
 
4594
- this.keydown.checkEvents(arrow, key);
4595
- this.keydown.setupBuffer(e, key);
5010
+ if (this.utils.isDesktop())
5011
+ {
5012
+ this.keydown.checkEvents(arrow, key);
5013
+ this.keydown.setupBuffer(e, key);
5014
+ }
5015
+
4596
5016
  this.keydown.addArrowsEvent(arrow);
4597
5017
  this.keydown.setupSelectAll(e, key);
4598
5018
 
@@ -4709,6 +5129,7 @@
4709
5129
 
4710
5130
  return this.keydown.insertBreakLine(e);
4711
5131
  }
5132
+
4712
5133
  }
4713
5134
  else if (this.opts.linebreaks && this.keydown.block)
4714
5135
  {
@@ -4717,11 +5138,9 @@
4717
5138
  // paragraphs
4718
5139
  else if (!this.opts.linebreaks && this.keydown.block)
4719
5140
  {
4720
- if (this.keydown.block.tagName !== 'LI')
4721
- {
4722
- setTimeout($.proxy(this.keydown.replaceDivToParagraph, this), 1);
4723
- }
4724
- else
5141
+ setTimeout($.proxy(this.keydown.replaceDivToParagraph, this), 1);
5142
+
5143
+ if (this.keydown.block.tagName === 'LI')
4725
5144
  {
4726
5145
  current = this.selection.getCurrent();
4727
5146
  var $parent = $(current).closest('li', this.$editor[0]);
@@ -4761,13 +5180,8 @@
4761
5180
  // image delete and backspace
4762
5181
  if (key === this.keyCode.BACKSPACE || key === this.keyCode.DELETE)
4763
5182
  {
4764
- if (this.utils.browser('mozilla') && this.keydown.current && this.keydown.current.tagName === 'TD')
4765
- {
4766
- e.preventDefault();
4767
- return false;
4768
- }
4769
-
4770
5183
  var nodes = this.selection.getNodes();
5184
+
4771
5185
  if (nodes)
4772
5186
  {
4773
5187
  var len = nodes.length;
@@ -4804,6 +5218,25 @@
4804
5218
  // backspace
4805
5219
  if (key === this.keyCode.BACKSPACE)
4806
5220
  {
5221
+ // backspace as outdent
5222
+ var block = this.selection.getBlock();
5223
+ var indented = ($(block).css('margin-left') !== '0px');
5224
+ if (block && indented && this.range.collapsed && this.utils.isStartOfElement())
5225
+ {
5226
+ this.indent.decrease();
5227
+ e.preventDefault();
5228
+ return;
5229
+ }
5230
+
5231
+ // remove hr in FF
5232
+ if (this.utils.browser('mozilla'))
5233
+ {
5234
+ var prev = this.selection.getPrev();
5235
+ var prev2 = $(prev).prev()[0];
5236
+ if (prev && prev.tagName === 'HR') $(prev).remove();
5237
+ if (prev2 && prev2.tagName === 'HR') $(prev2).remove();
5238
+ }
5239
+
4807
5240
  this.keydown.removeInvisibleSpace();
4808
5241
  this.keydown.removeEmptyListInTable(e);
4809
5242
  }
@@ -4825,7 +5258,7 @@
4825
5258
  checkKeyEvents: function(key)
4826
5259
  {
4827
5260
  var k = this.keyCode;
4828
- var keys = [k.BACKSPACE, k.DELETE, k.ENTER, k.SPACE, k.ESC, k.TAB, k.CTRL, k.META, k.ALT, k.SHIFT];
5261
+ var keys = [k.BACKSPACE, k.DELETE, k.ENTER, k.ESC, k.TAB, k.CTRL, k.META, k.ALT, k.SHIFT];
4829
5262
 
4830
5263
  return ($.inArray(key, keys) == -1) ? true : false;
4831
5264
 
@@ -4859,7 +5292,7 @@
4859
5292
  }
4860
5293
  else if (!this.keydown.ctrl)
4861
5294
  {
4862
- if (key == this.keyCode.BACKSPACE || key == this.keyCode.DELETE || (key == this.keyCode.ENTER && !e.ctrlKey && !e.shiftKey) || key == this.keyCode.SPACE)
5295
+ if (key == this.keyCode.BACKSPACE || key == this.keyCode.DELETE || (key == this.keyCode.ENTER && !e.ctrlKey && !e.shiftKey))
4863
5296
  {
4864
5297
  this.buffer.set();
4865
5298
  }
@@ -4950,7 +5383,7 @@
4950
5383
  {
4951
5384
  var blockElem = this.selection.getBlock();
4952
5385
  var blockHtml = blockElem.innerHTML.replace(/<br\s?\/?>/gi, '');
4953
- if (blockElem.tagName === 'DIV' && blockHtml === '' && !$(blockElem).hasClass('redactor-editor'))
5386
+ if (blockElem.tagName === 'DIV' && this.utils.isEmpty(blockHtml) && !$(blockElem).hasClass('redactor-editor'))
4954
5387
  {
4955
5388
  var p = document.createElement('p');
4956
5389
  p.innerHTML = this.opts.invisibleSpace;
@@ -5097,9 +5530,7 @@
5097
5530
 
5098
5531
  if ($parentA.length > 0)
5099
5532
  {
5100
- $parentA.find(br1)
5101
- .remove();
5102
-
5533
+ $parentA.find(br1).remove();
5103
5534
  $parentA.after(br1);
5104
5535
  }
5105
5536
 
@@ -5120,23 +5551,32 @@
5120
5551
  }
5121
5552
  else
5122
5553
  {
5123
- this.keydown.insertBreakLineProcessingAfter(br1);
5554
+ // caret does not move after the br visual
5555
+ if (this.utils.browser('msie'))
5556
+ {
5557
+ var space = document.createElement('span');
5558
+ space.innerHTML = '&#x200b;';
5559
+
5560
+ $(br1).after(space);
5561
+
5562
+ this.range.setStartAfter(space);
5563
+ this.range.setEndAfter(space);
5564
+ $(space).remove();
5565
+ }
5566
+ else
5567
+ {
5568
+ var range = document.createRange();
5569
+ range.setStartAfter(br1);
5570
+ range.collapse(true);
5571
+ var selection = window.getSelection();
5572
+ selection.removeAllRanges();
5573
+ selection.addRange(range);
5574
+ }
5124
5575
  }
5125
5576
 
5126
5577
  this.code.sync();
5127
5578
  return false;
5128
5579
  },
5129
- insertBreakLineProcessingAfter: function(node)
5130
- {
5131
- var space = this.utils.createSpaceElement();
5132
- $(node).after(space);
5133
- this.selection.selectElement(space);
5134
-
5135
- $(space).replaceWith(function()
5136
- {
5137
- return $(this).contents();
5138
- });
5139
- },
5140
5580
  removeInvisibleSpace: function()
5141
5581
  {
5142
5582
  var $current = $(this.keydown.current);
@@ -5170,6 +5610,7 @@
5170
5610
  return {
5171
5611
  init: function(e)
5172
5612
  {
5613
+
5173
5614
  if (this.rtePaste) return;
5174
5615
 
5175
5616
  var key = e.which;
@@ -5187,7 +5628,7 @@
5187
5628
  }
5188
5629
 
5189
5630
  // replace to p before / after the table or body
5190
- if (!this.opts.linebreaks && this.keyup.current.nodeType == 3 && this.keyup.current.length <= 1 && (this.keyup.parent === false || this.keyup.parent.tagName == 'BODY'))
5631
+ if (!this.opts.linebreaks && this.keyup.current.nodeType === 3 && this.keyup.current.length <= 1 && (this.keyup.parent === false || this.keyup.parent.tagName == 'BODY'))
5191
5632
  {
5192
5633
  this.keyup.replaceToParagraph();
5193
5634
  }
@@ -5206,11 +5647,20 @@
5206
5647
  }
5207
5648
 
5208
5649
  // linkify
5209
- if (this.linkify.isEnabled() && this.linkify.isKey(key))
5210
- this.linkify.format();
5650
+ if (this.linkify.isEnabled() && this.linkify.isKey(key)) this.linkify.format();
5211
5651
 
5212
5652
  if (key === this.keyCode.DELETE || key === this.keyCode.BACKSPACE)
5213
5653
  {
5654
+ if (this.utils.browser('mozilla'))
5655
+ {
5656
+ var td = $(this.keydown.current).closest('td', this.$editor[0]);
5657
+ if (td.size() !== 0 && td.text() !== '')
5658
+ {
5659
+ e.preventDefault();
5660
+ return false;
5661
+ }
5662
+ }
5663
+
5214
5664
  // clear unverified
5215
5665
  this.clean.clearUnverified();
5216
5666
 
@@ -5241,6 +5691,8 @@
5241
5691
  $(this.keyup.current).remove();
5242
5692
  }
5243
5693
 
5694
+ this.keyup.removeEmptyLists();
5695
+
5244
5696
  // if empty
5245
5697
  return this.keyup.formatEmpty(e);
5246
5698
  }
@@ -5268,6 +5720,20 @@
5268
5720
 
5269
5721
  this.caret.setEnd(node);
5270
5722
  },
5723
+ removeEmptyLists: function()
5724
+ {
5725
+ var removeIt = function()
5726
+ {
5727
+ var html = $.trim(this.innerHTML).replace(/\/t\/n/g, '');
5728
+ if (html === '')
5729
+ {
5730
+ $(this).remove();
5731
+ }
5732
+ };
5733
+
5734
+ this.$editor.find('li').each(removeIt);
5735
+ this.$editor.find('ul, ol').each(removeIt);
5736
+ },
5271
5737
  formatEmpty: function(e)
5272
5738
  {
5273
5739
  var html = $.trim(this.$editor.html());
@@ -5283,9 +5749,7 @@
5283
5749
  }
5284
5750
  else
5285
5751
  {
5286
- html = '<p><br /></p>';
5287
-
5288
- this.$editor.html(html);
5752
+ this.$editor.html(this.opts.emptyHtml);
5289
5753
  this.focus.setStart();
5290
5754
  }
5291
5755
 
@@ -5365,7 +5829,7 @@
5365
5829
  var extra = '<p id="redactor-insert-line"><br /></p>';
5366
5830
  if (this.opts.linebreaks) extra = '<br id="redactor-insert-line">';
5367
5831
 
5368
- document.execCommand('insertHTML', false, '<hr>' + extra);
5832
+ document.execCommand('insertHtml', false, '<hr>' + extra);
5369
5833
 
5370
5834
  this.line.setFocus();
5371
5835
  this.code.sync();
@@ -5374,16 +5838,43 @@
5374
5838
  {
5375
5839
  var node = this.$editor.find('#redactor-insert-line');
5376
5840
  var next = $(node).next()[0];
5841
+ var target = next;
5842
+ if (this.utils.browser('mozilla') && next && next.innerHTML === '')
5843
+ {
5844
+ target = $(next).next()[0];
5845
+ $(next).remove();
5846
+ }
5377
5847
 
5378
- if (next)
5848
+ if (target)
5379
5849
  {
5380
- this.caret.setAfter(node);
5381
5850
  node.remove();
5851
+
5852
+ if (!this.opts.linebreaks)
5853
+ {
5854
+ this.$editor.focus();
5855
+ this.line.setStart(target);
5856
+ }
5857
+
5382
5858
  }
5383
5859
  else
5384
5860
  {
5861
+
5385
5862
  node.removeAttr('id');
5863
+ this.line.setStart(node[0]);
5386
5864
  }
5865
+ },
5866
+ setStart: function(node)
5867
+ {
5868
+ if (typeof node === 'undefined') return;
5869
+
5870
+ var textNode = document.createTextNode('\u200B');
5871
+
5872
+ this.selection.get();
5873
+ this.range.setStart(node, 0);
5874
+ this.range.insertNode(textNode);
5875
+ this.range.collapse(true);
5876
+ this.selection.addRange();
5877
+
5387
5878
  }
5388
5879
  };
5389
5880
  },
@@ -5394,10 +5885,20 @@
5394
5885
  {
5395
5886
  if (typeof e != 'undefined' && e.preventDefault) e.preventDefault();
5396
5887
 
5397
- this.modal.load('link', this.lang.get('link_insert'), 600);
5888
+ if (!this.observe.isCurrent('a'))
5889
+ {
5890
+ this.modal.load('link', this.lang.get('link_insert'), 600);
5891
+ }
5892
+ else
5893
+ {
5894
+ this.modal.load('link', this.lang.get('link_edit'), 600);
5895
+ }
5398
5896
 
5399
5897
  this.modal.createCancelButton();
5400
- this.link.buttonInsert = this.modal.createActionButton(this.lang.get('insert'));
5898
+
5899
+ var buttonText = !this.observe.isCurrent('a') ? this.lang.get('insert') : this.lang.get('edit');
5900
+
5901
+ this.link.buttonInsert = this.modal.createActionButton(buttonText);
5401
5902
 
5402
5903
  this.selection.get();
5403
5904
 
@@ -5467,7 +5968,7 @@
5467
5968
 
5468
5969
  var target = '';
5469
5970
  var link = this.link.$inputUrl.val();
5470
- var text = this.link.$inputText.val();
5971
+ var text = this.link.$inputText.val().replace(/(<([^>]+)>)/ig,"");
5471
5972
 
5472
5973
  if ($.trim(link) === '')
5473
5974
  {
@@ -5484,6 +5985,7 @@
5484
5985
  // mailto
5485
5986
  if (link.search('@') != -1 && /(http|ftp|https):\/\//i.test(link) === false)
5486
5987
  {
5988
+ link = link.replace('mailto:', '');
5487
5989
  link = 'mailto:' + link;
5488
5990
  }
5489
5991
  // url, not anchor
@@ -5513,6 +6015,7 @@
5513
6015
  text = $.trim(text.replace(/<|>/g, ''));
5514
6016
 
5515
6017
  this.selection.restore();
6018
+ var blocks = this.selection.getBlocks();
5516
6019
 
5517
6020
  if (text === '' && link === '') return;
5518
6021
  if (text === '' && link !== '') text = link;
@@ -5561,7 +6064,13 @@
5561
6064
  var $a = $('<a />').attr('href', link).text(text);
5562
6065
  if (target !== '') $a.attr('target', target);
5563
6066
 
5564
- this.insert.node($a);
6067
+ $a = $(this.insert.node($a));
6068
+
6069
+ if (this.opts.linebreaks)
6070
+ {
6071
+ $a.after('&nbsp;');
6072
+ }
6073
+
5565
6074
  this.selection.selectElement($a);
5566
6075
  }
5567
6076
  else
@@ -5601,7 +6110,14 @@
5601
6110
 
5602
6111
  if (this.link.text !== '' || this.link.text != text)
5603
6112
  {
5604
- $a.text(text);
6113
+ if (!this.opts.linebreaks && blocks && blocks.length <= 1)
6114
+ {
6115
+ $a.text(text);
6116
+ }
6117
+ else if (this.opts.linebreaks)
6118
+ {
6119
+ $a.text(text);
6120
+ }
5605
6121
 
5606
6122
  this.selection.selectElement($a);
5607
6123
  }
@@ -5633,12 +6149,20 @@
5633
6149
  this.buffer.set();
5634
6150
 
5635
6151
  var len = nodes.length;
6152
+ var links = [];
5636
6153
  for (var i = 0; i < len; i++)
5637
6154
  {
6155
+ if (nodes[i].tagName === 'A')
6156
+ {
6157
+ links.push(nodes[i]);
6158
+ }
6159
+
5638
6160
  var $node = $(nodes[i]).closest('a', this.$editor[0]);
5639
6161
  $node.replaceWith($node.contents());
5640
6162
  }
5641
6163
 
6164
+ this.core.setCallback('deletedLink', links);
6165
+
5642
6166
  // hide link's tooltip
5643
6167
  $('.redactor-link-tooltip').remove();
5644
6168
 
@@ -5669,13 +6193,171 @@
5669
6193
  }
5670
6194
  };
5671
6195
  },
6196
+ linkify: function()
6197
+ {
6198
+ return {
6199
+ isKey: function(key)
6200
+ {
6201
+ return key == this.keyCode.ENTER || key == this.keyCode.SPACE;
6202
+ },
6203
+ isEnabled: function()
6204
+ {
6205
+ return this.opts.convertLinks && (this.opts.convertUrlLinks || this.opts.convertImageLinks || this.opts.convertVideoLinks) && !this.utils.isCurrentOrParent('PRE');
6206
+ },
6207
+ format: function()
6208
+ {
6209
+ var linkify = this.linkify,
6210
+ opts = this.opts;
6211
+
6212
+ this.$editor
6213
+ .find(":not(iframe,img,a,pre)")
6214
+ .addBack()
6215
+ .contents()
6216
+ .filter(function()
6217
+ {
6218
+ return this.nodeType === 3 && $.trim(this.nodeValue) != "" && !$(this).parent().is("pre") && (this.nodeValue.match(opts.linkify.regexps.youtube) || this.nodeValue.match(opts.linkify.regexps.vimeo) || this.nodeValue.match(opts.linkify.regexps.image) || this.nodeValue.match(opts.linkify.regexps.url));
6219
+ })
6220
+ .each(function()
6221
+ {
6222
+ var text = $(this).text(),
6223
+ html = text;
6224
+
6225
+ if (opts.convertVideoLinks && (html.match(opts.linkify.regexps.youtube) || html.match(opts.linkify.regexps.vimeo)) )
6226
+ {
6227
+ html = linkify.convertVideoLinks(html);
6228
+ }
6229
+ else if (opts.convertImageLinks && html.match(opts.linkify.regexps.image))
6230
+ {
6231
+ html = linkify.convertImages(html);
6232
+ }
6233
+ else if (opts.convertUrlLinks)
6234
+ {
6235
+ html = linkify.convertLinks(html);
6236
+ }
6237
+
6238
+ $(this).before(text.replace(text, html))
6239
+ .remove();
6240
+ });
6241
+
6242
+
6243
+ var objects = this.$editor.find('.redactor-linkify-object').each(function()
6244
+ {
6245
+ var $el = $(this);
6246
+ $el.removeClass('redactor-linkify-object');
6247
+ if ($el.attr('class') === '') $el.removeAttr('class');
6248
+
6249
+ return $el[0];
6250
+
6251
+ });
6252
+
6253
+ // callback
6254
+ setTimeout($.proxy(function()
6255
+ {
6256
+ this.observe.load();
6257
+ this.core.setCallback('linkify', objects);
6258
+ }, this), 100);
6259
+
6260
+ // sync
6261
+ this.code.sync();
6262
+ },
6263
+ convertVideoLinks: function(html)
6264
+ {
6265
+ var iframeStart = '<iframe class="redactor-linkify-object" width="500" height="281" src="',
6266
+ iframeEnd = '" frameborder="0" allowfullscreen></iframe>';
6267
+
6268
+ if (html.match(this.opts.linkify.regexps.youtube))
6269
+ {
6270
+ html = html.replace(this.opts.linkify.regexps.youtube, iframeStart + '//www.youtube.com/embed/$1' + iframeEnd);
6271
+ }
6272
+
6273
+ if (html.match(this.opts.linkify.regexps.vimeo))
6274
+ {
6275
+ html = html.replace(this.opts.linkify.regexps.vimeo, iframeStart + '//player.vimeo.com/video/$2' + iframeEnd);
6276
+ }
6277
+
6278
+ return html;
6279
+ },
6280
+ convertImages: function(html)
6281
+ {
6282
+ var matches = html.match(this.opts.linkify.regexps.image);
6283
+
6284
+ if (matches)
6285
+ {
6286
+ html = html.replace(html, '<img src="' + matches + '" class="redactor-linkify-object" />');
6287
+
6288
+ if (this.opts.linebreaks)
6289
+ {
6290
+ if (!this.utils.isEmpty(this.code.get()))
6291
+ {
6292
+ html = '<br>' + html;
6293
+ }
6294
+ }
6295
+
6296
+ html += '<br>';
6297
+ }
6298
+
6299
+ return html;
6300
+ },
6301
+ convertLinks: function(html)
6302
+ {
6303
+ var matches = html.match(this.opts.linkify.regexps.url);
6304
+
6305
+ if (matches)
6306
+ {
6307
+ matches = $.grep(matches, function(v, k) { return $.inArray(v, matches) === k; });
6308
+
6309
+ var length = matches.length;
6310
+
6311
+ for (var i = 0; i < length; i++)
6312
+ {
6313
+ var href = matches[i],
6314
+ text = href,
6315
+ linkProtocol = this.opts.linkProtocol + '://';
6316
+
6317
+ if (href.match(/(https?|ftp):\/\//i) !== null)
6318
+ {
6319
+ linkProtocol = "";
6320
+ }
6321
+
6322
+ if (text.length > this.opts.linkSize)
6323
+ {
6324
+ text = text.substring(0, this.opts.linkSize) + '...';
6325
+ }
6326
+
6327
+ if (text.search('%') === -1)
6328
+ {
6329
+ text = decodeURIComponent(text);
6330
+ }
6331
+
6332
+ var regexB = "\\b";
6333
+
6334
+ if ($.inArray(href.slice(-1), ["/", "&", "="]) != -1)
6335
+ {
6336
+ regexB = "";
6337
+ }
6338
+
6339
+ // escaping url
6340
+ var regexp = new RegExp('(' + href.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&") + regexB + ')', 'g');
6341
+
6342
+ html = html.replace(regexp, '<a href="' + linkProtocol + $.trim(href) + '" class="redactor-linkify-object">' + $.trim(text) + '</a>');
6343
+ }
6344
+ }
6345
+
6346
+ return html;
6347
+ }
6348
+ };
6349
+ },
5672
6350
  list: function()
5673
6351
  {
5674
6352
  return {
5675
6353
  toggle: function(cmd)
5676
6354
  {
5677
6355
  this.placeholder.remove();
5678
- if (!this.utils.browser('msie')) this.$editor.focus();
6356
+
6357
+ if (!this.utils.browser('msie') && !this.opts.linebreaks)
6358
+ {
6359
+ this.$editor.focus();
6360
+ }
5679
6361
 
5680
6362
  this.buffer.set();
5681
6363
  this.selection.save();
@@ -5711,7 +6393,7 @@
5711
6393
  {
5712
6394
  if (remove)
5713
6395
  {
5714
- this.list.remove(cmd);
6396
+ this.list.remove(cmd, $list);
5715
6397
  }
5716
6398
  else
5717
6399
  {
@@ -5721,10 +6403,10 @@
5721
6403
 
5722
6404
  this.selection.restore();
5723
6405
  this.code.sync();
6406
+
5724
6407
  },
5725
6408
  insert: function(cmd)
5726
6409
  {
5727
- var parent = this.selection.getParent();
5728
6410
  var current = this.selection.getCurrent();
5729
6411
  var $td = $(current).closest('td, th', this.$editor[0]);
5730
6412
 
@@ -5737,30 +6419,25 @@
5737
6419
  document.execCommand('insert' + cmd);
5738
6420
  }
5739
6421
 
5740
- var $list = $(this.selection.getParent()).closest('ol, ul', this.$editor[0]);
5741
-
6422
+ var parent = this.selection.getParent();
6423
+ var $list = $(parent).closest('ol, ul', this.$editor[0]);
5742
6424
  if ($td.length !== 0)
5743
6425
  {
5744
- var prev = $td.prev();
5745
- var html = $td.html();
5746
- $td.html('');
5747
- if (prev && prev.length === 1 && (prev[0].tagName === 'TD' || prev[0].tagName === 'TH'))
5748
- {
5749
- $(prev).after($td);
5750
- }
5751
- else
5752
- {
5753
- $(parent).prepend($td);
5754
- }
5755
-
5756
- $td.html(html);
6426
+ var newTd = $td.clone();
6427
+ $td.after(newTd).remove('');
5757
6428
  }
5758
6429
 
6430
+
5759
6431
  if (this.utils.isEmpty($list.find('li').text()))
5760
6432
  {
5761
6433
  var $children = $list.children('li');
5762
6434
  $children.find('br').remove();
5763
6435
  $children.append(this.selection.getMarkerAsHtml());
6436
+
6437
+ if (this.opts.linebreaks && this.utils.browser('mozilla') && $children.size() == 2 && this.utils.isEmpty($children.eq(1).text()))
6438
+ {
6439
+ $children.eq(1).remove();
6440
+ }
5764
6441
  }
5765
6442
 
5766
6443
  if ($list.length)
@@ -5778,6 +6455,7 @@
5778
6455
  this.$editor.focus();
5779
6456
  }
5780
6457
 
6458
+
5781
6459
  this.clean.clearUnverified();
5782
6460
  },
5783
6461
  insertInIe: function(cmd)
@@ -5816,12 +6494,13 @@
5816
6494
  $(wrapper).replaceWith(tmpList);
5817
6495
  }
5818
6496
  },
5819
- remove: function(cmd)
6497
+ remove: function(cmd, $list)
5820
6498
  {
6499
+ if ($.inArray('ul', this.selection.getBlocks())) cmd = 'unorderedlist';
6500
+
5821
6501
  document.execCommand('insert' + cmd);
5822
6502
 
5823
6503
  var $current = $(this.selection.getCurrent());
5824
-
5825
6504
  this.indent.fixEmptyIndent();
5826
6505
 
5827
6506
  if (!this.opts.linebreaks && $current.closest('li, th, td', this.$editor[0]).length === 0)
@@ -5839,6 +6518,7 @@
5839
6518
 
5840
6519
  this.clean.clearUnverified();
5841
6520
 
6521
+
5842
6522
  }
5843
6523
  };
5844
6524
  },
@@ -5854,10 +6534,10 @@
5854
6534
  + '<label>' + this.lang.get('title') + '</label>'
5855
6535
  + '<input type="text" id="redactor-image-title" />'
5856
6536
  + '<label class="redactor-image-link-option">' + this.lang.get('link') + '</label>'
5857
- + '<input type="text" id="redactor-image-link" class="redactor-image-link-option" />'
5858
- + '<label class="redactor-image-link-option"><input type="checkbox" id="redactor-image-link-blank"> ' + this.lang.get('link_new_tab') + '</label>'
6537
+ + '<input type="text" id="redactor-image-link" class="redactor-image-link-option" aria-label="' + this.lang.get('link') + '" />'
6538
+ + '<label class="redactor-image-link-option"><input type="checkbox" id="redactor-image-link-blank" aria-label="' + this.lang.get('link_new_tab') + '"> ' + this.lang.get('link_new_tab') + '</label>'
5859
6539
  + '<label class="redactor-image-position-option">' + this.lang.get('image_position') + '</label>'
5860
- + '<select class="redactor-image-position-option" id="redactor-image-align">'
6540
+ + '<select class="redactor-image-position-option" id="redactor-image-align" aria-label="' + this.lang.get('image_position') + '">'
5861
6541
  + '<option value="none">' + this.lang.get('none') + '</option>'
5862
6542
  + '<option value="left">' + this.lang.get('left') + '</option>'
5863
6543
  + '<option value="center">' + this.lang.get('center') + '</option>'
@@ -5874,7 +6554,7 @@
5874
6554
  + '<section id="redactor-modal-file-insert">'
5875
6555
  + '<div id="redactor-modal-file-upload-box">'
5876
6556
  + '<label>' + this.lang.get('filename') + '</label>'
5877
- + '<input type="text" id="redactor-filename" /><br><br>'
6557
+ + '<input type="text" id="redactor-filename" aria-label="' + this.lang.get('filename') + '" /><br><br>'
5878
6558
  + '<div id="redactor-modal-file-upload"></div>'
5879
6559
  + '</div>'
5880
6560
  + '</section>',
@@ -5882,9 +6562,9 @@
5882
6562
  link: String()
5883
6563
  + '<section id="redactor-modal-link-insert">'
5884
6564
  + '<label>URL</label>'
5885
- + '<input type="url" id="redactor-link-url" />'
6565
+ + '<input type="url" id="redactor-link-url" aria-label="URL" />'
5886
6566
  + '<label>' + this.lang.get('text') + '</label>'
5887
- + '<input type="text" id="redactor-link-url-text" />'
6567
+ + '<input type="text" id="redactor-link-url-text" aria-label="' + this.lang.get('text') + '" />'
5888
6568
  + '<label><input type="checkbox" id="redactor-link-blank"> ' + this.lang.get('link_new_tab') + '</label>'
5889
6569
  + '</section>'
5890
6570
  };
@@ -5957,15 +6637,7 @@
5957
6637
  },
5958
6638
  show: function()
5959
6639
  {
5960
- // ios keyboard hide
5961
- if (this.utils.isMobile() && !this.utils.browser('msie'))
5962
- {
5963
- document.activeElement.blur();
5964
- }
5965
-
5966
- $(document.body).removeClass('body-redactor-hidden');
5967
- this.modal.bodyOveflow = $(document.body).css('overflow');
5968
- $(document.body).css('overflow', 'hidden');
6640
+ this.utils.disableBodyScroll();
5969
6641
 
5970
6642
  if (this.utils.isMobile())
5971
6643
  {
@@ -5976,9 +6648,17 @@
5976
6648
  this.modal.showOnDesktop();
5977
6649
  }
5978
6650
 
6651
+ if (this.opts.highContrast)
6652
+ {
6653
+ this.$modalBox.addClass("redactor-modal-contrast");
6654
+ }
6655
+
5979
6656
  this.$modalOverlay.show();
5980
6657
  this.$modalBox.show();
5981
6658
 
6659
+ this.$modal.attr('tabindex', '-1');
6660
+ this.$modal.focus();
6661
+
5982
6662
  this.modal.setButtonsWidth();
5983
6663
 
5984
6664
  this.utils.saveScroll();
@@ -6104,10 +6784,10 @@
6104
6784
  {
6105
6785
  this.modal.buildOverlay();
6106
6786
 
6107
- this.$modalBox = $('<div id="redactor-modal-box" />').hide();
6108
- this.$modal = $('<div id="redactor-modal" />');
6109
- this.$modalHeader = $('<header />');
6110
- this.$modalClose = $('<span id="redactor-modal-close" />').html('&times;');
6787
+ this.$modalBox = $('<div id="redactor-modal-box"/>').hide();
6788
+ this.$modal = $('<div id="redactor-modal" role="dialog" aria-labelledby="redactor-modal-header" />');
6789
+ this.$modalHeader = $('<header id="redactor-modal-header"/>');
6790
+ this.$modalClose = $('<button type="button" id="redactor-modal-close" tabindex="1" aria-label="Close" />').html('&times;');
6111
6791
  this.$modalBody = $('<div id="redactor-modal-body" />');
6112
6792
  this.$modalFooter = $('<footer />');
6113
6793
 
@@ -6159,6 +6839,7 @@
6159
6839
  if (!this.$modalBox) return;
6160
6840
 
6161
6841
  this.modal.disableEvents();
6842
+ this.utils.enableBodyScroll();
6162
6843
 
6163
6844
  this.$modalOverlay.remove();
6164
6845
 
@@ -6183,9 +6864,116 @@
6183
6864
  return {
6184
6865
  load: function()
6185
6866
  {
6867
+ if (typeof this.opts.destroyed != "undefined") return;
6868
+
6869
+ if (this.utils.browser('msie'))
6870
+ {
6871
+ var self = this;
6872
+ this.$editor.find('pre, code').on('mouseover',function()
6873
+ {
6874
+ self.$editor.attr('contenteditable', false);
6875
+ $(this).attr('contenteditable', true);
6876
+
6877
+ }).on('mouseout',function()
6878
+ {
6879
+ self.$editor.attr('contenteditable', true);
6880
+ $(this).removeAttr('contenteditable');
6881
+
6882
+ });
6883
+ }
6884
+
6186
6885
  this.observe.images();
6187
6886
  this.observe.links();
6188
6887
  },
6888
+ toolbar: function(e, btnName)
6889
+ {
6890
+ this.observe.buttons(e, btnName);
6891
+ this.observe.dropdowns();
6892
+ },
6893
+ isCurrent: function($el, $current)
6894
+ {
6895
+ if (typeof $current == 'undefined')
6896
+ {
6897
+ var $current = $(this.selection.getCurrent());
6898
+ }
6899
+
6900
+ return $current.is($el) || $current.parents($el).length > 0;
6901
+ },
6902
+ dropdowns: function()
6903
+ {
6904
+ var $current = $(this.selection.getCurrent());
6905
+
6906
+ $.each(this.opts.observe.dropdowns, $.proxy(function(key, value)
6907
+ {
6908
+ var observe = value.observe,
6909
+ element = observe.element,
6910
+ $item = value.item,
6911
+ inValues = typeof observe['in'] != 'undefined' ? observe['in'] : false,
6912
+ outValues = typeof observe['out'] != 'undefined' ? observe['out'] : false;
6913
+
6914
+ if ($current.closest(element).size() > 0)
6915
+ {
6916
+ this.observe.setDropdownProperties($item, inValues, outValues);
6917
+ }
6918
+ else
6919
+ {
6920
+ this.observe.setDropdownProperties($item, outValues, inValues);
6921
+ }
6922
+ }, this));
6923
+ },
6924
+ setDropdownProperties: function($item, addProperties, deleteProperties)
6925
+ {
6926
+ if (deleteProperties && typeof deleteProperties['attr'] != 'undefined')
6927
+ {
6928
+ this.observe.setDropdownAttr($item, deleteProperties.attr, true);
6929
+ }
6930
+
6931
+ if (typeof addProperties['attr'] != 'undefined')
6932
+ {
6933
+ this.observe.setDropdownAttr($item, addProperties.attr);
6934
+ }
6935
+
6936
+ if (typeof addProperties['title'] != 'undefined')
6937
+ {
6938
+ $item.text(addProperties['title']);
6939
+ }
6940
+ },
6941
+ setDropdownAttr: function($item, properties, isDelete)
6942
+ {
6943
+ $.each(properties, function(key, value)
6944
+ {
6945
+ if (key == 'class')
6946
+ {
6947
+ if (!isDelete)
6948
+ {
6949
+ $item.addClass(value);
6950
+ }
6951
+ else
6952
+ {
6953
+ $item.removeClass(value);
6954
+ }
6955
+ }
6956
+ else
6957
+ {
6958
+ if (!isDelete)
6959
+ {
6960
+ $item.attr(key, value);
6961
+ }
6962
+ else
6963
+ {
6964
+ $item.removeAttr(key);
6965
+ }
6966
+ }
6967
+ });
6968
+ },
6969
+ addDropdown: function($item, btnName, btnObject)
6970
+ {
6971
+ if (typeof btnObject.observe == "undefined") return;
6972
+
6973
+ btnObject.item = $item;
6974
+
6975
+ this.opts.observe.dropdowns.push(btnObject);
6976
+ },
6189
6977
  buttons: function(e, btnName)
6190
6978
  {
6191
6979
  var current = this.selection.getCurrent();
@@ -6503,7 +7291,9 @@
6503
7291
  $(window).off('scroll.redactor-freeze');
6504
7292
 
6505
7293
  if (this.linkify.isEnabled())
7294
+ {
6506
7295
  this.linkify.format();
7296
+ }
6507
7297
 
6508
7298
  }, this), 1);
6509
7299
  },
@@ -6517,10 +7307,19 @@
6517
7307
  }
6518
7308
  else
6519
7309
  {
6520
- $('body').append(this.$pasteBox);
7310
+ // bootstrap modal
7311
+ var $visibleModals = $('.modal-body:visible');
7312
+ if ($visibleModals.length > 0)
7313
+ {
7314
+ $visibleModals.append(this.$pasteBox);
7315
+ }
7316
+ else
7317
+ {
7318
+ $('body').append(this.$pasteBox);
7319
+ }
6521
7320
  }
6522
7321
 
6523
- this.$pasteBox.focus();
7322
+ this.$pasteBox.get(0).focus();
6524
7323
  },
6525
7324
  insert: function(html)
6526
7325
  {
@@ -6551,12 +7350,13 @@
6551
7350
  var spans = this.$editor.find('span');
6552
7351
  $.each(spans, function(i,s)
6553
7352
  {
6554
- var html = s.innerHTML.replace(/[\u200B-\u200D\uFEFF]/, '');
7353
+ var html = s.innerHTML.replace(/\u200B/, '');
6555
7354
  if (html === '' && s.attributes.length === 0) $(s).remove();
6556
7355
 
6557
7356
  });
6558
7357
 
6559
7358
  }, this), 10);
7359
+
6560
7360
  }
6561
7361
  };
6562
7362
  },
@@ -6570,14 +7370,16 @@
6570
7370
  this.$editor.attr('placeholder', this.$element.attr('placeholder'));
6571
7371
 
6572
7372
  this.placeholder.toggle();
6573
- this.$editor.on('keyup.redactor-placeholder', $.proxy(this.placeholder.toggle, this));
6574
-
7373
+ this.$editor.on('keydown.redactor-placeholder', $.proxy(this.placeholder.toggle, this));
6575
7374
  },
6576
7375
  toggle: function()
6577
7376
  {
6578
- var func = 'removeClass';
6579
- if (this.utils.isEmpty(this.$editor.html(), false)) func = 'addClass';
6580
- this.$editor[func]('redactor-placeholder');
7377
+ setTimeout($.proxy(function()
7378
+ {
7379
+ var func = this.utils.isEmpty(this.$editor.html(), false) ? 'addClass' : 'removeClass';
7380
+ this.$editor[func]('redactor-placeholder');
7381
+
7382
+ }, this), 5);
6581
7383
  },
6582
7384
  remove: function()
6583
7385
  {
@@ -6641,6 +7443,7 @@
6641
7443
  getCurrent: function()
6642
7444
  {
6643
7445
  var el = false;
7446
+
6644
7447
  this.selection.get();
6645
7448
 
6646
7449
  if (this.sel && this.sel.rangeCount > 0)
@@ -6660,6 +7463,14 @@
6660
7463
 
6661
7464
  return false;
6662
7465
  },
7466
+ getPrev: function()
7467
+ {
7468
+ return window.getSelection().anchorNode.previousSibling;
7469
+ },
7470
+ getNext: function()
7471
+ {
7472
+ return window.getSelection().anchorNode.nextSibling;
7473
+ },
6663
7474
  getBlock: function(node)
6664
7475
  {
6665
7476
  node = node || this.selection.getCurrent();
@@ -6742,11 +7553,11 @@
6742
7553
 
6743
7554
  var blocks = [];
6744
7555
  nodes = (typeof nodes == 'undefined') ? this.selection.getNodes() : nodes;
7556
+
6745
7557
  $.each(nodes, $.proxy(function(i,node)
6746
7558
  {
6747
7559
  if (this.utils.isBlock(node))
6748
7560
  {
6749
- this.selection.lastBlock = node;
6750
7561
  blocks.push(node);
6751
7562
  }
6752
7563
 
@@ -6764,23 +7575,34 @@
6764
7575
 
6765
7576
  var startNode = this.selection.getNodesMarker(1);
6766
7577
  var endNode = this.selection.getNodesMarker(2);
6767
- var range = this.range.cloneRange();
6768
7578
 
6769
7579
  if (this.range.collapsed === false)
6770
7580
  {
6771
- var startContainer = range.startContainer;
6772
- var startOffset = range.startOffset;
6773
-
6774
- // end marker
6775
- this.selection.setNodesMarker(range, endNode, false);
6776
-
6777
- // start marker
6778
- range.setStart(startContainer, startOffset);
6779
- this.selection.setNodesMarker(range, startNode, true);
7581
+ if (window.getSelection) {
7582
+ var sel = window.getSelection();
7583
+ if (sel.rangeCount > 0) {
7584
+
7585
+ var range = sel.getRangeAt(0);
7586
+ var startPointNode = range.startContainer, startOffset = range.startOffset;
7587
+
7588
+ var boundaryRange = range.cloneRange();
7589
+ boundaryRange.collapse(false);
7590
+ boundaryRange.insertNode(endNode);
7591
+ boundaryRange.setStart(startPointNode, startOffset);
7592
+ boundaryRange.collapse(true);
7593
+ boundaryRange.insertNode(startNode);
7594
+
7595
+ // Reselect the original text
7596
+ range.setStartAfter(startNode);
7597
+ range.setEndBefore(endNode);
7598
+ sel.removeAllRanges();
7599
+ sel.addRange(range);
7600
+ }
7601
+ }
6780
7602
  }
6781
7603
  else
6782
7604
  {
6783
- this.selection.setNodesMarker(range, startNode, true);
7605
+ this.selection.setNodesMarker(this.range, startNode, true);
6784
7606
  endNode = startNode;
6785
7607
  }
6786
7608
 
@@ -6838,6 +7660,8 @@
6838
7660
  },
6839
7661
  setNodesMarker: function(range, node, type)
6840
7662
  {
7663
+ var range = range.cloneRange();
7664
+
6841
7665
  try {
6842
7666
  range.collapse(type);
6843
7667
  range.insertNode(node);
@@ -6867,7 +7691,17 @@
6867
7691
  },
6868
7692
  selectElement: function(node)
6869
7693
  {
6870
- this.caret.set(node, 0, node, 1);
7694
+ if (this.utils.browser('mozilla'))
7695
+ {
7696
+ node = node[0] || node;
7697
+
7698
+ var range = document.createRange();
7699
+ range.selectNodeContents(node);
7700
+ }
7701
+ else
7702
+ {
7703
+ this.caret.set(node, 0, node, 1);
7704
+ }
6871
7705
  },
6872
7706
  selectAll: function()
6873
7707
  {
@@ -6891,11 +7725,14 @@
6891
7725
  var node1 = this.selection.getMarker(1);
6892
7726
 
6893
7727
  this.selection.setMarker(this.range, node1, true);
6894
-
6895
7728
  if (this.range.collapsed === false)
6896
7729
  {
6897
7730
  var node2 = this.selection.getMarker(2);
6898
7731
  this.selection.setMarker(this.range, node2, false);
7732
+ if (this.utils.browser('chrome'))
7733
+ {
7734
+ this.caret.set(node1, 0, node2, 0);
7735
+ }
6899
7736
  }
6900
7737
 
6901
7738
  this.savedSel = this.$editor.html();
@@ -6917,17 +7754,24 @@
6917
7754
  try {
6918
7755
  range.collapse(type);
6919
7756
  range.insertNode(node);
7757
+
6920
7758
  }
6921
7759
  catch (e)
6922
7760
  {
6923
7761
  this.focus.setStart();
6924
7762
  }
7763
+
6925
7764
  },
6926
7765
  restore: function()
6927
7766
  {
6928
7767
  var node1 = this.$editor.find('span#selection-marker-1');
6929
7768
  var node2 = this.$editor.find('span#selection-marker-2');
6930
7769
 
7770
+ if (this.utils.browser('mozilla'))
7771
+ {
7772
+ this.$editor.focus();
7773
+ }
7774
+
6931
7775
  if (node1.length !== 0 && node2.length !== 0)
6932
7776
  {
6933
7777
  this.caret.set(node1, 0, node2, 0);
@@ -6949,7 +7793,7 @@
6949
7793
  {
6950
7794
  this.$editor.find('span.redactor-selection-marker').each(function(i,s)
6951
7795
  {
6952
- var text = $(s).text().replace(/[\u200B-\u200D\uFEFF]/g, '');
7796
+ var text = $(s).text().replace(/\u200B/g, '');
6953
7797
  if (text === '') $(s).remove();
6954
7798
  else $(s).replaceWith(function() { return $(this).contents(); });
6955
7799
  });
@@ -6979,6 +7823,19 @@
6979
7823
 
6980
7824
  return this.clean.onSync(html);
6981
7825
  },
7826
+ replaceSelection: function(html)
7827
+ {
7828
+ this.selection.get();
7829
+ this.range.deleteContents();
7830
+ var div = document.createElement("div");
7831
+ div.innerHTML = html;
7832
+ var frag = document.createDocumentFragment(), child;
7833
+ while ((child = div.firstChild)) {
7834
+ frag.appendChild(child);
7835
+ }
7836
+
7837
+ this.range.insertNode(frag);
7838
+ },
6982
7839
  replaceWithHtml: function(html)
6983
7840
  {
6984
7841
  html = this.selection.getMarkerAsHtml(1) + html + this.selection.getMarkerAsHtml(2);
@@ -6987,17 +7844,7 @@
6987
7844
 
6988
7845
  if (window.getSelection && window.getSelection().getRangeAt)
6989
7846
  {
6990
- this.range.deleteContents();
6991
- var div = document.createElement("div");
6992
- div.innerHTML = html;
6993
-
6994
- var frag = document.createDocumentFragment(), child;
6995
- while ((child = div.firstChild))
6996
- {
6997
- frag.appendChild(child);
6998
- }
6999
-
7000
- this.range.insertNode(frag);
7847
+ this.selection.replaceSelection(html);
7001
7848
  }
7002
7849
  else if (document.selection && document.selection.createRange)
7003
7850
  {
@@ -7316,6 +8163,12 @@
7316
8163
  return {
7317
8164
  setupAllowed: function()
7318
8165
  {
8166
+ var index = $.inArray('span', this.opts.removeEmpty);
8167
+ if (index !== -1)
8168
+ {
8169
+ this.opts.removeEmpty.splice(index, 1);
8170
+ }
8171
+
7319
8172
  if (this.opts.allowedTags) this.opts.deniedTags = false;
7320
8173
  if (this.opts.allowedAttr) this.opts.removeAttr = false;
7321
8174
 
@@ -7453,6 +8306,8 @@
7453
8306
  {
7454
8307
  this.tidy.$div.find(this.tidy.settings.deniedTags.join(',')).each(function(i, s)
7455
8308
  {
8309
+ if ($(s).hasClass('redactor-script-tag') || $(s).hasClass('redactor-selection-marker')) return;
8310
+
7456
8311
  if (s.innerHTML === '') $(s).remove();
7457
8312
  else $(s).contents().unwrap();
7458
8313
  });
@@ -7566,7 +8421,7 @@
7566
8421
  {
7567
8422
  var $el = $(this);
7568
8423
  var text = $el.text();
7569
- text = text.replace(/[\u200B-\u200D\uFEFF]/g, '');
8424
+ text = text.replace(/\u200B/g, '');
7570
8425
  text = text.replace(/&nbsp;/gi, '');
7571
8426
  text = text.replace(/\s/g, '');
7572
8427
 
@@ -7720,12 +8575,30 @@
7720
8575
  link:
7721
8576
  {
7722
8577
  title: this.lang.get('link_insert'),
7723
- func: 'link.show'
8578
+ func: 'link.show',
8579
+ observe: {
8580
+ element: 'a',
8581
+ in: {
8582
+ title: this.lang.get('link_edit'),
8583
+ },
8584
+ out: {
8585
+ title: this.lang.get('link_insert')
8586
+ }
8587
+ }
7724
8588
  },
7725
8589
  unlink:
7726
8590
  {
7727
8591
  title: this.lang.get('unlink'),
7728
- func: 'link.unlink'
8592
+ func: 'link.unlink',
8593
+ observe: {
8594
+ element: 'a',
8595
+ out: {
8596
+ attr: {
8597
+ 'class': 'redactor-dropdown-link-inactive',
8598
+ 'aria-disabled': true
8599
+ }
8600
+ }
8601
+ }
7729
8602
  }
7730
8603
  }
7731
8604
  },
@@ -7782,13 +8655,13 @@
7782
8655
  // buttons response
7783
8656
  if (this.opts.activeButtons)
7784
8657
  {
7785
- this.$editor.on('mouseup.redactor keyup.redactor focus.redactor', $.proxy(this.observe.buttons, this));
8658
+ this.$editor.on('mouseup.redactor keyup.redactor focus.redactor', $.proxy(this.observe.toolbar, this));
7786
8659
  }
7787
8660
 
7788
8661
  },
7789
8662
  createContainer: function()
7790
8663
  {
7791
- return $('<ul>').addClass('redactor-toolbar').attr('id', 'redactor-toolbar-' + this.uuid);
8664
+ return $('<ul>').addClass('redactor-toolbar').attr({'id': 'redactor-toolbar-' + this.uuid, 'role': 'toolbar'});
7792
8665
  },
7793
8666
  setFormattingTags: function()
7794
8667
  {
@@ -7892,7 +8765,7 @@
7892
8765
  boxTop = this.$box.offset().top;
7893
8766
  }
7894
8767
 
7895
- if (scrollTop > boxTop)
8768
+ if ((scrollTop + this.opts.toolbarFixedTopOffset) > boxTop)
7896
8769
  {
7897
8770
  this.toolbar.observeScrollEnable(scrollTop, boxTop);
7898
8771
  }
@@ -8052,6 +8925,12 @@
8052
8925
  {
8053
8926
  this.upload.type = 'file';
8054
8927
  }
8928
+
8929
+ if (this.opts.imageUpload === null && this.opts.fileUpload !== null)
8930
+ {
8931
+ this.upload.type = 'file';
8932
+ }
8933
+
8055
8934
  },
8056
8935
  getHiddenFields: function(obj, fd)
8057
8936
  {
@@ -8182,9 +9061,7 @@
8182
9061
  s3executeOnSignedUrl: function(file, callback)
8183
9062
  {
8184
9063
  var xhr = new XMLHttpRequest();
8185
-
8186
- var mark = '?';
8187
- if (this.opts.s3.search(/\?/) != '-1') mark = '&';
9064
+ var mark = (this.opts.s3.search(/\?/) !== '-1') ? '?' : '&';
8188
9065
 
8189
9066
  xhr.open('GET', this.opts.s3 + mark + 'name=' + file.name + '&type=' + file.type, true);
8190
9067
 
@@ -8274,21 +9151,9 @@
8274
9151
  }
8275
9152
  }, this);
8276
9153
 
8277
- xhr.onerror = function()
8278
- {
8279
- //setProgress(0, 'XHR error.');
8280
- };
9154
+ xhr.onerror = function() {};
8281
9155
 
8282
- xhr.upload.onprogress = function(e)
8283
- {
8284
- /*
8285
- if (e.lengthComputable)
8286
- {
8287
- var percentLoaded = Math.round((e.loaded / e.total) * 100);
8288
- setProgress(percentLoaded, percentLoaded == 100 ? 'Finalizing.' : 'Uploading.');
8289
- }
8290
- */
8291
- };
9156
+ xhr.upload.onprogress = function(e) {};
8292
9157
 
8293
9158
  xhr.setRequestHeader('Content-Type', file.type);
8294
9159
  xhr.setRequestHeader('x-amz-acl', 'public-read');
@@ -8493,6 +9358,12 @@
8493
9358
 
8494
9359
  return (offset == text.length) ? true : false;
8495
9360
  },
9361
+ isStartOfEditor: function()
9362
+ {
9363
+ var offset = this.caret.getOffsetOfElement(this.$editor[0]);
9364
+
9365
+ return (offset === 0) ? true : false;
9366
+ },
8496
9367
  isEndOfEditor: function()
8497
9368
  {
8498
9369
  var block = this.$editor[0];
@@ -8626,135 +9497,48 @@
8626
9497
  if (match[1] == 'opr') return browser == 'webkit';
8627
9498
 
8628
9499
  return browser == match[1];
8629
- }
8630
- };
8631
- },
8632
- linkify: function()
8633
- {
8634
- return {
8635
- isKey: function(key)
8636
- {
8637
- return key == this.keyCode.ENTER || key == this.keyCode.SPACE;
8638
- },
8639
- isEnabled: function()
8640
- {
8641
- return this.opts.convertLinks && (this.opts.convertUrlLinks || this.opts.convertImageLinks || this.opts.convertVideoLinks) && !this.utils.isCurrentOrParent('PRE');
8642
9500
  },
8643
- format: function()
9501
+ strpos: function(haystack, needle, offset)
8644
9502
  {
8645
- var linkify = this.linkify,
8646
- opts = this.opts;
8647
-
8648
- this.$editor
8649
- .find(":not(iframe,img,a,pre)")
8650
- .addBack()
8651
- .contents()
8652
- .filter(function()
8653
- {
8654
- return this.nodeType === 3 && $.trim(this.nodeValue) != "" && !$(this).parent().is("pre") && (this.nodeValue.match(opts.linkify.regexps.youtube) || this.nodeValue.match(opts.linkify.regexps.vimeo) || this.nodeValue.match(opts.linkify.regexps.image) || this.nodeValue.match(opts.linkify.regexps.url));
8655
- })
8656
- .each(function()
8657
- {
8658
- var text = $(this).text(),
8659
- html = text;
8660
-
8661
- if (opts.convertVideoLinks && (html.match(opts.linkify.regexps.youtube) || html.match(opts.linkify.regexps.vimeo)) )
8662
- {
8663
- html = linkify.convertVideoLinks(html);
8664
- }
8665
- else if (opts.convertImageLinks && html.match(opts.linkify.regexps.image))
8666
- {
8667
- html = linkify.convertImages(html);
8668
- }
8669
- else if (opts.convertUrlLinks)
8670
- {
8671
- html = linkify.convertLinks(html);
8672
- }
8673
-
8674
- $(this).before(text.replace(text, html))
8675
- .remove();
8676
- });
8677
-
8678
- this.linkify.after();
9503
+ var i = haystack.indexOf(needle, offset);
9504
+ return i >= 0 ? i : false;
8679
9505
  },
8680
- convertVideoLinks: function(html)
9506
+ disableBodyScroll: function()
8681
9507
  {
8682
- var iframeStart = '<iframe width="500" height="281" src="',
8683
- iframeEnd = '" frameborder="0" allowfullscreen></iframe>';
8684
9508
 
8685
- if (html.match(this.opts.linkify.regexps.youtube))
9509
+ var $body = $('html');
9510
+ var windowWidth = window.innerWidth;
9511
+ if (!windowWidth)
8686
9512
  {
8687
- html = html.replace(this.opts.linkify.regexps.youtube, iframeStart + '//www.youtube.com/embed/$1' + iframeEnd);
9513
+ var documentElementRect = document.documentElement.getBoundingClientRect();
9514
+ windowWidth = documentElementRect.right - Math.abs(documentElementRect.left);
8688
9515
  }
8689
9516
 
8690
- if (html.match(this.opts.linkify.regexps.vimeo))
8691
- {
8692
- html = html.replace(this.opts.linkify.regexps.vimeo, iframeStart + '//player.vimeo.com/video/$2' + iframeEnd);
8693
- }
9517
+ var isOverflowing = document.body.clientWidth < windowWidth;
9518
+ var scrollbarWidth = this.utils.measureScrollbar();
8694
9519
 
8695
- return html;
8696
- },
8697
- convertImages: function(html)
8698
- {
8699
- var matches = html.match(this.opts.linkify.regexps.image);
9520
+ $body.css('overflow', 'hidden');
9521
+ if (isOverflowing) $body.css('padding-right', scrollbarWidth);
8700
9522
 
8701
- if (matches)
8702
- {
8703
- html = html.replace(html, '<img src="' + matches + '" />');
8704
- }
8705
9523
 
8706
- return html;
8707
9524
  },
8708
- convertLinks: function(html)
9525
+ measureScrollbar: function()
8709
9526
  {
8710
- var matches = html.match(this.opts.linkify.regexps.url);
8711
-
8712
- if (matches)
8713
- {
8714
- matches = $.grep(matches, function(v, k) { return $.inArray(v, matches) === k; });
8715
-
8716
- var length = matches.length;
8717
-
8718
- for (var i = 0; i < length; i++)
8719
- {
8720
- var href = matches[i],
8721
- text = href,
8722
- linkProtocol = this.opts.linkProtocol + '://';
8723
-
8724
- if (href.match(/(https?|ftp):\/\//i) !== null)
8725
- {
8726
- linkProtocol = "";
8727
- }
8728
-
8729
- if (text.length > this.opts.linkSize)
8730
- {
8731
- text = text.substring(0, this.opts.linkSize) + '...';
8732
- }
8733
-
8734
- text = decodeURIComponent(text);
8735
-
8736
- var regexB = "\\b";
8737
-
8738
- if ($.inArray(href.slice(-1), ["/", "&", "="]) != -1)
8739
- {
8740
- regexB = "";
8741
- }
8742
-
8743
- // escaping url
8744
- var regexp = new RegExp('(' + href.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&") + regexB + ')', 'g');
8745
-
8746
- html = html.replace(regexp, '<a href="' + linkProtocol + $.trim(href) + '">' + $.trim(text) + '</a>');
8747
- }
8748
- }
9527
+ var $body = $('body');
9528
+ var scrollDiv = document.createElement('div');
9529
+ scrollDiv.className = 'redactor-scrollbar-measure';
8749
9530
 
8750
- return html;
9531
+ $body.append(scrollDiv);
9532
+ var scrollbarWidth = scrollDiv.offsetWidth - scrollDiv.clientWidth;
9533
+ $body[0].removeChild(scrollDiv);
9534
+ return scrollbarWidth;
8751
9535
  },
8752
- after: function()
9536
+ enableBodyScroll: function()
8753
9537
  {
8754
- this.observe.load();
8755
- this.code.sync();
9538
+ $('html').css({ 'overflow': '', 'padding-right': '' });
9539
+ $('body').remove('redactor-scrollbar-measure');
8756
9540
  }
8757
- }
9541
+ };
8758
9542
  }
8759
9543
  };
8760
9544
 
@@ -8765,4 +9549,5 @@
8765
9549
 
8766
9550
  // constructor
8767
9551
  Redactor.prototype.init.prototype = Redactor.prototype;
9552
+
8768
9553
  })(jQuery);