summernote-rails 0.8.3.0 → 0.8.8.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,12 +1,12 @@
1
1
  /**
2
- * Super simple wysiwyg editor v0.8.3
2
+ * Super simple wysiwyg editor v0.8.8
3
3
  * http://summernote.org/
4
4
  *
5
5
  * summernote.js
6
- * Copyright 2013-2016 Alan Hong. and other contributors
6
+ * Copyright 2013- Alan Hong. and other contributors
7
7
  * summernote may be freely distributed under the MIT license./
8
8
  *
9
- * Date: 2017-04-01T13:43Z
9
+ * Date: 2017-09-09T11:03Z
10
10
  */
11
11
  (function (factory) {
12
12
  /* global define */
@@ -23,6 +23,108 @@
23
23
  }(function ($) {
24
24
  'use strict';
25
25
 
26
+ var isSupportAmd = typeof define === 'function' && define.amd;
27
+
28
+ /**
29
+ * returns whether font is installed or not.
30
+ *
31
+ * @param {String} fontName
32
+ * @return {Boolean}
33
+ */
34
+ var isFontInstalled = function (fontName) {
35
+ var testFontName = fontName === 'Comic Sans MS' ? 'Courier New' : 'Comic Sans MS';
36
+ var $tester = $('<div>').css({
37
+ position: 'absolute',
38
+ left: '-9999px',
39
+ top: '-9999px',
40
+ fontSize: '200px'
41
+ }).text('mmmmmmmmmwwwwwww').appendTo(document.body);
42
+
43
+ var originalWidth = $tester.css('fontFamily', testFontName).width();
44
+ var width = $tester.css('fontFamily', fontName + ',' + testFontName).width();
45
+
46
+ $tester.remove();
47
+
48
+ return originalWidth !== width;
49
+ };
50
+
51
+ var userAgent = navigator.userAgent;
52
+ var isMSIE = /MSIE|Trident/i.test(userAgent);
53
+ var browserVersion;
54
+ if (isMSIE) {
55
+ var matches = /MSIE (\d+[.]\d+)/.exec(userAgent);
56
+ if (matches) {
57
+ browserVersion = parseFloat(matches[1]);
58
+ }
59
+ matches = /Trident\/.*rv:([0-9]{1,}[\.0-9]{0,})/.exec(userAgent);
60
+ if (matches) {
61
+ browserVersion = parseFloat(matches[1]);
62
+ }
63
+ }
64
+
65
+ var isEdge = /Edge\/\d+/.test(userAgent);
66
+
67
+ var hasCodeMirror = !!window.CodeMirror;
68
+ if (!hasCodeMirror && isSupportAmd) {
69
+ // Webpack
70
+ if (typeof __webpack_require__ === 'function') { // jshint ignore:line
71
+ try {
72
+ // If CodeMirror can't be resolved, `require.resolve` will throw an
73
+ // exception and `hasCodeMirror` won't be set to `true`.
74
+ require.resolve('codemirror');
75
+ hasCodeMirror = true;
76
+ } catch (e) {
77
+ // do nothing
78
+ }
79
+ } else if (typeof require !== 'undefined') {
80
+ // Browserify
81
+ if (typeof require.resolve !== 'undefined') {
82
+ try {
83
+ // If CodeMirror can't be resolved, `require.resolve` will throw an
84
+ // exception and `hasCodeMirror` won't be set to `true`.
85
+ require.resolve('codemirror');
86
+ hasCodeMirror = true;
87
+ } catch (e) {
88
+ // do nothing
89
+ }
90
+ // Almond/Require
91
+ } else if (typeof require.specified !== 'undefined') {
92
+ hasCodeMirror = require.specified('codemirror');
93
+ }
94
+ }
95
+ }
96
+
97
+ var isSupportTouch =
98
+ (('ontouchstart' in window) ||
99
+ (navigator.MaxTouchPoints > 0) ||
100
+ (navigator.msMaxTouchPoints > 0));
101
+
102
+ /**
103
+ * @class core.agent
104
+ *
105
+ * Object which check platform and agent
106
+ *
107
+ * @singleton
108
+ * @alternateClassName agent
109
+ */
110
+ var agent = {
111
+ isMac: navigator.appVersion.indexOf('Mac') > -1,
112
+ isMSIE: isMSIE,
113
+ isEdge: isEdge,
114
+ isFF: !isEdge && /firefox/i.test(userAgent),
115
+ isPhantom: /PhantomJS/i.test(userAgent),
116
+ isWebkit: !isEdge && /webkit/i.test(userAgent),
117
+ isChrome: !isEdge && /chrome/i.test(userAgent),
118
+ isSafari: !isEdge && /safari/i.test(userAgent),
119
+ browserVersion: browserVersion,
120
+ jqueryVersion: parseFloat($.fn.jquery),
121
+ isSupportAmd: isSupportAmd,
122
+ isSupportTouch: isSupportTouch,
123
+ hasCodeMirror: hasCodeMirror,
124
+ isFontInstalled: isFontInstalled,
125
+ isW3CRangeSupport: !!document.createRange
126
+ };
127
+
26
128
  /**
27
129
  * @class core.func
28
130
  *
@@ -382,88 +484,6 @@
382
484
  clusterBy: clusterBy, compact: compact, unique: unique };
383
485
  })();
384
486
 
385
- var isSupportAmd = typeof define === 'function' && define.amd;
386
-
387
- /**
388
- * returns whether font is installed or not.
389
- *
390
- * @param {String} fontName
391
- * @return {Boolean}
392
- */
393
- var isFontInstalled = function (fontName) {
394
- var testFontName = fontName === 'Comic Sans MS' ? 'Courier New' : 'Comic Sans MS';
395
- var $tester = $('<div>').css({
396
- position: 'absolute',
397
- left: '-9999px',
398
- top: '-9999px',
399
- fontSize: '200px'
400
- }).text('mmmmmmmmmwwwwwww').appendTo(document.body);
401
-
402
- var originalWidth = $tester.css('fontFamily', testFontName).width();
403
- var width = $tester.css('fontFamily', fontName + ',' + testFontName).width();
404
-
405
- $tester.remove();
406
-
407
- return originalWidth !== width;
408
- };
409
-
410
- var userAgent = navigator.userAgent;
411
- var isMSIE = /MSIE|Trident/i.test(userAgent);
412
- var browserVersion;
413
- if (isMSIE) {
414
- var matches = /MSIE (\d+[.]\d+)/.exec(userAgent);
415
- if (matches) {
416
- browserVersion = parseFloat(matches[1]);
417
- }
418
- matches = /Trident\/.*rv:([0-9]{1,}[\.0-9]{0,})/.exec(userAgent);
419
- if (matches) {
420
- browserVersion = parseFloat(matches[1]);
421
- }
422
- }
423
-
424
- var isEdge = /Edge\/\d+/.test(userAgent);
425
-
426
- var hasCodeMirror = !!window.CodeMirror;
427
- if (!hasCodeMirror && isSupportAmd && typeof require !== 'undefined') {
428
- if (typeof require.resolve !== 'undefined') {
429
- try {
430
- // If CodeMirror can't be resolved, `require.resolve` will throw an
431
- // exception and `hasCodeMirror` won't be set to `true`.
432
- require.resolve('codemirror');
433
- hasCodeMirror = true;
434
- } catch (e) {
435
- // Do nothing.
436
- }
437
- } else if (typeof eval('require').specified !== 'undefined') {
438
- hasCodeMirror = eval('require').specified('codemirror');
439
- }
440
- }
441
-
442
- /**
443
- * @class core.agent
444
- *
445
- * Object which check platform and agent
446
- *
447
- * @singleton
448
- * @alternateClassName agent
449
- */
450
- var agent = {
451
- isMac: navigator.appVersion.indexOf('Mac') > -1,
452
- isMSIE: isMSIE,
453
- isEdge: isEdge,
454
- isFF: !isEdge && /firefox/i.test(userAgent),
455
- isPhantom: /PhantomJS/i.test(userAgent),
456
- isWebkit: !isEdge && /webkit/i.test(userAgent),
457
- isChrome: !isEdge && /chrome/i.test(userAgent),
458
- isSafari: !isEdge && /safari/i.test(userAgent),
459
- browserVersion: browserVersion,
460
- jqueryVersion: parseFloat($.fn.jquery),
461
- isSupportAmd: isSupportAmd,
462
- hasCodeMirror: hasCodeMirror,
463
- isFontInstalled: isFontInstalled,
464
- isW3CRangeSupport: !!document.createRange
465
- };
466
-
467
487
 
468
488
  var NBSP_CHAR = String.fromCharCode(160);
469
489
  var ZERO_WIDTH_NBSP_CHAR = '\ufeff';
@@ -545,7 +565,7 @@
545
565
  * @see http://www.w3.org/html/wg/drafts/html/master/syntax.html#void-elements
546
566
  */
547
567
  var isVoid = function (node) {
548
- return node && /^BR|^IMG|^HR|^IFRAME|^BUTTON/.test(node.nodeName.toUpperCase());
568
+ return node && /^BR|^IMG|^HR|^IFRAME|^BUTTON|^INPUT/.test(node.nodeName.toUpperCase());
549
569
  };
550
570
 
551
571
  var isPara = function (node) {
@@ -1455,6 +1475,18 @@
1455
1475
  });
1456
1476
  };
1457
1477
 
1478
+ /**
1479
+ * @method isCustomStyleTag
1480
+ *
1481
+ * assert if a node contains a "note-styletag" class,
1482
+ * which implies that's a custom-made style tag node
1483
+ *
1484
+ * @param {Node} an HTML DOM node
1485
+ */
1486
+ var isCustomStyleTag = function (node) {
1487
+ return node && !dom.isText(node) && list.contains(node.classList, 'note-styletag');
1488
+ };
1489
+
1458
1490
  return {
1459
1491
  /** @property {String} NBSP_CHAR */
1460
1492
  NBSP_CHAR: NBSP_CHAR,
@@ -1542,7 +1574,8 @@
1542
1574
  value: value,
1543
1575
  posFromPlaceholder: posFromPlaceholder,
1544
1576
  attachEvents: attachEvents,
1545
- detachEvents: detachEvents
1577
+ detachEvents: detachEvents,
1578
+ isCustomStyleTag: isCustomStyleTag
1546
1579
  };
1547
1580
  })();
1548
1581
 
@@ -1649,6 +1682,7 @@
1649
1682
  this.enable = function () {
1650
1683
  this.layoutInfo.editable.attr('contenteditable', true);
1651
1684
  this.invoke('toolbar.activate', true);
1685
+ this.triggerEvent('disable', false);
1652
1686
  };
1653
1687
 
1654
1688
  this.disable = function () {
@@ -1658,6 +1692,8 @@
1658
1692
  }
1659
1693
  this.layoutInfo.editable.attr('contenteditable', false);
1660
1694
  this.invoke('toolbar.deactivate', true);
1695
+
1696
+ this.triggerEvent('disable', true);
1661
1697
  };
1662
1698
 
1663
1699
  this.triggerEvent = function () {
@@ -1731,10 +1767,21 @@
1731
1767
  delete this.memos[key];
1732
1768
  };
1733
1769
 
1770
+ /**
1771
+ *Some buttons need to change their visual style immediately once they get pressed
1772
+ */
1773
+ this.createInvokeHandlerAndUpdateState = function (namespace, value) {
1774
+ return function (event) {
1775
+ self.createInvokeHandler(namespace, value)(event);
1776
+ self.invoke('buttons.updateCurrentStyle');
1777
+ };
1778
+ };
1779
+
1734
1780
  this.createInvokeHandler = function (namespace, value) {
1735
1781
  return function (event) {
1736
1782
  event.preventDefault();
1737
- self.invoke(namespace, value || $(event.target).closest('[data-value]').data('value'));
1783
+ var $target = $(event.target);
1784
+ self.invoke(namespace, value || $target.closest('[data-value]').data('value'), $target);
1738
1785
  };
1739
1786
  };
1740
1787
 
@@ -1773,8 +1820,11 @@
1773
1820
  var options = hasInitOptions ? list.head(arguments) : {};
1774
1821
 
1775
1822
  options = $.extend({}, $.summernote.options, options);
1823
+
1824
+ // Update options
1776
1825
  options.langInfo = $.extend(true, {}, $.summernote.lang['en-US'], $.summernote.lang[options.lang]);
1777
1826
  options.icons = $.extend(true, {}, $.summernote.options.icons, options.icons);
1827
+ options.tooltip = options.tooltip === 'auto' ? !agent.isSupportTouch : options.tooltip;
1778
1828
 
1779
1829
  this.each(function (idx, note) {
1780
1830
  var $note = $(note);
@@ -1877,28 +1927,25 @@
1877
1927
  var airEditable = renderer.create('<div class="note-editable" contentEditable="true"/>');
1878
1928
 
1879
1929
  var buttonGroup = renderer.create('<div class="note-btn-group btn-group">');
1880
- var button = renderer.create('<button type="button" class="note-btn btn btn-default btn-sm" tabindex="-1">', function ($node, options) {
1881
- if (options && options.tooltip) {
1882
- $node.attr({
1883
- title: options.tooltip
1884
- }).tooltip({
1885
- container: 'body',
1886
- trigger: 'hover',
1887
- placement: 'bottom'
1888
- });
1889
- }
1890
- });
1891
1930
 
1892
1931
  var dropdown = renderer.create('<div class="dropdown-menu">', function ($node, options) {
1893
1932
  var markup = $.isArray(options.items) ? options.items.map(function (item) {
1894
1933
  var value = (typeof item === 'string') ? item : (item.value || '');
1895
1934
  var content = options.template ? options.template(item) : item;
1896
- return '<li><a href="#" data-value="' + value + '">' + content + '</a></li>';
1935
+ var option = (typeof item === 'object') ? item.option : undefined;
1936
+
1937
+ var dataValue = 'data-value="' + value + '"';
1938
+ var dataOption = (option !== undefined) ? ' data-option="' + option + '"' : '';
1939
+ return '<li><a href="#" ' + (dataValue + dataOption) + '>' + content + '</a></li>';
1897
1940
  }).join('') : options.items;
1898
1941
 
1899
1942
  $node.html(markup);
1900
1943
  });
1901
1944
 
1945
+ var dropdownButtonContents = function (contents, options) {
1946
+ return contents + ' ' + icon(options.icons.caret, 'span');
1947
+ };
1948
+
1902
1949
  var dropdownCheck = renderer.create('<div class="dropdown-menu note-check">', function ($node, options) {
1903
1950
  var markup = $.isArray(options.items) ? options.items.map(function (item) {
1904
1951
  var value = (typeof item === 'string') ? item : (item.value || '');
@@ -1929,11 +1976,13 @@
1929
1976
  }
1930
1977
  $node.html(contents.join(''));
1931
1978
 
1932
- $node.find('.note-color-btn').tooltip({
1933
- container: 'body',
1934
- trigger: 'hover',
1935
- placement: 'bottom'
1936
- });
1979
+ if (options.tooltip) {
1980
+ $node.find('.note-color-btn').tooltip({
1981
+ container: 'body',
1982
+ trigger: 'hover',
1983
+ placement: 'bottom'
1984
+ });
1985
+ }
1937
1986
  });
1938
1987
 
1939
1988
  var dialog = renderer.create('<div class="modal" aria-hidden="false" tabindex="-1"/>', function ($node, options) {
@@ -1973,6 +2022,16 @@
1973
2022
  }
1974
2023
  });
1975
2024
 
2025
+ var checkbox = renderer.create('<div class="checkbox"></div>', function ($node, options) {
2026
+ $node.html([
2027
+ ' <label' + (options.id ? ' for="' + options.id + '"' : '') + '>',
2028
+ ' <input type="checkbox"' + (options.id ? ' id="' + options.id + '"' : ''),
2029
+ (options.checked ? ' checked' : '') + '/>',
2030
+ (options.text ? options.text : ''),
2031
+ '</label>'
2032
+ ].join(''));
2033
+ });
2034
+
1976
2035
  var icon = function (iconClassName, tagName) {
1977
2036
  tagName = tagName || 'i';
1978
2037
  return '<' + tagName + ' class="' + iconClassName + '"/>';
@@ -1988,13 +2047,29 @@
1988
2047
  airEditor: airEditor,
1989
2048
  airEditable: airEditable,
1990
2049
  buttonGroup: buttonGroup,
1991
- button: button,
1992
2050
  dropdown: dropdown,
2051
+ dropdownButtonContents: dropdownButtonContents,
1993
2052
  dropdownCheck: dropdownCheck,
1994
2053
  palette: palette,
1995
2054
  dialog: dialog,
1996
2055
  popover: popover,
2056
+ checkbox: checkbox,
1997
2057
  icon: icon,
2058
+ options: {},
2059
+
2060
+ button: function ($node, options) {
2061
+ return renderer.create('<button type="button" class="note-btn btn btn-default btn-sm" tabindex="-1">', function ($node, options) {
2062
+ if (options && options.tooltip && self.options.tooltip) {
2063
+ $node.attr({
2064
+ title: options.tooltip
2065
+ }).tooltip({
2066
+ container: 'body',
2067
+ trigger: 'hover',
2068
+ placement: 'bottom'
2069
+ });
2070
+ }
2071
+ })($node, options);
2072
+ },
1998
2073
 
1999
2074
  toggleBtn: function ($btn, isEnable) {
2000
2075
  $btn.toggleClass('disabled', !isEnable);
@@ -2022,6 +2097,7 @@
2022
2097
  },
2023
2098
 
2024
2099
  createLayout: function ($note, options) {
2100
+ self.options = options;
2025
2101
  var $editor = (options.airMode ? ui.airEditor([
2026
2102
  ui.editingArea([
2027
2103
  ui.airEditable()
@@ -2111,7 +2187,14 @@
2111
2187
  openInNewWindow: 'Open in new window'
2112
2188
  },
2113
2189
  table: {
2114
- table: 'Table'
2190
+ table: 'Table',
2191
+ addRowAbove: 'Add row above',
2192
+ addRowBelow: 'Add row below',
2193
+ addColLeft: 'Add column left',
2194
+ addColRight: 'Add column right',
2195
+ delRow: 'Delete row',
2196
+ delCol: 'Delete column',
2197
+ delTable: 'Delete table'
2115
2198
  },
2116
2199
  hr: {
2117
2200
  insert: 'Insert Horizontal Rule'
@@ -2220,6 +2303,7 @@
2220
2303
  'TAB': 9,
2221
2304
  'ENTER': 13,
2222
2305
  'SPACE': 32,
2306
+ 'DELETE': 46,
2223
2307
 
2224
2308
  // Arrow
2225
2309
  'LEFT': 37,
@@ -2270,7 +2354,8 @@
2270
2354
  keyMap.BACKSPACE,
2271
2355
  keyMap.TAB,
2272
2356
  keyMap.ENTER,
2273
- keyMap.SPACE
2357
+ keyMap.SPACE,
2358
+ keyMap.DELETE
2274
2359
  ], keyCode);
2275
2360
  },
2276
2361
  /**
@@ -3385,7 +3470,8 @@
3385
3470
  'font-underline': document.queryCommandState('underline') ? 'underline' : 'normal',
3386
3471
  'font-subscript': document.queryCommandState('subscript') ? 'subscript' : 'normal',
3387
3472
  'font-superscript': document.queryCommandState('superscript') ? 'superscript' : 'normal',
3388
- 'font-strikethrough': document.queryCommandState('strikethrough') ? 'strikethrough' : 'normal'
3473
+ 'font-strikethrough': document.queryCommandState('strikethrough') ? 'strikethrough' : 'normal',
3474
+ 'font-family': document.queryCommandValue('fontname') || styleInfo['font-family']
3389
3475
  });
3390
3476
  } catch (e) {}
3391
3477
 
@@ -3687,8 +3773,8 @@
3687
3773
  dom.remove(anchor);
3688
3774
  });
3689
3775
 
3690
- // replace empty heading or pre with P tag
3691
- if ((dom.isHeading(nextPara) || dom.isPre(nextPara)) && dom.isEmpty(nextPara)) {
3776
+ // replace empty heading, pre or custom-made styleTag with P tag
3777
+ if ((dom.isHeading(nextPara) || dom.isPre(nextPara) || dom.isCustomStyleTag(nextPara)) && dom.isEmpty(nextPara)) {
3692
3778
  nextPara = dom.replace(nextPara, 'p');
3693
3779
  }
3694
3780
  }
@@ -3707,62 +3793,581 @@
3707
3793
  };
3708
3794
  };
3709
3795
 
3796
+
3710
3797
  /**
3711
- * @class editing.Table
3712
- *
3713
- * Table
3714
- *
3798
+ * @class Create a virtual table to create what actions to do in change.
3799
+ * @param {object} startPoint Cell selected to apply change.
3800
+ * @param {enum} where Where change will be applied Row or Col. Use enum: TableResultAction.where
3801
+ * @param {enum} action Action to be applied. Use enum: TableResultAction.requestAction
3802
+ * @param {object} domTable Dom element of table to make changes.
3715
3803
  */
3716
- var Table = function () {
3804
+ var TableResultAction = function (startPoint, where, action, domTable) {
3805
+ var _startPoint = { 'colPos': 0, 'rowPos': 0 };
3806
+ var _virtualTable = [];
3807
+ var _actionCellList = [];
3808
+
3809
+ //////////////////////////////////////////////
3810
+ // Private functions
3811
+ //////////////////////////////////////////////
3812
+
3717
3813
  /**
3718
- * handle tab key
3719
- *
3720
- * @param {WrappedRange} rng
3721
- * @param {Boolean} isShift
3814
+ * Set the startPoint of action.
3722
3815
  */
3723
- this.tab = function (rng, isShift) {
3724
- var cell = dom.ancestor(rng.commonAncestor(), dom.isCell);
3725
- var table = dom.ancestor(cell, dom.isTable);
3726
- var cells = dom.listDescendant(table, dom.isCell);
3816
+ function setStartPoint() {
3817
+ if (!startPoint || !startPoint.tagName || (startPoint.tagName.toLowerCase() !== 'td' && startPoint.tagName.toLowerCase() !== 'th')) {
3818
+ console.error('Impossible to identify start Cell point.', startPoint);
3819
+ return;
3820
+ }
3821
+ _startPoint.colPos = startPoint.cellIndex;
3822
+ if (!startPoint.parentElement || !startPoint.parentElement.tagName || startPoint.parentElement.tagName.toLowerCase() !== 'tr') {
3823
+ console.error('Impossible to identify start Row point.', startPoint);
3824
+ return;
3825
+ }
3826
+ _startPoint.rowPos = startPoint.parentElement.rowIndex;
3827
+ }
3727
3828
 
3728
- var nextCell = list[isShift ? 'prev' : 'next'](cells, cell);
3729
- if (nextCell) {
3730
- range.create(nextCell, 0).select();
3829
+ /**
3830
+ * Define virtual table position info object.
3831
+ *
3832
+ * @param {int} rowIndex Index position in line of virtual table.
3833
+ * @param {int} cellIndex Index position in column of virtual table.
3834
+ * @param {object} baseRow Row affected by this position.
3835
+ * @param {object} baseCell Cell affected by this position.
3836
+ * @param {bool} isSpan Inform if it is an span cell/row.
3837
+ */
3838
+ function setVirtualTablePosition(rowIndex, cellIndex, baseRow, baseCell, isRowSpan, isColSpan, isVirtualCell) {
3839
+ var objPosition = {
3840
+ 'baseRow': baseRow,
3841
+ 'baseCell': baseCell,
3842
+ 'isRowSpan': isRowSpan,
3843
+ 'isColSpan': isColSpan,
3844
+ 'isVirtual': isVirtualCell
3845
+ };
3846
+ if (!_virtualTable[rowIndex]) {
3847
+ _virtualTable[rowIndex] = [];
3731
3848
  }
3732
- };
3849
+ _virtualTable[rowIndex][cellIndex] = objPosition;
3850
+ }
3733
3851
 
3734
3852
  /**
3735
- * create empty table element
3736
- *
3737
- * @param {Number} rowCount
3738
- * @param {Number} colCount
3739
- * @return {Node}
3853
+ * Create action cell object.
3854
+ *
3855
+ * @param {object} virtualTableCellObj Object of specific position on virtual table.
3856
+ * @param {enum} resultAction Action to be applied in that item.
3740
3857
  */
3741
- this.createTable = function (colCount, rowCount, options) {
3742
- var tds = [], tdHTML;
3743
- for (var idxCol = 0; idxCol < colCount; idxCol++) {
3744
- tds.push('<td>' + dom.blank + '</td>');
3745
- }
3746
- tdHTML = tds.join('');
3858
+ function getActionCell(virtualTableCellObj, resultAction, virtualRowPosition, virtualColPosition) {
3859
+ return {
3860
+ 'baseCell': virtualTableCellObj.baseCell,
3861
+ 'action': resultAction,
3862
+ 'virtualTable': {
3863
+ 'rowIndex': virtualRowPosition,
3864
+ 'cellIndex': virtualColPosition
3865
+ }
3866
+ };
3867
+ }
3747
3868
 
3748
- var trs = [], trHTML;
3749
- for (var idxRow = 0; idxRow < rowCount; idxRow++) {
3750
- trs.push('<tr>' + tdHTML + '</tr>');
3869
+ /**
3870
+ * Recover free index of row to append Cell.
3871
+ *
3872
+ * @param {int} rowIndex Index of row to find free space.
3873
+ * @param {int} cellIndex Index of cell to find free space in table.
3874
+ */
3875
+ function recoverCellIndex(rowIndex, cellIndex) {
3876
+ if (!_virtualTable[rowIndex]) {
3877
+ return cellIndex;
3751
3878
  }
3752
- trHTML = trs.join('');
3753
- var $table = $('<table>' + trHTML + '</table>');
3754
- if (options && options.tableClassName) {
3755
- $table.addClass(options.tableClassName);
3879
+ if (!_virtualTable[rowIndex][cellIndex]) {
3880
+ return cellIndex;
3756
3881
  }
3757
3882
 
3758
- return $table[0];
3759
- };
3760
- };
3883
+ var newCellIndex = cellIndex;
3884
+ while (_virtualTable[rowIndex][newCellIndex]) {
3885
+ newCellIndex++;
3886
+ if (!_virtualTable[rowIndex][newCellIndex]) {
3887
+ return newCellIndex;
3888
+ }
3889
+ }
3890
+ }
3761
3891
 
3892
+ /**
3893
+ * Recover info about row and cell and add information to virtual table.
3894
+ *
3895
+ * @param {object} row Row to recover information.
3896
+ * @param {object} cell Cell to recover information.
3897
+ */
3898
+ function addCellInfoToVirtual(row, cell) {
3899
+ var cellIndex = recoverCellIndex(row.rowIndex, cell.cellIndex);
3900
+ var cellHasColspan = (cell.colSpan > 1);
3901
+ var cellHasRowspan = (cell.rowSpan > 1);
3902
+ var isThisSelectedCell = (row.rowIndex === _startPoint.rowPos && cell.cellIndex === _startPoint.colPos);
3903
+ setVirtualTablePosition(row.rowIndex, cellIndex, row, cell, cellHasRowspan, cellHasColspan, false);
3904
+
3905
+ // Add span rows to virtual Table.
3906
+ var rowspanNumber = cell.attributes.rowSpan ? parseInt(cell.attributes.rowSpan.value, 10) : 0;
3907
+ if (rowspanNumber > 1) {
3908
+ for (var rp = 1; rp < rowspanNumber; rp++) {
3909
+ var rowspanIndex = row.rowIndex + rp;
3910
+ adjustStartPoint(rowspanIndex, cellIndex, cell, isThisSelectedCell);
3911
+ setVirtualTablePosition(rowspanIndex, cellIndex, row, cell, true, cellHasColspan, true);
3912
+ }
3913
+ }
3762
3914
 
3763
- var KEY_BOGUS = 'bogus';
3915
+ // Add span cols to virtual table.
3916
+ var colspanNumber = cell.attributes.colSpan ? parseInt(cell.attributes.colSpan.value, 10) : 0;
3917
+ if (colspanNumber > 1) {
3918
+ for (var cp = 1; cp < colspanNumber; cp++) {
3919
+ var cellspanIndex = recoverCellIndex(row.rowIndex, (cellIndex + cp));
3920
+ adjustStartPoint(row.rowIndex, cellspanIndex, cell, isThisSelectedCell);
3921
+ setVirtualTablePosition(row.rowIndex, cellspanIndex, row, cell, cellHasRowspan, true, true);
3922
+ }
3923
+ }
3924
+ }
3764
3925
 
3765
- /**
3926
+ /**
3927
+ * Process validation and adjust of start point if needed
3928
+ *
3929
+ * @param {int} rowIndex
3930
+ * @param {int} cellIndex
3931
+ * @param {object} cell
3932
+ * @param {bool} isSelectedCell
3933
+ */
3934
+ function adjustStartPoint(rowIndex, cellIndex, cell, isSelectedCell) {
3935
+ if (rowIndex === _startPoint.rowPos && _startPoint.colPos >= cell.cellIndex && cell.cellIndex <= cellIndex && !isSelectedCell) {
3936
+ _startPoint.colPos++;
3937
+ }
3938
+ }
3939
+
3940
+ /**
3941
+ * Create virtual table of cells with all cells, including span cells.
3942
+ */
3943
+ function createVirtualTable() {
3944
+ var rows = domTable.rows;
3945
+ for (var rowIndex = 0; rowIndex < rows.length; rowIndex++) {
3946
+ var cells = rows[rowIndex].cells;
3947
+ for (var cellIndex = 0; cellIndex < cells.length; cellIndex++) {
3948
+ addCellInfoToVirtual(rows[rowIndex], cells[cellIndex]);
3949
+ }
3950
+ }
3951
+ }
3952
+
3953
+ /**
3954
+ * Get action to be applied on the cell.
3955
+ *
3956
+ * @param {object} cell virtual table cell to apply action
3957
+ */
3958
+ function getDeleteResultActionToCell(cell) {
3959
+ switch (where) {
3960
+ case TableResultAction.where.Column:
3961
+ if (cell.isColSpan) {
3962
+ return TableResultAction.resultAction.SubtractSpanCount;
3963
+ }
3964
+ break;
3965
+ case TableResultAction.where.Row:
3966
+ if (!cell.isVirtual && cell.isRowSpan) {
3967
+ return TableResultAction.resultAction.AddCell;
3968
+ }
3969
+ else if (cell.isRowSpan) {
3970
+ return TableResultAction.resultAction.SubtractSpanCount;
3971
+ }
3972
+ break;
3973
+ }
3974
+ return TableResultAction.resultAction.RemoveCell;
3975
+ }
3976
+
3977
+ /**
3978
+ * Get action to be applied on the cell.
3979
+ *
3980
+ * @param {object} cell virtual table cell to apply action
3981
+ */
3982
+ function getAddResultActionToCell(cell) {
3983
+ switch (where) {
3984
+ case TableResultAction.where.Column:
3985
+ if (cell.isColSpan) {
3986
+ return TableResultAction.resultAction.SumSpanCount;
3987
+ } else if (cell.isRowSpan && cell.isVirtual) {
3988
+ return TableResultAction.resultAction.Ignore;
3989
+ }
3990
+ break;
3991
+ case TableResultAction.where.Row:
3992
+ if (cell.isRowSpan) {
3993
+ return TableResultAction.resultAction.SumSpanCount;
3994
+ } else if (cell.isColSpan && cell.isVirtual) {
3995
+ return TableResultAction.resultAction.Ignore;
3996
+ }
3997
+ break;
3998
+ }
3999
+ return TableResultAction.resultAction.AddCell;
4000
+ }
4001
+
4002
+ function init() {
4003
+ setStartPoint();
4004
+ createVirtualTable();
4005
+ }
4006
+
4007
+ //////////////////////////////////////////////
4008
+ // Public functions
4009
+ //////////////////////////////////////////////
4010
+
4011
+ /**
4012
+ * Recover array os what to do in table.
4013
+ */
4014
+ this.getActionList = function () {
4015
+ var fixedRow = (where === TableResultAction.where.Row) ? _startPoint.rowPos : -1;
4016
+ var fixedCol = (where === TableResultAction.where.Column) ? _startPoint.colPos : -1;
4017
+
4018
+ var actualPosition = 0;
4019
+ var canContinue = true;
4020
+ while (canContinue) {
4021
+ var rowPosition = (fixedRow >= 0) ? fixedRow : actualPosition;
4022
+ var colPosition = (fixedCol >= 0) ? fixedCol : actualPosition;
4023
+ var row = _virtualTable[rowPosition];
4024
+ if (!row) {
4025
+ canContinue = false;
4026
+ return _actionCellList;
4027
+ }
4028
+ var cell = row[colPosition];
4029
+ if (!cell) {
4030
+ canContinue = false;
4031
+ return _actionCellList;
4032
+ }
4033
+
4034
+ // Define action to be applied in this cell
4035
+ var resultAction = TableResultAction.resultAction.Ignore;
4036
+ switch (action) {
4037
+ case TableResultAction.requestAction.Add:
4038
+ resultAction = getAddResultActionToCell(cell);
4039
+ break;
4040
+ case TableResultAction.requestAction.Delete:
4041
+ resultAction = getDeleteResultActionToCell(cell);
4042
+ break;
4043
+ }
4044
+ _actionCellList.push(getActionCell(cell, resultAction, rowPosition, colPosition));
4045
+ actualPosition++;
4046
+ }
4047
+
4048
+ return _actionCellList;
4049
+ };
4050
+
4051
+ init();
4052
+ };
4053
+ /**
4054
+ *
4055
+ * Where action occours enum.
4056
+ */
4057
+ TableResultAction.where = { 'Row': 0, 'Column': 1 };
4058
+ /**
4059
+ *
4060
+ * Requested action to apply enum.
4061
+ */
4062
+ TableResultAction.requestAction = { 'Add': 0, 'Delete': 1 };
4063
+ /**
4064
+ *
4065
+ * Result action to be executed enum.
4066
+ */
4067
+ TableResultAction.resultAction = { 'Ignore': 0, 'SubtractSpanCount': 1, 'RemoveCell': 2, 'AddCell': 3, 'SumSpanCount': 4 };
4068
+
4069
+ /**
4070
+ *
4071
+ * @class editing.Table
4072
+ *
4073
+ * Table
4074
+ *
4075
+ */
4076
+ var Table = function () {
4077
+ /**
4078
+ * handle tab key
4079
+ *
4080
+ * @param {WrappedRange} rng
4081
+ * @param {Boolean} isShift
4082
+ */
4083
+ this.tab = function (rng, isShift) {
4084
+ var cell = dom.ancestor(rng.commonAncestor(), dom.isCell);
4085
+ var table = dom.ancestor(cell, dom.isTable);
4086
+ var cells = dom.listDescendant(table, dom.isCell);
4087
+
4088
+ var nextCell = list[isShift ? 'prev' : 'next'](cells, cell);
4089
+ if (nextCell) {
4090
+ range.create(nextCell, 0).select();
4091
+ }
4092
+ };
4093
+
4094
+ /**
4095
+ * Add a new row
4096
+ *
4097
+ * @param {WrappedRange} rng
4098
+ * @param {String} position (top/bottom)
4099
+ * @return {Node}
4100
+ */
4101
+ this.addRow = function (rng, position) {
4102
+ var cell = dom.ancestor(rng.commonAncestor(), dom.isCell);
4103
+
4104
+ var currentTr = $(cell).closest('tr');
4105
+ var trAttributes = this.recoverAttributes(currentTr);
4106
+ var html = $('<tr' + trAttributes + '></tr>');
4107
+
4108
+ var vTable = new TableResultAction(cell, TableResultAction.where.Row,
4109
+ TableResultAction.requestAction.Add, $(currentTr).closest('table')[0]);
4110
+ var actions = vTable.getActionList();
4111
+
4112
+ for (var idCell = 0; idCell < actions.length; idCell++) {
4113
+ var currentCell = actions[idCell];
4114
+ var tdAttributes = this.recoverAttributes(currentCell.baseCell);
4115
+ switch (currentCell.action) {
4116
+ case TableResultAction.resultAction.AddCell:
4117
+ html.append('<td' + tdAttributes + '>' + dom.blank + '</td>');
4118
+ break;
4119
+ case TableResultAction.resultAction.SumSpanCount:
4120
+ if (position === 'top') {
4121
+ var baseCellTr = currentCell.baseCell.parent;
4122
+ var isTopFromRowSpan = (!baseCellTr ? 0 : currentCell.baseCell.closest('tr').rowIndex) <= currentTr[0].rowIndex;
4123
+ if (isTopFromRowSpan) {
4124
+ var newTd = $('<div></div>').append($('<td' + tdAttributes + '>' + dom.blank + '</td>').removeAttr('rowspan')).html();
4125
+ html.append(newTd);
4126
+ break;
4127
+ }
4128
+ }
4129
+ var rowspanNumber = parseInt(currentCell.baseCell.rowSpan, 10);
4130
+ rowspanNumber++;
4131
+ currentCell.baseCell.setAttribute('rowSpan', rowspanNumber);
4132
+ break;
4133
+ }
4134
+ }
4135
+
4136
+ if (position === 'top') {
4137
+ currentTr.before(html);
4138
+ }
4139
+ else {
4140
+ var cellHasRowspan = (cell.rowSpan > 1);
4141
+ if (cellHasRowspan) {
4142
+ var lastTrIndex = currentTr[0].rowIndex + (cell.rowSpan - 2);
4143
+ $($(currentTr).parent().find('tr')[lastTrIndex]).after($(html));
4144
+ return;
4145
+ }
4146
+ currentTr.after(html);
4147
+ }
4148
+ };
4149
+
4150
+ /**
4151
+ * Add a new col
4152
+ *
4153
+ * @param {WrappedRange} rng
4154
+ * @param {String} position (left/right)
4155
+ * @return {Node}
4156
+ */
4157
+ this.addCol = function (rng, position) {
4158
+ var cell = dom.ancestor(rng.commonAncestor(), dom.isCell);
4159
+ var row = $(cell).closest('tr');
4160
+ var rowsGroup = $(row).siblings();
4161
+ rowsGroup.push(row);
4162
+
4163
+ var vTable = new TableResultAction(cell, TableResultAction.where.Column,
4164
+ TableResultAction.requestAction.Add, $(row).closest('table')[0]);
4165
+ var actions = vTable.getActionList();
4166
+
4167
+ for (var actionIndex = 0; actionIndex < actions.length; actionIndex++) {
4168
+ var currentCell = actions[actionIndex];
4169
+ var tdAttributes = this.recoverAttributes(currentCell.baseCell);
4170
+ switch (currentCell.action) {
4171
+ case TableResultAction.resultAction.AddCell:
4172
+ if (position === 'right') {
4173
+ $(currentCell.baseCell).after('<td' + tdAttributes + '>' + dom.blank + '</td>');
4174
+ } else {
4175
+ $(currentCell.baseCell).before('<td' + tdAttributes + '>' + dom.blank + '</td>');
4176
+ }
4177
+ break;
4178
+ case TableResultAction.resultAction.SumSpanCount:
4179
+ if (position === 'right') {
4180
+ var colspanNumber = parseInt(currentCell.baseCell.colSpan, 10);
4181
+ colspanNumber++;
4182
+ currentCell.baseCell.setAttribute('colSpan', colspanNumber);
4183
+ } else {
4184
+ $(currentCell.baseCell).before('<td' + tdAttributes + '>' + dom.blank + '</td>');
4185
+ }
4186
+ break;
4187
+ }
4188
+ }
4189
+ };
4190
+
4191
+ /*
4192
+ * Copy attributes from element.
4193
+ *
4194
+ * @param {object} Element to recover attributes.
4195
+ * @return {string} Copied string elements.
4196
+ */
4197
+ this.recoverAttributes = function (el) {
4198
+ var resultStr = '';
4199
+
4200
+ if (!el) {
4201
+ return resultStr;
4202
+ }
4203
+
4204
+ var attrList = el.attributes || [];
4205
+
4206
+ for (var i = 0; i < attrList.length; i++) {
4207
+ if (attrList[i].name.toLowerCase() === 'id') {
4208
+ continue;
4209
+ }
4210
+
4211
+ if (attrList[i].specified) {
4212
+ resultStr += ' ' + attrList[i].name + '=\'' + attrList[i].value + '\'';
4213
+ }
4214
+ }
4215
+
4216
+ return resultStr;
4217
+ };
4218
+
4219
+ /**
4220
+ * Delete current row
4221
+ *
4222
+ * @param {WrappedRange} rng
4223
+ * @return {Node}
4224
+ */
4225
+ this.deleteRow = function (rng) {
4226
+ var cell = dom.ancestor(rng.commonAncestor(), dom.isCell);
4227
+ var row = $(cell).closest('tr');
4228
+ var cellPos = row.children('td, th').index($(cell));
4229
+ var rowPos = row[0].rowIndex;
4230
+
4231
+ var vTable = new TableResultAction(cell, TableResultAction.where.Row,
4232
+ TableResultAction.requestAction.Delete, $(row).closest('table')[0]);
4233
+ var actions = vTable.getActionList();
4234
+
4235
+ for (var actionIndex = 0; actionIndex < actions.length; actionIndex++) {
4236
+ if (!actions[actionIndex]) {
4237
+ continue;
4238
+ }
4239
+
4240
+ var baseCell = actions[actionIndex].baseCell;
4241
+ var virtualPosition = actions[actionIndex].virtualTable;
4242
+ var hasRowspan = (baseCell.rowSpan && baseCell.rowSpan > 1);
4243
+ var rowspanNumber = (hasRowspan) ? parseInt(baseCell.rowSpan, 10) : 0;
4244
+ switch (actions[actionIndex].action) {
4245
+ case TableResultAction.resultAction.Ignore:
4246
+ continue;
4247
+ case TableResultAction.resultAction.AddCell:
4248
+ var nextRow = row.next('tr')[0];
4249
+ if (!nextRow) { continue; }
4250
+ var cloneRow = row[0].cells[cellPos];
4251
+ if (hasRowspan) {
4252
+ if (rowspanNumber > 2) {
4253
+ rowspanNumber--;
4254
+ nextRow.insertBefore(cloneRow, nextRow.cells[cellPos]);
4255
+ nextRow.cells[cellPos].setAttribute('rowSpan', rowspanNumber);
4256
+ nextRow.cells[cellPos].innerHTML = '';
4257
+ } else if (rowspanNumber === 2) {
4258
+ nextRow.insertBefore(cloneRow, nextRow.cells[cellPos]);
4259
+ nextRow.cells[cellPos].removeAttribute('rowSpan');
4260
+ nextRow.cells[cellPos].innerHTML = '';
4261
+ }
4262
+ }
4263
+ continue;
4264
+ case TableResultAction.resultAction.SubtractSpanCount:
4265
+ if (hasRowspan) {
4266
+ if (rowspanNumber > 2) {
4267
+ rowspanNumber--;
4268
+ baseCell.setAttribute('rowSpan', rowspanNumber);
4269
+ if (virtualPosition.rowIndex !== rowPos && baseCell.cellIndex === cellPos) { baseCell.innerHTML = ''; }
4270
+ } else if (rowspanNumber === 2) {
4271
+ baseCell.removeAttribute('rowSpan');
4272
+ if (virtualPosition.rowIndex !== rowPos && baseCell.cellIndex === cellPos) { baseCell.innerHTML = ''; }
4273
+ }
4274
+ }
4275
+ continue;
4276
+ case TableResultAction.resultAction.RemoveCell:
4277
+ // Do not need remove cell because row will be deleted.
4278
+ continue;
4279
+ }
4280
+ }
4281
+ row.remove();
4282
+ };
4283
+
4284
+ /**
4285
+ * Delete current col
4286
+ *
4287
+ * @param {WrappedRange} rng
4288
+ * @return {Node}
4289
+ */
4290
+ this.deleteCol = function (rng) {
4291
+ var cell = dom.ancestor(rng.commonAncestor(), dom.isCell);
4292
+ var row = $(cell).closest('tr');
4293
+ var cellPos = row.children('td, th').index($(cell));
4294
+
4295
+ var vTable = new TableResultAction(cell, TableResultAction.where.Column,
4296
+ TableResultAction.requestAction.Delete, $(row).closest('table')[0]);
4297
+ var actions = vTable.getActionList();
4298
+
4299
+ for (var actionIndex = 0; actionIndex < actions.length; actionIndex++) {
4300
+ if (!actions[actionIndex]) {
4301
+ continue;
4302
+ }
4303
+ switch (actions[actionIndex].action) {
4304
+ case TableResultAction.resultAction.Ignore:
4305
+ continue;
4306
+ case TableResultAction.resultAction.SubtractSpanCount:
4307
+ var baseCell = actions[actionIndex].baseCell;
4308
+ var hasColspan = (baseCell.colSpan && baseCell.colSpan > 1);
4309
+ if (hasColspan) {
4310
+ var colspanNumber = (baseCell.colSpan) ? parseInt(baseCell.colSpan, 10) : 0;
4311
+ if (colspanNumber > 2) {
4312
+ colspanNumber--;
4313
+ baseCell.setAttribute('colSpan', colspanNumber);
4314
+ if (baseCell.cellIndex === cellPos) { baseCell.innerHTML = ''; }
4315
+ } else if (colspanNumber === 2) {
4316
+ baseCell.removeAttribute('colSpan');
4317
+ if (baseCell.cellIndex === cellPos) { baseCell.innerHTML = ''; }
4318
+ }
4319
+ }
4320
+ continue;
4321
+ case TableResultAction.resultAction.RemoveCell:
4322
+ dom.remove(actions[actionIndex].baseCell, true);
4323
+ continue;
4324
+ }
4325
+ }
4326
+ };
4327
+
4328
+ /**
4329
+ * create empty table element
4330
+ *
4331
+ * @param {Number} rowCount
4332
+ * @param {Number} colCount
4333
+ * @return {Node}
4334
+ */
4335
+ this.createTable = function (colCount, rowCount, options) {
4336
+ var tds = [], tdHTML;
4337
+ for (var idxCol = 0; idxCol < colCount; idxCol++) {
4338
+ tds.push('<td>' + dom.blank + '</td>');
4339
+ }
4340
+ tdHTML = tds.join('');
4341
+
4342
+ var trs = [], trHTML;
4343
+ for (var idxRow = 0; idxRow < rowCount; idxRow++) {
4344
+ trs.push('<tr>' + tdHTML + '</tr>');
4345
+ }
4346
+ trHTML = trs.join('');
4347
+ var $table = $('<table>' + trHTML + '</table>');
4348
+ if (options && options.tableClassName) {
4349
+ $table.addClass(options.tableClassName);
4350
+ }
4351
+
4352
+ return $table[0];
4353
+ };
4354
+
4355
+ /**
4356
+ * Delete current table
4357
+ *
4358
+ * @param {WrappedRange} rng
4359
+ * @return {Node}
4360
+ */
4361
+ this.deleteTable = function (rng) {
4362
+ var cell = dom.ancestor(rng.commonAncestor(), dom.isCell);
4363
+ $(cell).closest('table').remove();
4364
+ };
4365
+ };
4366
+
4367
+
4368
+ var KEY_BOGUS = 'bogus';
4369
+
4370
+ /**
3766
4371
  * @class Editor
3767
4372
  */
3768
4373
  var Editor = function (context) {
@@ -3822,7 +4427,7 @@
3822
4427
  var changeEventName = agent.isMSIE ? 'DOMCharacterDataModified DOMSubtreeModified DOMNodeInserted' : 'input';
3823
4428
  $editable.on(changeEventName, func.debounce(function () {
3824
4429
  context.triggerEvent('change', $editable.html());
3825
- }, 250));
4430
+ }, 100));
3826
4431
 
3827
4432
  $editor.on('focusin', function (event) {
3828
4433
  context.triggerEvent('focusin', event);
@@ -3998,7 +4603,7 @@
3998
4603
  var commands = ['bold', 'italic', 'underline', 'strikethrough', 'superscript', 'subscript',
3999
4604
  'justifyLeft', 'justifyCenter', 'justifyRight', 'justifyFull',
4000
4605
  'formatBlock', 'removeFormat',
4001
- 'backColor', 'foreColor', 'fontName'];
4606
+ 'backColor', 'fontName'];
4002
4607
 
4003
4608
  for (var idx = 0, len = commands.length; idx < len; idx ++) {
4004
4609
  this[commands[idx]] = (function (sCmd) {
@@ -4191,11 +4796,20 @@
4191
4796
  *
4192
4797
  * @param {String} tagName
4193
4798
  */
4194
- this.formatBlock = this.wrapCommand(function (tagName) {
4799
+ this.formatBlock = this.wrapCommand(function (tagName, $target) {
4800
+ var onApplyCustomStyle = context.options.callbacks.onApplyCustomStyle;
4801
+ if (onApplyCustomStyle) {
4802
+ onApplyCustomStyle.call(this, $target, context, this.onFormatBlock);
4803
+ } else {
4804
+ this.onFormatBlock(tagName);
4805
+ }
4806
+ });
4807
+
4808
+ this.onFormatBlock = function (tagName) {
4195
4809
  // [workaround] for MSIE, IE need `<`
4196
4810
  tagName = agent.isMSIE ? '<' + tagName + '>' : tagName;
4197
4811
  document.execCommand('FormatBlock', false, tagName);
4198
- });
4812
+ };
4199
4813
 
4200
4814
  this.formatPara = function () {
4201
4815
  this.formatBlock('P');
@@ -4381,13 +4995,18 @@
4381
4995
 
4382
4996
  // Get the first anchor on range(for edit).
4383
4997
  var $anchor = $(list.head(rng.nodes(dom.isAnchor)));
4384
-
4385
- return {
4998
+ var linkInfo = {
4386
4999
  range: rng,
4387
5000
  text: rng.toString(),
4388
- isNewWindow: $anchor.length ? $anchor.attr('target') === '_blank' : false,
4389
5001
  url: $anchor.length ? $anchor.attr('href') : ''
4390
5002
  };
5003
+
5004
+ // Define isNewWindow when anchor exists.
5005
+ if ($anchor.length) {
5006
+ linkInfo.isNewWindow = $anchor.attr('target') === '_blank';
5007
+ }
5008
+
5009
+ return linkInfo;
4391
5010
  };
4392
5011
 
4393
5012
  /**
@@ -4405,6 +5024,16 @@
4405
5024
  if (backColor) { document.execCommand('backColor', false, backColor); }
4406
5025
  });
4407
5026
 
5027
+ /**
5028
+ * Set foreground color
5029
+ *
5030
+ * @param {String} colorCode foreground color code
5031
+ */
5032
+ this.foreColor = this.wrapCommand(function (colorInfo) {
5033
+ document.execCommand('styleWithCSS', false, true);
5034
+ document.execCommand('foreColor', false, colorInfo);
5035
+ });
5036
+
4408
5037
  /**
4409
5038
  * insert Table
4410
5039
  *
@@ -4417,6 +5046,76 @@
4417
5046
  rng.insertNode(table.createTable(dimension[0], dimension[1], options));
4418
5047
  });
4419
5048
 
5049
+ /**
5050
+ * @method addRow
5051
+ *
5052
+ *
5053
+ */
5054
+ this.addRow = function (position) {
5055
+ var rng = this.createRange($editable);
5056
+ if (rng.isCollapsed() && rng.isOnCell()) {
5057
+ beforeCommand();
5058
+ table.addRow(rng, position);
5059
+ afterCommand();
5060
+ }
5061
+ };
5062
+
5063
+ /**
5064
+ * @method addCol
5065
+ *
5066
+ *
5067
+ */
5068
+ this.addCol = function (position) {
5069
+ var rng = this.createRange($editable);
5070
+ if (rng.isCollapsed() && rng.isOnCell()) {
5071
+ beforeCommand();
5072
+ table.addCol(rng, position);
5073
+ afterCommand();
5074
+ }
5075
+ };
5076
+
5077
+ /**
5078
+ * @method deleteRow
5079
+ *
5080
+ *
5081
+ */
5082
+ this.deleteRow = function () {
5083
+ var rng = this.createRange($editable);
5084
+ if (rng.isCollapsed() && rng.isOnCell()) {
5085
+ beforeCommand();
5086
+ table.deleteRow(rng);
5087
+ afterCommand();
5088
+ }
5089
+ };
5090
+
5091
+ /**
5092
+ * @method deleteCol
5093
+ *
5094
+ *
5095
+ */
5096
+ this.deleteCol = function () {
5097
+ var rng = this.createRange($editable);
5098
+ if (rng.isCollapsed() && rng.isOnCell()) {
5099
+ beforeCommand();
5100
+ table.deleteCol(rng);
5101
+ afterCommand();
5102
+ }
5103
+ };
5104
+
5105
+ /**
5106
+ * @method deleteTable
5107
+ *
5108
+ *
5109
+ */
5110
+ this.deleteTable = function () {
5111
+ var rng = this.createRange($editable);
5112
+ if (rng.isCollapsed() && rng.isOnCell()) {
5113
+ beforeCommand();
5114
+ table.deleteTable(rng);
5115
+ afterCommand();
5116
+ }
5117
+ };
5118
+
4420
5119
  /**
4421
5120
  * float me
4422
5121
  *
@@ -4424,6 +5123,8 @@
4424
5123
  */
4425
5124
  this.floatMe = this.wrapCommand(function (value) {
4426
5125
  var $target = $(this.restoreTarget());
5126
+ $target.toggleClass('note-float-left', value === 'left');
5127
+ $target.toggleClass('note-float-right', value === 'right');
4427
5128
  $target.css('float', value);
4428
5129
  });
4429
5130
 
@@ -4559,9 +5260,9 @@
4559
5260
  this.pasteByHook = function () {
4560
5261
  var node = this.$paste[0].firstChild;
4561
5262
 
4562
- if (dom.isImg(node)) {
4563
- var dataURI = node.src;
4564
- var decodedData = atob(dataURI.split(',')[1]);
5263
+ var src = node && node.src;
5264
+ if (dom.isImg(node) && src.indexOf('data:') === 0) {
5265
+ var decodedData = atob(node.src.split(',')[1]);
4565
5266
  var array = new Uint8Array(decodedData.length);
4566
5267
  for (var i = 0; i < decodedData.length; i++) {
4567
5268
  array[i] = decodedData.charCodeAt(i);
@@ -4844,17 +5545,20 @@
4844
5545
  event.stopPropagation();
4845
5546
 
4846
5547
  var editableTop = $editable.offset().top - $document.scrollTop();
4847
-
4848
- $document.on('mousemove', function (event) {
5548
+ var onMouseMove = function (event) {
4849
5549
  var height = event.clientY - (editableTop + EDITABLE_PADDING);
4850
5550
 
4851
5551
  height = (options.minheight > 0) ? Math.max(height, options.minheight) : height;
4852
5552
  height = (options.maxHeight > 0) ? Math.min(height, options.maxHeight) : height;
4853
5553
 
4854
5554
  $editable.height(height);
4855
- }).one('mouseup', function () {
4856
- $document.off('mousemove');
4857
- });
5555
+ };
5556
+
5557
+ $document
5558
+ .on('mousemove', onMouseMove)
5559
+ .one('mouseup', function () {
5560
+ $document.off('mousemove', onMouseMove);
5561
+ });
4858
5562
  });
4859
5563
  };
4860
5564
 
@@ -4865,6 +5569,7 @@
4865
5569
  };
4866
5570
 
4867
5571
  var Fullscreen = function (context) {
5572
+ var self = this;
4868
5573
  var $editor = context.layoutInfo.editor;
4869
5574
  var $toolbar = context.layoutInfo.toolbar;
4870
5575
  var $editable = context.layoutInfo.editable;
@@ -4873,34 +5578,32 @@
4873
5578
  var $window = $(window);
4874
5579
  var $scrollbar = $('html, body');
4875
5580
 
5581
+ this.resizeTo = function (size) {
5582
+ $editable.css('height', size.h);
5583
+ $codable.css('height', size.h);
5584
+ if ($codable.data('cmeditor')) {
5585
+ $codable.data('cmeditor').setsize(null, size.h);
5586
+ }
5587
+ };
5588
+
5589
+ this.onResize = function () {
5590
+ self.resizeTo({
5591
+ h: $window.height() - $toolbar.outerHeight()
5592
+ });
5593
+ };
5594
+
4876
5595
  /**
4877
5596
  * toggle fullscreen
4878
5597
  */
4879
5598
  this.toggle = function () {
4880
- var resize = function (size) {
4881
- $editable.css('height', size.h);
4882
- $codable.css('height', size.h);
4883
- if ($codable.data('cmeditor')) {
4884
- $codable.data('cmeditor').setsize(null, size.h);
4885
- }
4886
- };
4887
-
4888
5599
  $editor.toggleClass('fullscreen');
4889
5600
  if (this.isFullscreen()) {
4890
5601
  $editable.data('orgHeight', $editable.css('height'));
4891
-
4892
- $window.on('resize', function () {
4893
- resize({
4894
- h: $window.height() - $toolbar.outerHeight()
4895
- });
4896
- }).trigger('resize');
4897
-
5602
+ $window.on('resize', this.onResize).trigger('resize');
4898
5603
  $scrollbar.css('overflow', 'hidden');
4899
5604
  } else {
4900
- $window.off('resize');
4901
- resize({
4902
- h: $editable.data('orgHeight')
4903
- });
5605
+ $window.off('resize', this.onResize);
5606
+ this.resizeTo({ h: $editable.data('orgHeight') });
4904
5607
  $scrollbar.css('overflow', 'visible');
4905
5608
  }
4906
5609
 
@@ -4927,6 +5630,12 @@
4927
5630
  },
4928
5631
  'summernote.keyup summernote.scroll summernote.change summernote.dialog.shown': function () {
4929
5632
  self.update();
5633
+ },
5634
+ 'summernote.disable': function () {
5635
+ self.hide();
5636
+ },
5637
+ 'summernote.codeview.toggled': function () {
5638
+ self.update();
4930
5639
  }
4931
5640
  };
4932
5641
 
@@ -4955,24 +5664,34 @@
4955
5664
  posStart = $target.offset(),
4956
5665
  scrollTop = $document.scrollTop();
4957
5666
 
4958
- $document.on('mousemove', function (event) {
5667
+ var onMouseMove = function (event) {
4959
5668
  context.invoke('editor.resizeTo', {
4960
5669
  x: event.clientX - posStart.left,
4961
5670
  y: event.clientY - (posStart.top - scrollTop)
4962
5671
  }, $target, !event.shiftKey);
4963
5672
 
4964
5673
  self.update($target[0]);
4965
- }).one('mouseup', function (e) {
4966
- e.preventDefault();
4967
- $document.off('mousemove');
4968
- context.invoke('editor.afterCommand');
4969
- });
5674
+ };
5675
+
5676
+ $document
5677
+ .on('mousemove', onMouseMove)
5678
+ .one('mouseup', function (e) {
5679
+ e.preventDefault();
5680
+ $document.off('mousemove', onMouseMove);
5681
+ context.invoke('editor.afterCommand');
5682
+ });
4970
5683
 
4971
5684
  if (!$target.data('ratio')) { // original ratio.
4972
5685
  $target.data('ratio', $target.height() / $target.width());
4973
5686
  }
4974
5687
  }
4975
5688
  });
5689
+
5690
+ // Listen for scrolling on the handle overlay.
5691
+ this.$handle.on('wheel', function (e) {
5692
+ e.preventDefault();
5693
+ self.update();
5694
+ });
4976
5695
  };
4977
5696
 
4978
5697
  this.destroy = function () {
@@ -4980,6 +5699,10 @@
4980
5699
  };
4981
5700
 
4982
5701
  this.update = function (target) {
5702
+ if (context.isDisabled()) {
5703
+ return false;
5704
+ }
5705
+
4983
5706
  var isImage = dom.isImg(target);
4984
5707
  var $selection = this.$handle.find('.note-control-selection');
4985
5708
 
@@ -4987,12 +5710,16 @@
4987
5710
 
4988
5711
  if (isImage) {
4989
5712
  var $image = $(target);
4990
- var pos = $image.position();
5713
+ var position = $image.position();
5714
+ var pos = {
5715
+ left: position.left + parseInt($image.css('marginLeft'), 10),
5716
+ top: position.top + parseInt($image.css('marginTop'), 10)
5717
+ };
4991
5718
 
4992
- // include margin
5719
+ // exclude margin
4993
5720
  var imageSize = {
4994
- w: $image.outerWidth(true),
4995
- h: $image.outerHeight(true)
5721
+ w: $image.outerWidth(false),
5722
+ h: $image.outerHeight(false)
4996
5723
  };
4997
5724
 
4998
5725
  $selection.css({
@@ -5121,6 +5848,8 @@
5121
5848
  this.$placeholder.on('click', function () {
5122
5849
  context.invoke('focus');
5123
5850
  }).text(options.placeholder).prependTo($editingArea);
5851
+
5852
+ this.update();
5124
5853
  };
5125
5854
 
5126
5855
  this.destroy = function () {
@@ -5148,7 +5877,7 @@
5148
5877
  if (!options.shortcuts || !shortcut) {
5149
5878
  return '';
5150
5879
  }
5151
-
5880
+
5152
5881
  if (agent.isMac) {
5153
5882
  shortcut = shortcut.replace('CMD', '⌘').replace('SHIFT', '⇧');
5154
5883
  }
@@ -5165,6 +5894,7 @@
5165
5894
  this.addToolbarButtons();
5166
5895
  this.addImagePopoverButtons();
5167
5896
  this.addLinkPopoverButtons();
5897
+ this.addTablePopoverButtons();
5168
5898
  this.fontInstalledMap = {};
5169
5899
  };
5170
5900
 
@@ -5186,7 +5916,7 @@
5186
5916
  return ui.buttonGroup([
5187
5917
  ui.button({
5188
5918
  className: 'dropdown-toggle',
5189
- contents: ui.icon(options.icons.magic) + ' ' + ui.icon(options.icons.caret, 'span'),
5919
+ contents: ui.dropdownButtonContents(ui.icon(options.icons.magic), options),
5190
5920
  tooltip: lang.style.style,
5191
5921
  data: {
5192
5922
  toggle: 'dropdown'
@@ -5218,7 +5948,7 @@
5218
5948
  className: 'note-btn-bold',
5219
5949
  contents: ui.icon(options.icons.bold),
5220
5950
  tooltip: lang.font.bold + representShortcut('bold'),
5221
- click: context.createInvokeHandler('editor.bold')
5951
+ click: context.createInvokeHandlerAndUpdateState('editor.bold')
5222
5952
  }).render();
5223
5953
  });
5224
5954
 
@@ -5227,7 +5957,7 @@
5227
5957
  className: 'note-btn-italic',
5228
5958
  contents: ui.icon(options.icons.italic),
5229
5959
  tooltip: lang.font.italic + representShortcut('italic'),
5230
- click: context.createInvokeHandler('editor.italic')
5960
+ click: context.createInvokeHandlerAndUpdateState('editor.italic')
5231
5961
  }).render();
5232
5962
  });
5233
5963
 
@@ -5236,7 +5966,7 @@
5236
5966
  className: 'note-btn-underline',
5237
5967
  contents: ui.icon(options.icons.underline),
5238
5968
  tooltip: lang.font.underline + representShortcut('underline'),
5239
- click: context.createInvokeHandler('editor.underline')
5969
+ click: context.createInvokeHandlerAndUpdateState('editor.underline')
5240
5970
  }).render();
5241
5971
  });
5242
5972
 
@@ -5253,7 +5983,7 @@
5253
5983
  className: 'note-btn-strikethrough',
5254
5984
  contents: ui.icon(options.icons.strikethrough),
5255
5985
  tooltip: lang.font.strikethrough + representShortcut('strikethrough'),
5256
- click: context.createInvokeHandler('editor.strikethrough')
5986
+ click: context.createInvokeHandlerAndUpdateState('editor.strikethrough')
5257
5987
  }).render();
5258
5988
  });
5259
5989
 
@@ -5262,7 +5992,7 @@
5262
5992
  className: 'note-btn-superscript',
5263
5993
  contents: ui.icon(options.icons.superscript),
5264
5994
  tooltip: lang.font.superscript,
5265
- click: context.createInvokeHandler('editor.superscript')
5995
+ click: context.createInvokeHandlerAndUpdateState('editor.superscript')
5266
5996
  }).render();
5267
5997
  });
5268
5998
 
@@ -5271,7 +6001,7 @@
5271
6001
  className: 'note-btn-subscript',
5272
6002
  contents: ui.icon(options.icons.subscript),
5273
6003
  tooltip: lang.font.subscript,
5274
- click: context.createInvokeHandler('editor.subscript')
6004
+ click: context.createInvokeHandlerAndUpdateState('editor.subscript')
5275
6005
  }).render();
5276
6006
  });
5277
6007
 
@@ -5279,7 +6009,7 @@
5279
6009
  return ui.buttonGroup([
5280
6010
  ui.button({
5281
6011
  className: 'dropdown-toggle',
5282
- contents: '<span class="note-current-fontname"/> ' + ui.icon(options.icons.caret, 'span'),
6012
+ contents: ui.dropdownButtonContents('<span class="note-current-fontname"/>', options),
5283
6013
  tooltip: lang.font.name,
5284
6014
  data: {
5285
6015
  toggle: 'dropdown'
@@ -5292,7 +6022,7 @@
5292
6022
  template: function (item) {
5293
6023
  return '<span style="font-family:' + item + '">' + item + '</span>';
5294
6024
  },
5295
- click: context.createInvokeHandler('editor.fontName')
6025
+ click: context.createInvokeHandlerAndUpdateState('editor.fontName')
5296
6026
  })
5297
6027
  ]).render();
5298
6028
  });
@@ -5301,7 +6031,7 @@
5301
6031
  return ui.buttonGroup([
5302
6032
  ui.button({
5303
6033
  className: 'dropdown-toggle',
5304
- contents: '<span class="note-current-fontsize"/>' + ui.icon(options.icons.caret, 'span'),
6034
+ contents: ui.dropdownButtonContents('<span class="note-current-fontsize"/>', options),
5305
6035
  tooltip: lang.font.size,
5306
6036
  data: {
5307
6037
  toggle: 'dropdown'
@@ -5311,7 +6041,7 @@
5311
6041
  className: 'dropdown-fontsize',
5312
6042
  checkClassName: options.icons.menuCheck,
5313
6043
  items: options.fontSizes,
5314
- click: context.createInvokeHandler('editor.fontSize')
6044
+ click: context.createInvokeHandlerAndUpdateState('editor.fontSize')
5315
6045
  })
5316
6046
  ]).render();
5317
6047
  });
@@ -5339,7 +6069,7 @@
5339
6069
  }),
5340
6070
  ui.button({
5341
6071
  className: 'dropdown-toggle',
5342
- contents: ui.icon(options.icons.caret, 'span'),
6072
+ contents: ui.dropdownButtonContents('', options),
5343
6073
  tooltip: lang.color.more,
5344
6074
  data: {
5345
6075
  toggle: 'dropdown'
@@ -5347,33 +6077,32 @@
5347
6077
  }),
5348
6078
  ui.dropdown({
5349
6079
  items: [
5350
- '<li>',
5351
- '<div class="btn-group">',
6080
+ '<div class="note-palette">',
5352
6081
  ' <div class="note-palette-title">' + lang.color.background + '</div>',
5353
6082
  ' <div>',
5354
- ' <button type="button" class="note-color-reset btn btn-default" data-event="backColor" data-value="inherit">',
6083
+ ' <button type="button" class="note-color-reset btn btn-light" data-event="backColor" data-value="inherit">',
5355
6084
  lang.color.transparent,
5356
6085
  ' </button>',
5357
6086
  ' </div>',
5358
6087
  ' <div class="note-holder" data-event="backColor"/>',
5359
6088
  '</div>',
5360
- '<div class="btn-group">',
6089
+ '<div class="note-palette">',
5361
6090
  ' <div class="note-palette-title">' + lang.color.foreground + '</div>',
5362
6091
  ' <div>',
5363
- ' <button type="button" class="note-color-reset btn btn-default" data-event="removeFormat" data-value="foreColor">',
6092
+ ' <button type="button" class="note-color-reset btn btn-light" data-event="removeFormat" data-value="foreColor">',
5364
6093
  lang.color.resetToDefault,
5365
6094
  ' </button>',
5366
6095
  ' </div>',
5367
6096
  ' <div class="note-holder" data-event="foreColor"/>',
5368
- '</div>',
5369
- '</li>'
6097
+ '</div>'
5370
6098
  ].join(''),
5371
6099
  callback: function ($dropdown) {
5372
6100
  $dropdown.find('.note-holder').each(function () {
5373
6101
  var $holder = $(this);
5374
6102
  $holder.append(ui.palette({
5375
6103
  colors: options.colors,
5376
- eventName: $holder.data('event')
6104
+ eventName: $holder.data('event'),
6105
+ tooltip: options.tooltip
5377
6106
  }).render());
5378
6107
  });
5379
6108
  },
@@ -5460,7 +6189,7 @@
5460
6189
  return ui.buttonGroup([
5461
6190
  ui.button({
5462
6191
  className: 'dropdown-toggle',
5463
- contents: ui.icon(options.icons.alignLeft) + ' ' + ui.icon(options.icons.caret, 'span'),
6192
+ contents: ui.dropdownButtonContents(ui.icon(options.icons.alignLeft), options),
5464
6193
  tooltip: lang.paragraph.paragraph,
5465
6194
  data: {
5466
6195
  toggle: 'dropdown'
@@ -5483,7 +6212,7 @@
5483
6212
  return ui.buttonGroup([
5484
6213
  ui.button({
5485
6214
  className: 'dropdown-toggle',
5486
- contents: ui.icon(options.icons.textHeight) + ' ' + ui.icon(options.icons.caret, 'span'),
6215
+ contents: ui.dropdownButtonContents(ui.icon(options.icons.textHeight), options),
5487
6216
  tooltip: lang.font.height,
5488
6217
  data: {
5489
6218
  toggle: 'dropdown'
@@ -5502,7 +6231,7 @@
5502
6231
  return ui.buttonGroup([
5503
6232
  ui.button({
5504
6233
  className: 'dropdown-toggle',
5505
- contents: ui.icon(options.icons.table) + ' ' + ui.icon(options.icons.caret, 'span'),
6234
+ contents: ui.dropdownButtonContents(ui.icon(options.icons.table), options),
5506
6235
  tooltip: lang.table.table,
5507
6236
  data: {
5508
6237
  toggle: 'dropdown'
@@ -5690,6 +6419,71 @@
5690
6419
  });
5691
6420
  };
5692
6421
 
6422
+ /**
6423
+ * table : [
6424
+ * ['add', ['addRowDown', 'addRowUp', 'addColLeft', 'addColRight']],
6425
+ * ['delete', ['deleteRow', 'deleteCol', 'deleteTable']]
6426
+ * ],
6427
+ */
6428
+ this.addTablePopoverButtons = function () {
6429
+ context.memo('button.addRowUp', function () {
6430
+ return ui.button({
6431
+ className: 'btn-md',
6432
+ contents: ui.icon(options.icons.rowAbove),
6433
+ tooltip: lang.table.addRowAbove,
6434
+ click: context.createInvokeHandler('editor.addRow', 'top')
6435
+ }).render();
6436
+ });
6437
+ context.memo('button.addRowDown', function () {
6438
+ return ui.button({
6439
+ className: 'btn-md',
6440
+ contents: ui.icon(options.icons.rowBelow),
6441
+ tooltip: lang.table.addRowBelow,
6442
+ click: context.createInvokeHandler('editor.addRow', 'bottom')
6443
+ }).render();
6444
+ });
6445
+ context.memo('button.addColLeft', function () {
6446
+ return ui.button({
6447
+ className: 'btn-md',
6448
+ contents: ui.icon(options.icons.colBefore),
6449
+ tooltip: lang.table.addColLeft,
6450
+ click: context.createInvokeHandler('editor.addCol', 'left')
6451
+ }).render();
6452
+ });
6453
+ context.memo('button.addColRight', function () {
6454
+ return ui.button({
6455
+ className: 'btn-md',
6456
+ contents: ui.icon(options.icons.colAfter),
6457
+ tooltip: lang.table.addColRight,
6458
+ click: context.createInvokeHandler('editor.addCol', 'right')
6459
+ }).render();
6460
+ });
6461
+ context.memo('button.deleteRow', function () {
6462
+ return ui.button({
6463
+ className: 'btn-md',
6464
+ contents: ui.icon(options.icons.rowRemove),
6465
+ tooltip: lang.table.delRow,
6466
+ click: context.createInvokeHandler('editor.deleteRow')
6467
+ }).render();
6468
+ });
6469
+ context.memo('button.deleteCol', function () {
6470
+ return ui.button({
6471
+ className: 'btn-md',
6472
+ contents: ui.icon(options.icons.colRemove),
6473
+ tooltip: lang.table.delCol,
6474
+ click: context.createInvokeHandler('editor.deleteCol')
6475
+ }).render();
6476
+ });
6477
+ context.memo('button.deleteTable', function () {
6478
+ return ui.button({
6479
+ className: 'btn-md',
6480
+ contents: ui.icon(options.icons.trash),
6481
+ tooltip: lang.table.delTable,
6482
+ click: context.createInvokeHandler('editor.deleteTable')
6483
+ }).render();
6484
+ });
6485
+ };
6486
+
5693
6487
  this.build = function ($container, groups) {
5694
6488
  for (var groupIdx = 0, groupLen = groups.length; groupIdx < groupLen; groupIdx++) {
5695
6489
  var group = groups[groupIdx];
@@ -5710,9 +6504,14 @@
5710
6504
  }
5711
6505
  };
5712
6506
 
5713
- this.updateCurrentStyle = function () {
6507
+ /**
6508
+ * @param {jQuery} [$container]
6509
+ */
6510
+ this.updateCurrentStyle = function ($container) {
6511
+ var $cont = $container || $toolbar;
6512
+
5714
6513
  var styleInfo = context.invoke('editor.currentStyle');
5715
- this.updateBtnStates({
6514
+ this.updateBtnStates($cont, {
5716
6515
  '.note-btn-bold': function () {
5717
6516
  return styleInfo['font-bold'] === 'bold';
5718
6517
  },
@@ -5741,27 +6540,29 @@
5741
6540
  });
5742
6541
  var fontName = list.find(fontNames, self.isFontInstalled);
5743
6542
 
5744
- $toolbar.find('.dropdown-fontname li a').each(function () {
6543
+ $cont.find('.dropdown-fontname a').each(function () {
6544
+ var $item = $(this);
5745
6545
  // always compare string to avoid creating another func.
5746
- var isChecked = ($(this).data('value') + '') === (fontName + '');
5747
- this.className = isChecked ? 'checked' : '';
6546
+ var isChecked = ($item.data('value') + '') === (fontName + '');
6547
+ $item.toggleClass('checked', isChecked);
5748
6548
  });
5749
- $toolbar.find('.note-current-fontname').text(fontName);
6549
+ $cont.find('.note-current-fontname').text(fontName);
5750
6550
  }
5751
6551
 
5752
6552
  if (styleInfo['font-size']) {
5753
6553
  var fontSize = styleInfo['font-size'];
5754
- $toolbar.find('.dropdown-fontsize li a').each(function () {
6554
+ $cont.find('.dropdown-fontsize a').each(function () {
6555
+ var $item = $(this);
5755
6556
  // always compare with string to avoid creating another func.
5756
- var isChecked = ($(this).data('value') + '') === (fontSize + '');
5757
- this.className = isChecked ? 'checked' : '';
6557
+ var isChecked = ($item.data('value') + '') === (fontSize + '');
6558
+ $item.toggleClass('checked', isChecked);
5758
6559
  });
5759
- $toolbar.find('.note-current-fontsize').text(fontSize);
6560
+ $cont.find('.note-current-fontsize').text(fontSize);
5760
6561
  }
5761
6562
 
5762
6563
  if (styleInfo['line-height']) {
5763
6564
  var lineHeight = styleInfo['line-height'];
5764
- $toolbar.find('.dropdown-line-height li a').each(function () {
6565
+ $cont.find('.dropdown-line-height li a').each(function () {
5765
6566
  // always compare with string to avoid creating another func.
5766
6567
  var isChecked = ($(this).data('value') + '') === (lineHeight + '');
5767
6568
  this.className = isChecked ? 'checked' : '';
@@ -5769,9 +6570,9 @@
5769
6570
  }
5770
6571
  };
5771
6572
 
5772
- this.updateBtnStates = function (infos) {
6573
+ this.updateBtnStates = function ($container, infos) {
5773
6574
  $.each(infos, function (selector, pred) {
5774
- ui.toggleBtnActive($toolbar.find(selector), pred());
6575
+ ui.toggleBtnActive($container.find(selector), pred());
5775
6576
  });
5776
6577
  };
5777
6578
 
@@ -5822,6 +6623,7 @@
5822
6623
  var ui = $.summernote.ui;
5823
6624
 
5824
6625
  var $note = context.layoutInfo.note;
6626
+ var $editor = context.layoutInfo.editor;
5825
6627
  var $toolbar = context.layoutInfo.toolbar;
5826
6628
  var options = context.options;
5827
6629
 
@@ -5842,6 +6644,8 @@
5842
6644
  $toolbar.appendTo(options.toolbarContainer);
5843
6645
  }
5844
6646
 
6647
+ this.changeContainer(false);
6648
+
5845
6649
  $note.on('summernote.keyup summernote.mouseup summernote.change', function () {
5846
6650
  context.invoke('buttons.updateCurrentStyle');
5847
6651
  });
@@ -5853,8 +6657,20 @@
5853
6657
  $toolbar.children().remove();
5854
6658
  };
5855
6659
 
6660
+ this.changeContainer = function (isFullscreen) {
6661
+ if (isFullscreen) {
6662
+ $toolbar.prependTo($editor);
6663
+ } else {
6664
+ if (options.toolbarContainer) {
6665
+ $toolbar.appendTo(options.toolbarContainer);
6666
+ }
6667
+ }
6668
+ };
6669
+
5856
6670
  this.updateFullscreen = function (isFullscreen) {
5857
6671
  ui.toggleBtnActive($toolbar.find('.btn-fullscreen'), isFullscreen);
6672
+
6673
+ this.changeContainer(isFullscreen);
5858
6674
  };
5859
6675
 
5860
6676
  this.updateCodeview = function (isCodeview) {
@@ -5894,20 +6710,22 @@
5894
6710
  this.initialize = function () {
5895
6711
  var $container = options.dialogsInBody ? $(document.body) : $editor;
5896
6712
 
5897
- var body = '<div class="form-group">' +
5898
- '<label>' + lang.link.textToDisplay + '</label>' +
5899
- '<input class="note-link-text form-control" type="text" />' +
6713
+ var body = '<div class="form-group note-form-group">' +
6714
+ '<label class="note-form-label">' + lang.link.textToDisplay + '</label>' +
6715
+ '<input class="note-link-text form-control '+
6716
+ ' note-form-control note-input" type="text" />' +
5900
6717
  '</div>' +
5901
- '<div class="form-group">' +
5902
- '<label>' + lang.link.url + '</label>' +
5903
- '<input class="note-link-url form-control" type="text" value="http://" />' +
6718
+ '<div class="form-group note-form-group">' +
6719
+ '<label class="note-form-label">' + lang.link.url + '</label>' +
6720
+ '<input class="note-link-url form-control note-form-control ' +
6721
+ 'note-input" type="text" value="http://" />' +
5904
6722
  '</div>' +
5905
- (!options.disableLinkTarget ?
5906
- '<div class="checkbox">' +
5907
- '<label>' + '<input type="checkbox" checked> ' + lang.link.openInNewWindow + '</label>' +
5908
- '</div>' : ''
5909
- );
5910
- var footer = '<button href="#" class="btn btn-primary note-link-btn disabled" disabled>' + lang.link.insert + '</button>';
6723
+ (!options.disableLinkTarget ?
6724
+ $('<div/>').append(ui.checkbox({ id: 'sn-checkbox-open-in-new-window', text: lang.link.openInNewWindow, checked: true }).render())
6725
+ .html()
6726
+ : '');
6727
+ var footer = '<button href="#" class="btn btn-primary note-btn note-btn-primary ' +
6728
+ 'note-link-btn disabled" disabled>' + lang.link.insert + '</button>';
5911
6729
 
5912
6730
  this.$dialog = ui.dialog({
5913
6731
  className: 'link-dialog',
@@ -5989,7 +6807,10 @@
5989
6807
  self.bindEnterKey($linkUrl, $linkBtn);
5990
6808
  self.bindEnterKey($linkText, $linkBtn);
5991
6809
 
5992
- $openInNewWindow.prop('checked', linkInfo.isNewWindow);
6810
+ var isChecked = linkInfo.isNewWindow !== undefined ?
6811
+ linkInfo.isNewWindow : context.options.linkTargetBlank;
6812
+
6813
+ $openInNewWindow.prop('checked', isChecked);
5993
6814
 
5994
6815
  $linkBtn.one('click', function (event) {
5995
6816
  event.preventDefault();
@@ -6000,7 +6821,7 @@
6000
6821
  text: $linkText.val(),
6001
6822
  isNewWindow: $openInNewWindow.is(':checked')
6002
6823
  });
6003
- self.$dialog.modal('hide');
6824
+ ui.hideDialog(self.$dialog);
6004
6825
  });
6005
6826
  });
6006
6827
 
@@ -6046,7 +6867,7 @@
6046
6867
  'summernote.keyup summernote.mouseup summernote.change summernote.scroll': function () {
6047
6868
  self.update();
6048
6869
  },
6049
- 'summernote.dialog.shown': function () {
6870
+ 'summernote.disable summernote.dialog.shown': function () {
6050
6871
  self.hide();
6051
6872
  }
6052
6873
  };
@@ -6059,11 +6880,11 @@
6059
6880
  this.$popover = ui.popover({
6060
6881
  className: 'note-link-popover',
6061
6882
  callback: function ($node) {
6062
- var $content = $node.find('.popover-content');
6883
+ var $content = $node.find('.popover-content,.note-popover-content');
6063
6884
  $content.prepend('<span><a target="_blank"></a>&nbsp;</span>');
6064
6885
  }
6065
6886
  }).render().appendTo('body');
6066
- var $content = this.$popover.find('.popover-content');
6887
+ var $content = this.$popover.find('.popover-content,.note-popover-content');
6067
6888
 
6068
6889
  context.invoke('buttons.build', $content, options.popover.link);
6069
6890
  };
@@ -6120,16 +6941,19 @@
6120
6941
  imageLimitation = '<small>' + lang.image.maximumFileSize + ' : ' + readableSize + '</small>';
6121
6942
  }
6122
6943
 
6123
- var body = '<div class="form-group note-group-select-from-files">' +
6124
- '<label>' + lang.image.selectFromFiles + '</label>' +
6125
- '<input class="note-image-input form-control" type="file" name="files" accept="image/*" multiple="multiple" />' +
6944
+ var body = '<div class="form-group note-form-group note-group-select-from-files">' +
6945
+ '<label class="note-form-label">' + lang.image.selectFromFiles + '</label>' +
6946
+ '<input class="note-image-input form-control note-form-control note-input" '+
6947
+ ' type="file" name="files" accept="image/*" multiple="multiple" />' +
6126
6948
  imageLimitation +
6127
- '</div>' +
6949
+ '</div>' +
6128
6950
  '<div class="form-group note-group-image-url" style="overflow:auto;">' +
6129
- '<label>' + lang.image.url + '</label>' +
6130
- '<input class="note-image-url form-control col-md-12" type="text" />' +
6951
+ '<label class="note-form-label">' + lang.image.url + '</label>' +
6952
+ '<input class="note-image-url form-control note-form-control note-input ' +
6953
+ ' col-md-12" type="text" />' +
6131
6954
  '</div>';
6132
- var footer = '<button href="#" class="btn btn-primary note-image-btn disabled" disabled>' + lang.image.insert + '</button>';
6955
+ var footer = '<button href="#" class="btn btn-primary note-btn note-btn-primary ' +
6956
+ 'note-image-btn disabled" disabled>' + lang.image.insert + '</button>';
6133
6957
 
6134
6958
  this.$dialog = ui.dialog({
6135
6959
  title: lang.image.insert,
@@ -6220,11 +7044,26 @@
6220
7044
  };
6221
7045
  };
6222
7046
 
7047
+
7048
+ /**
7049
+ * Image popover module
7050
+ * mouse events that show/hide popover will be handled by Handle.js.
7051
+ * Handle.js will receive the events and invoke 'imagePopover.update'.
7052
+ */
6223
7053
  var ImagePopover = function (context) {
7054
+ var self = this;
6224
7055
  var ui = $.summernote.ui;
6225
7056
 
7057
+ var $editable = context.layoutInfo.editable;
7058
+ var editable = $editable[0];
6226
7059
  var options = context.options;
6227
7060
 
7061
+ this.events = {
7062
+ 'summernote.disable': function () {
7063
+ self.hide();
7064
+ }
7065
+ };
7066
+
6228
7067
  this.shouldInitialize = function () {
6229
7068
  return !list.isEmpty(options.popover.image);
6230
7069
  };
@@ -6233,7 +7072,7 @@
6233
7072
  this.$popover = ui.popover({
6234
7073
  className: 'note-image-popover'
6235
7074
  }).render().appendTo('body');
6236
- var $content = this.$popover.find('.popover-content');
7075
+ var $content = this.$popover.find('.popover-content,.note-popover-content');
6237
7076
 
6238
7077
  context.invoke('buttons.build', $content, options.popover.image);
6239
7078
  };
@@ -6244,6 +7083,72 @@
6244
7083
 
6245
7084
  this.update = function (target) {
6246
7085
  if (dom.isImg(target)) {
7086
+ var pos = dom.posFromPlaceholder(target);
7087
+ var posEditor = dom.posFromPlaceholder(editable);
7088
+
7089
+ this.$popover.css({
7090
+ display: 'block',
7091
+ left: pos.left,
7092
+ top: Math.min(pos.top, posEditor.top)
7093
+ });
7094
+ } else {
7095
+ this.hide();
7096
+ }
7097
+ };
7098
+
7099
+ this.hide = function () {
7100
+ this.$popover.hide();
7101
+ };
7102
+ };
7103
+
7104
+ var TablePopover = function (context) {
7105
+ var self = this;
7106
+ var ui = $.summernote.ui;
7107
+
7108
+ var options = context.options;
7109
+
7110
+ this.events = {
7111
+ 'summernote.mousedown': function (we, e) {
7112
+ self.update(e.target);
7113
+ },
7114
+ 'summernote.keyup summernote.scroll summernote.change': function () {
7115
+ self.update();
7116
+ },
7117
+ 'summernote.disable': function () {
7118
+ self.hide();
7119
+ }
7120
+ };
7121
+
7122
+ this.shouldInitialize = function () {
7123
+ return !list.isEmpty(options.popover.table);
7124
+ };
7125
+
7126
+ this.initialize = function () {
7127
+ this.$popover = ui.popover({
7128
+ className: 'note-table-popover'
7129
+ }).render().appendTo('body');
7130
+ var $content = this.$popover.find('.popover-content,.note-popover-content');
7131
+
7132
+ context.invoke('buttons.build', $content, options.popover.table);
7133
+
7134
+ // [workaround] Disable Firefox's default table editor
7135
+ if (agent.isFF) {
7136
+ document.execCommand('enableInlineTableEditing', false, false);
7137
+ }
7138
+ };
7139
+
7140
+ this.destroy = function () {
7141
+ this.$popover.remove();
7142
+ };
7143
+
7144
+ this.update = function (target) {
7145
+ if (context.isDisabled()) {
7146
+ return false;
7147
+ }
7148
+
7149
+ var isCell = dom.isCell(target);
7150
+
7151
+ if (isCell) {
6247
7152
  var pos = dom.posFromPlaceholder(target);
6248
7153
  this.$popover.css({
6249
7154
  display: 'block',
@@ -6253,6 +7158,8 @@
6253
7158
  } else {
6254
7159
  this.hide();
6255
7160
  }
7161
+
7162
+ return isCell;
6256
7163
  };
6257
7164
 
6258
7165
  this.hide = function () {
@@ -6271,11 +7178,13 @@
6271
7178
  this.initialize = function () {
6272
7179
  var $container = options.dialogsInBody ? $(document.body) : $editor;
6273
7180
 
6274
- var body = '<div class="form-group row-fluid">' +
6275
- '<label>' + lang.video.url + ' <small class="text-muted">' + lang.video.providers + '</small></label>' +
6276
- '<input class="note-video-url form-control span12" type="text" />' +
7181
+ var body = '<div class="form-group note-form-group row-fluid">' +
7182
+ '<label class="note-form-label">' + lang.video.url + ' <small class="text-muted">' + lang.video.providers + '</small></label>' +
7183
+ '<input class="note-video-url form-control note-form-control note-input span12" ' +
7184
+ ' type="text" />' +
6277
7185
  '</div>';
6278
- var footer = '<button href="#" class="btn btn-primary note-video-btn disabled" disabled>' + lang.video.insert + '</button>';
7186
+ var footer = '<button href="#" class="btn btn-primary note-btn note-btn-primary ' +
7187
+ ' note-video-btn disabled" disabled>' + lang.video.insert + '</button>';
6279
7188
 
6280
7189
  this.$dialog = ui.dialog({
6281
7190
  title: lang.video.insert,
@@ -6309,7 +7218,7 @@
6309
7218
  var vRegExp = /\/\/vine\.co\/v\/([a-zA-Z0-9]+)/;
6310
7219
  var vMatch = url.match(vRegExp);
6311
7220
 
6312
- var vimRegExp = /\/\/(player\.)?vimeo\.com\/([a-z]*\/)*([0-9]{6,11})[?]?.*/;
7221
+ var vimRegExp = /\/\/(player\.)?vimeo\.com\/([a-z]*\/)*(\d+)[?]?.*/;
6313
7222
  var vimMatch = url.match(vimRegExp);
6314
7223
 
6315
7224
  var dmRegExp = /.+dailymotion.com\/(video|hub)\/([^_]+)[^#]*(#video=([^_&]+))?/;
@@ -6318,6 +7227,12 @@
6318
7227
  var youkuRegExp = /\/\/v\.youku\.com\/v_show\/id_(\w+)=*\.html/;
6319
7228
  var youkuMatch = url.match(youkuRegExp);
6320
7229
 
7230
+ var qqRegExp = /\/\/v\.qq\.com.*?vid=(.+)/;
7231
+ var qqMatch = url.match(qqRegExp);
7232
+
7233
+ var qqRegExp2 = /\/\/v\.qq\.com\/x?\/?(page|cover).*?\/([^\/]+)\.html\??.*/;
7234
+ var qqMatch2 = url.match(qqRegExp2);
7235
+
6321
7236
  var mp4RegExp = /^.+.(mp4|m4v)$/;
6322
7237
  var mp4Match = url.match(mp4RegExp);
6323
7238
 
@@ -6363,6 +7278,13 @@
6363
7278
  .attr('height', '498')
6364
7279
  .attr('width', '510')
6365
7280
  .attr('src', '//player.youku.com/embed/' + youkuMatch[1]);
7281
+ } else if ((qqMatch && qqMatch[1].length) || (qqMatch2 && qqMatch2[2].length)) {
7282
+ var vid = ((qqMatch && qqMatch[1].length) ? qqMatch[1]:qqMatch2[2]);
7283
+ $video = $('<iframe webkitallowfullscreen mozallowfullscreen allowfullscreen>')
7284
+ .attr('frameborder', 0)
7285
+ .attr('height', '310')
7286
+ .attr('width', '500')
7287
+ .attr('src', 'http://v.qq.com/iframe/player.html?vid=' + vid + '&amp;auto=0');
6366
7288
  } else if (mp4Match || oggMatch || webmMatch) {
6367
7289
  $video = $('<video controls>')
6368
7290
  .attr('src', url)
@@ -6464,7 +7386,7 @@
6464
7386
 
6465
7387
  var body = [
6466
7388
  '<p class="text-center">',
6467
- '<a href="http://summernote.org/" target="_blank">Summernote 0.8.3</a> · ',
7389
+ '<a href="http://summernote.org/" target="_blank">Summernote 0.8.8</a> · ',
6468
7390
  '<a href="https://github.com/summernote/summernote" target="_blank">Project</a> · ',
6469
7391
  '<a href="https://github.com/summernote/summernote/issues" target="_blank">Issues</a>',
6470
7392
  '</p>'
@@ -6476,7 +7398,7 @@
6476
7398
  body: this.createShortCutList(),
6477
7399
  footer: body,
6478
7400
  callback: function ($node) {
6479
- $node.find('.modal-body').css({
7401
+ $node.find('.modal-body,.note-modal-body').css({
6480
7402
  'max-height': 300,
6481
7403
  'overflow': 'scroll'
6482
7404
  });
@@ -6524,7 +7446,7 @@
6524
7446
  'summernote.keyup summernote.mouseup summernote.scroll': function () {
6525
7447
  self.update();
6526
7448
  },
6527
- 'summernote.change summernote.dialog.shown': function () {
7449
+ 'summernote.disable summernote.change summernote.dialog.shown': function () {
6528
7450
  self.hide();
6529
7451
  },
6530
7452
  'summernote.focusout': function (we, e) {
@@ -6568,6 +7490,7 @@
6568
7490
  left: Math.max(bnd.left + bnd.width / 2, 0) - AIR_MODE_POPOVER_X_OFFSET,
6569
7491
  top: bnd.top + bnd.height
6570
7492
  });
7493
+ context.invoke('buttons.updateCurrentStyle', this.$popover);
6571
7494
  }
6572
7495
  } else {
6573
7496
  this.hide();
@@ -6597,7 +7520,7 @@
6597
7520
  'summernote.keydown': function (we, e) {
6598
7521
  self.handleKeydown(e);
6599
7522
  },
6600
- 'summernote.dialog.shown': function () {
7523
+ 'summernote.disable summernote.dialog.shown': function () {
6601
7524
  self.hide();
6602
7525
  }
6603
7526
  };
@@ -6616,7 +7539,7 @@
6616
7539
 
6617
7540
  this.$popover.hide();
6618
7541
 
6619
- this.$content = this.$popover.find('.popover-content');
7542
+ this.$content = this.$popover.find('.popover-content,.note-popover-content');
6620
7543
 
6621
7544
  this.$content.on('click', '.note-hint-item', function () {
6622
7545
  self.$content.find('.active').removeClass('active');
@@ -6675,11 +7598,13 @@
6675
7598
 
6676
7599
  if ($item.length) {
6677
7600
  var node = this.nodeFromItem($item);
7601
+ // XXX: consider to move codes to editor for recording redo/undo.
6678
7602
  this.lastWordRange.insertNode(node);
6679
7603
  range.createFromNode(node).collapse().select();
6680
7604
 
6681
7605
  this.lastWordRange = null;
6682
7606
  this.hide();
7607
+ context.triggerEvent('change', context.layoutInfo.editable.html(), context.layoutInfo.editable);
6683
7608
  context.invoke('editor.focus');
6684
7609
  }
6685
7610
 
@@ -6809,7 +7734,7 @@
6809
7734
 
6810
7735
 
6811
7736
  $.summernote = $.extend($.summernote, {
6812
- version: '0.8.3',
7737
+ version: '0.8.8',
6813
7738
  ui: ui,
6814
7739
  dom: dom,
6815
7740
 
@@ -6836,13 +7761,14 @@
6836
7761
  'linkPopover': LinkPopover,
6837
7762
  'imageDialog': ImageDialog,
6838
7763
  'imagePopover': ImagePopover,
7764
+ 'tablePopover': TablePopover,
6839
7765
  'videoDialog': VideoDialog,
6840
7766
  'helpDialog': HelpDialog,
6841
7767
  'airPopover': AirPopover
6842
7768
  },
6843
7769
 
6844
7770
  buttons: {},
6845
-
7771
+
6846
7772
  lang: 'en-US',
6847
7773
 
6848
7774
  // toolbar
@@ -6867,6 +7793,10 @@
6867
7793
  link: [
6868
7794
  ['link', ['linkDialogShow', 'unlink']]
6869
7795
  ],
7796
+ table: [
7797
+ ['add', ['addRowDown', 'addRowUp', 'addColLeft', 'addColRight']],
7798
+ ['delete', ['deleteRow', 'deleteCol', 'deleteTable']]
7799
+ ],
6870
7800
  air: [
6871
7801
  ['color', ['color']],
6872
7802
  ['font', ['bold', 'underline', 'clear']],
@@ -6881,6 +7811,7 @@
6881
7811
 
6882
7812
  width: null,
6883
7813
  height: null,
7814
+ linkTargetBlank: true,
6884
7815
 
6885
7816
  focus: false,
6886
7817
  tabSize: 4,
@@ -6888,6 +7819,7 @@
6888
7819
  shortcuts: true,
6889
7820
  textareaAutoSync: true,
6890
7821
  direction: null,
7822
+ tooltip: 'auto',
6891
7823
 
6892
7824
  styleTags: ['p', 'blockquote', 'pre', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6'],
6893
7825
 
@@ -7009,6 +7941,12 @@
7009
7941
  'alignJustify': 'note-icon-align-justify',
7010
7942
  'alignLeft': 'note-icon-align-left',
7011
7943
  'alignRight': 'note-icon-align-right',
7944
+ 'rowBelow': 'note-icon-row-below',
7945
+ 'colBefore': 'note-icon-col-before',
7946
+ 'colAfter': 'note-icon-col-after',
7947
+ 'rowAbove': 'note-icon-row-above',
7948
+ 'rowRemove': 'note-icon-row-remove',
7949
+ 'colRemove': 'note-icon-col-remove',
7012
7950
  'indent': 'note-icon-align-indent',
7013
7951
  'outdent': 'note-icon-align-outdent',
7014
7952
  'arrowsAlt': 'note-icon-arrows-alt',
@@ -7024,7 +7962,7 @@
7024
7962
  'link': 'note-icon-link',
7025
7963
  'unlink': 'note-icon-chain-broken',
7026
7964
  'magic': 'note-icon-magic',
7027
- 'menuCheck': 'note-icon-check',
7965
+ 'menuCheck': 'note-icon-menu-check',
7028
7966
  'minus': 'note-icon-minus',
7029
7967
  'orderedlist': 'note-icon-orderedlist',
7030
7968
  'pencil': 'note-icon-pencil',