summernote-rails 0.8.3.0 → 0.8.8.0

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.
@@ -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',