godmin-redactor 1.0.1 → 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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);