diff --git a/apps/editor/src/js/wwTableManager.js b/apps/editor/src/js/wwTableManager.js index 27c9934d73..75cdfb9131 100644 --- a/apps/editor/src/js/wwTableManager.js +++ b/apps/editor/src/js/wwTableManager.js @@ -10,6 +10,8 @@ const isIE10 = tui.util.browser.msie && tui.util.browser.version === 10; const TABLE_COMPLETION_DELAY = 10; const SET_SELECTION_DELAY = 50; const TABLE_CLASS_PREFIX = 'te-content-table-'; +const isIE10And11 = tui.util.browser.msie + && (tui.util.browser.version === 10 || tui.util.browser.version === 11); /** * WwTableManager @@ -98,6 +100,7 @@ class WwTableManager { if (isRangeInTable && !this._isSingleModifierKey(keymap)) { this._recordUndoStateIfNeed(range); + this._removeBRIfNeed(range); this._removeContentsAndChangeSelectionIfNeed(range, keymap, ev); } else if (!isRangeInTable && this._lastCellNode) { this._recordUndoStateAndResetCellNode(range); @@ -211,6 +214,7 @@ class WwTableManager { this._tableHandlerOnDelete(range, ev); } + this._insertBRIfNeed(range); this._removeContentsAndChangeSelectionIfNeed(range, keymap, ev); isNeedNext = false; } else if ((isBackspace && this._isBeforeTable(range)) @@ -307,6 +311,7 @@ class WwTableManager { if (domUtils.getNodeName(tdOrTh.lastChild) !== 'BR' && domUtils.getNodeName(tdOrTh.lastChild) !== 'DIV' + && !isIE10And11 ) { $(tdOrTh).append($('
')[0]); } @@ -320,9 +325,16 @@ class WwTableManager { * @private */ _unwrapBlockInTable() { - this.wwe.get$Body().find('td div,th div,tr>br').each((index, node) => { - if (node.nodeName === 'BR') { - $(node).remove(); + this.wwe.get$Body().find('td div,th div,tr>br,td>br,th>br').each((index, node) => { + if (domUtils.getNodeName(node) === 'BR') { + const parentNodeName = domUtils.getNodeName(node.parentNode); + const isInTableCell = /TD|TH/.test(parentNodeName); + const isEmptyTableCell = node.parentNode.textContent.length === 0; + const isLastBR = node.parentNode.lastChild === node; + + if (parentNodeName === 'TR' || (isInTableCell && !isEmptyTableCell && isLastBR)) { + $(node).remove(); + } } else { $(node).children().unwrap(); } @@ -992,22 +1004,6 @@ class WwTableManager { } } - /** - * Create selection by selected cells and collapse that selection to end - * @private - */ - _createRangeBySelectedCells() { - const sq = this.wwe.getEditor(); - const range = sq.getSelection().cloneRange(); - const selectedCells = this.wwe.getManager('tableSelection').getSelectedCells(); - - if (selectedCells.length && this.isInTable(range)) { - range.setStart(selectedCells.first()[0], 0); - range.setEnd(selectedCells.last()[0], 1); - sq.setSelection(range); - } - } - /** * Create selection by selected cells and collapse that selection to end * @private @@ -1062,10 +1058,11 @@ class WwTableManager { _bindKeyEventForTableCopyAndCut() { const isMac = /Mac OS X/.test(navigator.userAgent); const commandKey = isMac ? 'metaKey' : 'ctrlKey'; + const selectionManager = this.wwe.getManager('tableSelection'); this.wwe.getEditor().addEventListener('keydown', event => { if (event[commandKey]) { - this._createRangeBySelectedCells(); + selectionManager.createRangeBySelectedCells(); } }); @@ -1113,6 +1110,44 @@ class WwTableManager { return tableClassName; } + + /** + * Remove br when text inputted + * @param {Range} range Range object + * @private + */ + _removeBRIfNeed(range) { + const startContainer = domUtils.isTextNode(range.startContainer) + ? range.startContainer.parentNode : range.startContainer; + const nodeName = domUtils.getNodeName(startContainer); + + if (/td|th/i.test(nodeName) && range.collapsed && startContainer.textContent.length === 1) { + $(startContainer).find('br').remove(); + } + } + + + /** + * Insert br when text deleted + * @param {Range} range Range object + * @private + */ + _insertBRIfNeed(range) { + const currentCell = range.startContainer.nodeType === 3 + ? range.startContainer.parentNode : range.startContainer; + const nodeName = domUtils.getNodeName(currentCell); + const $currentCell = $(currentCell); + + if (/td|th/i.test(nodeName) + && range.collapsed + && !currentCell.textContent.length + && !$currentCell.children().length + && !isIE10And11 + ) { + currentCell.normalize(); + $currentCell.append('
'); + } + } } /** diff --git a/apps/editor/src/js/wwTableSelectionManager.js b/apps/editor/src/js/wwTableSelectionManager.js index c44f079fb6..46c214b7fd 100644 --- a/apps/editor/src/js/wwTableSelectionManager.js +++ b/apps/editor/src/js/wwTableSelectionManager.js @@ -77,7 +77,7 @@ class WwTableSelectionManager { this.eventManager.listen('mousedown', ev => { const MOUSE_RIGHT_BUTTON = 2; - selectionStart = ev.data.target; + selectionStart = $(ev.data.target).closest('td,th')[0]; const isSelectedCell = $(selectionStart).hasClass(TABLE_CELL_SELECTED_CLASS_NAME); selectionEnd = null; @@ -91,12 +91,14 @@ class WwTableSelectionManager { }); this.eventManager.listen('mouseover', ev => { - const isTextSelect = this.wwe.getEditor().getSelection().commonAncestorContainer.nodeType === 3; + selectionEnd = $(ev.data.target).closest('td,th')[0]; - selectionEnd = ev.data.target; - const isTableCell = $(selectionEnd).parents('table')[0]; + const range = this.wwe.getEditor().getSelection(); + const isEndsInTable = $(selectionEnd).parents('table')[0]; + const isSameCell = selectionStart === selectionEnd; + const isTextSelect = this._isTextSelect(range, isSameCell); - if (this._isSelectionStarted && !isTextSelect && isTableCell) { + if (this._isSelectionStarted && isEndsInTable && (!isTextSelect || isSameCell && !isTextSelect)) { // For disable firefox's native table cell selection if (tui.util.browser.firefox && !this._removeSelectionTimer) { this._removeSelectionTimer = setInterval(() => { @@ -106,23 +108,42 @@ class WwTableSelectionManager { this._highlightTableCellsBy(selectionStart, selectionEnd); } }); - this.eventManager.listen('mouseup', () => { - const isTextSelect = this.wwe.getEditor().getSelection().commonAncestorContainer.nodeType === 3; + this.eventManager.listen('mouseup', ev => { + selectionEnd = $(ev.data.target).closest('td,th')[0]; + + let range = this.wwe.getEditor().getSelection(); + const isSameCell = selectionStart === selectionEnd; + const isTextSelect = this._isTextSelect(range, isSameCell); this._clearTableSelectionTimerIfNeed(); - if (this._isSelectionStarted && !isTextSelect) { - this.wwe.getManager('table').resetLastCellNode(); + if (this._isSelectionStarted) { + if (isTextSelect) { + this.removeClassAttrbuteFromAllCellsIfNeed(); + } else { + this.wwe.getManager('table').resetLastCellNode(); - const range = this.wwe.getEditor().getSelection(); - range.collapse(true); - this.wwe.getEditor().setSelection(range); + range = this.wwe.getEditor().getSelection(); + range.collapse(true); + this.wwe.getEditor().setSelection(range); + } } this._isSelectionStarted = false; }); } + /** + * Return whether single cell text selection or not + * @param {Range} range Range object + * @param {boolean} isSameCell Boolean value for same cell selection + * @returns {boolean} + * @private + */ + _isTextSelect(range, isSameCell) { + return /TD|TH|TEXT/i.test(range.commonAncestorContainer.nodeName) && isSameCell; + } + /** * Set setTimeout and setInterval timer execution if table selecting situation * @param {HTMLElement} selectionStart Start element @@ -134,8 +155,6 @@ class WwTableSelectionManager { if (isTableSelecting) { this._tableSelectionTimer = setTimeout(() => { this._isSelectionStarted = true; - this._isCellsSelected = true; - this._isSecondMouseDown = false; }, 100); } } @@ -335,5 +354,22 @@ class WwTableSelectionManager { getSelectedCells() { return this.wwe.get$Body().find(`.${TABLE_CELL_SELECTED_CLASS_NAME}`); } + + /** + * Create selection by selected cells and collapse that selection to end + * @private + */ + createRangeBySelectedCells() { + const sq = this.wwe.getEditor(); + const range = sq.getSelection().cloneRange(); + const selectedCells = this.getSelectedCells(); + const tableManager = this.wwe.getManager('table'); + + if (selectedCells.length && tableManager.isInTable(range)) { + range.setStart(selectedCells.first()[0], 0); + range.setEnd(selectedCells.last()[0], 1); + sq.setSelection(range); + } + } } module.exports = WwTableSelectionManager; diff --git a/apps/editor/src/js/wysiwygCommands/bold.js b/apps/editor/src/js/wysiwygCommands/bold.js index c3a66a245a..9c0588e54a 100644 --- a/apps/editor/src/js/wysiwygCommands/bold.js +++ b/apps/editor/src/js/wysiwygCommands/bold.js @@ -5,7 +5,8 @@ */ -const CommandManager = require('../commandManager'); +import CommandManager from '../commandManager'; +import domUtils from '../domUtils'; /** * Bold @@ -23,9 +24,14 @@ const Bold = CommandManager.command('wysiwyg', /** @lends Bold */{ */ exec(wwe) { const sq = wwe.getEditor(); + const tableSelectionManager = wwe.getManager('tableSelection'); sq.focus(); + if (sq.hasFormat('table') && tableSelectionManager.getSelectedCells().length) { + tableSelectionManager.createRangeBySelectedCells(); + } + if (sq.hasFormat('b') || sq.hasFormat('strong')) { sq.changeFormat(null, {tag: 'b'}); } else if (!sq.hasFormat('a') && !sq.hasFormat('PRE')) { @@ -34,6 +40,12 @@ const Bold = CommandManager.command('wysiwyg', /** @lends Bold */{ } sq.bold(); } + + const range = sq.getSelection(); + if (sq.hasFormat('table') && !domUtils.isTextNode(range.commonAncestorContainer)) { + range.collapse(true); + sq.setSelection(range); + } } }); diff --git a/apps/editor/src/js/wysiwygCommands/code.js b/apps/editor/src/js/wysiwygCommands/code.js index 06e4038722..effa6688fe 100644 --- a/apps/editor/src/js/wysiwygCommands/code.js +++ b/apps/editor/src/js/wysiwygCommands/code.js @@ -5,8 +5,8 @@ */ -const CommandManager = require('../commandManager'), - domUtils = require('../domUtils'); +import CommandManager from '../commandManager'; +import domUtils from '../domUtils'; /** * Code @@ -24,9 +24,15 @@ const Code = CommandManager.command('wysiwyg', /** @lends Code */{ */ exec(wwe) { const sq = wwe.getEditor(); + let range = sq.getSelection(); + const tableSelectionManager = wwe.getManager('tableSelection'); sq.focus(); + if (sq.hasFormat('table') && tableSelectionManager.getSelectedCells().length) { + tableSelectionManager.createRangeBySelectedCells(); + } + if (!sq.hasFormat('PRE') && sq.hasFormat('code')) { sq.changeFormat(null, {tag: 'code'}); removeUnnecessaryCodeInNextToRange(wwe.getEditor().getSelection().cloneRange()); @@ -39,12 +45,17 @@ const Code = CommandManager.command('wysiwyg', /** @lends Code */{ sq.changeFormat({tag: 'code'}); - const range = sq.getSelection().cloneRange(); + range = sq.getSelection().cloneRange(); range.setStart(range.endContainer, range.endOffset); range.collapse(true); sq.setSelection(range); } + + if (sq.hasFormat('table') && !domUtils.isTextNode(range.commonAncestorContainer)) { + range.collapse(true); + sq.setSelection(range); + } } }); diff --git a/apps/editor/src/js/wysiwygCommands/codeBlock.js b/apps/editor/src/js/wysiwygCommands/codeBlock.js index 06453a3e43..dbcd6ae36b 100644 --- a/apps/editor/src/js/wysiwygCommands/codeBlock.js +++ b/apps/editor/src/js/wysiwygCommands/codeBlock.js @@ -29,7 +29,7 @@ const CodeBlock = CommandManager.command('wysiwyg', /** @lends CodeBlock */{ exec(wwe, type) { const sq = wwe.getEditor(); const range = sq.getSelection().cloneRange(); - if (!sq.hasFormat('PRE')) { + if (!sq.hasFormat('PRE') && !sq.hasFormat('TABLE')) { let attr = `${CODEBLOCK_ATTR_NAME} class = "${CODEBLOCK_CLASS_PREFIX}${codeBlockID}"`; if (type) { diff --git a/apps/editor/src/js/wysiwygCommands/heading.js b/apps/editor/src/js/wysiwygCommands/heading.js index 9cba3af295..4b416be640 100644 --- a/apps/editor/src/js/wysiwygCommands/heading.js +++ b/apps/editor/src/js/wysiwygCommands/heading.js @@ -24,6 +24,9 @@ const Heading = CommandManager.command('wysiwyg', /** @lends Heading */{ */ exec(wwe, size) { const sq = wwe.getEditor(); + + sq.focus(); + const range = sq.getSelection().cloneRange(); const nodeName = domUtils.getNodeName(range.commonAncestorContainer); @@ -33,7 +36,6 @@ const Heading = CommandManager.command('wysiwyg', /** @lends Heading */{ } } - sq.focus(); } }); diff --git a/apps/editor/src/js/wysiwygCommands/italic.js b/apps/editor/src/js/wysiwygCommands/italic.js index 1dc9d281c8..dc2643b84a 100644 --- a/apps/editor/src/js/wysiwygCommands/italic.js +++ b/apps/editor/src/js/wysiwygCommands/italic.js @@ -6,6 +6,7 @@ import CommandManager from '../commandManager'; +import domUtils from '../domUtils'; /** * Italic @@ -23,6 +24,14 @@ const Italic = CommandManager.command('wysiwyg', /** @lends Italic */{ */ exec(wwe) { const sq = wwe.getEditor(); + const range = sq.getSelection(); + const tableSelectionManager = wwe.getManager('tableSelection'); + + sq.focus(); + + if (sq.hasFormat('table') && tableSelectionManager.getSelectedCells().length) { + tableSelectionManager.createRangeBySelectedCells(); + } if (sq.hasFormat('i') || sq.hasFormat('em')) { sq.changeFormat(null, {tag: 'i'}); @@ -33,7 +42,10 @@ const Italic = CommandManager.command('wysiwyg', /** @lends Italic */{ sq.italic(); } - sq.focus(); + if (sq.hasFormat('table') && !domUtils.isTextNode(range.commonAncestorContainer)) { + range.collapse(true); + sq.setSelection(range); + } } }); diff --git a/apps/editor/src/js/wysiwygCommands/strike.js b/apps/editor/src/js/wysiwygCommands/strike.js index 0d79f102c1..686b950005 100644 --- a/apps/editor/src/js/wysiwygCommands/strike.js +++ b/apps/editor/src/js/wysiwygCommands/strike.js @@ -5,6 +5,7 @@ import CommandManager from '../commandManager'; +import domUtils from '../domUtils'; /** * Strike @@ -22,6 +23,14 @@ const Strike = CommandManager.command('wysiwyg', /** @lends Strike */{ */ exec(wwe) { const sq = wwe.getEditor(); + const range = sq.getSelection(); + const tableSelectionManager = wwe.getManager('tableSelection'); + + sq.focus(); + + if (sq.hasFormat('table') && tableSelectionManager.getSelectedCells().length) { + tableSelectionManager.createRangeBySelectedCells(); + } if (sq.hasFormat('S')) { sq.changeFormat(null, {tag: 'S'}); @@ -32,7 +41,10 @@ const Strike = CommandManager.command('wysiwyg', /** @lends Strike */{ sq.strikethrough(); } - sq.focus(); + if (sq.hasFormat('table') && !domUtils.isTextNode(range.commonAncestorContainer)) { + range.collapse(true); + sq.setSelection(range); + } } }); diff --git a/apps/editor/test/wwTableManager.spec.js b/apps/editor/test/wwTableManager.spec.js index 20e9b4883d..a49c93b5a9 100644 --- a/apps/editor/test/wwTableManager.spec.js +++ b/apps/editor/test/wwTableManager.spec.js @@ -1052,57 +1052,7 @@ describe('WwTableManager', () => { expect(target).toBeUndefined(); }); }); - describe('_createRangeBySelectedCells', () => { - it('should create selection by selected cells and current selection is in table', () => { - const html = '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '
12
3
2
1
4
56
'; - - wwe.focus(); - wwe.get$Body().html(html); - - let range = wwe.getEditor().getSelection(); - range.setStart(wwe.get$Body().find('th')[0], 0); - range.collapse(true); - wwe.getEditor().setSelection(range); - - mgr._createRangeBySelectedCells(); - range = wwe.getEditor().getSelection(); - - expect(range.startContainer).toBe(wwe.get$Body().find('th')[0]); - expect(range.endContainer).toBe(wwe.get$Body().find('td')[1]); - }); - it('do not selection on table when current selection is not in table', () => { - const html = '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '
12
3
2
1
4
56
2
'; - wwe.focus(); - wwe.get$Body().html(html); - - let range = wwe.getEditor().getSelection(); - range.setStart(wwe.get$Body().find('div')[0], 0); - range.collapse(true); - wwe.getEditor().setSelection(range); - - mgr._createRangeBySelectedCells(); - range = wwe.getEditor().getSelection(); - - expect(range.startContainer).toBe(wwe.get$Body().find('div')[0]); - }); - }); describe('_collapseRangeToEndContainer', () => { it('collapse selection to endContainer', done => { const html = '' + diff --git a/apps/editor/test/wwTableSelectionManager.spec.js b/apps/editor/test/wwTableSelectionManager.spec.js index 5a8233f02a..a9dd36d51c 100644 --- a/apps/editor/test/wwTableSelectionManager.spec.js +++ b/apps/editor/test/wwTableSelectionManager.spec.js @@ -1,6 +1,7 @@ import WysiwygEditor from '../src/js/wysiwygEditor'; import EventManager from '../src/js/eventManager'; import WwTableSelectionManager from '../src/js/wwTableSelectionManager'; +import WwTableManager from '../src/js/wwTableManager'; describe('WwTableSelectionManager', () => { let $container, em, wwe, mgr; @@ -18,6 +19,7 @@ describe('WwTableSelectionManager', () => { mgr = new WwTableSelectionManager(wwe); wwe.focus(); + wwe.addManager(WwTableManager); }); //we need to wait squire input event process @@ -329,4 +331,56 @@ describe('WwTableSelectionManager', () => { expect(selectedCells.eq(3).text()).toBe('5'); }); }); + + describe('_createRangeBySelectedCells', () => { + it('should create selection by selected cells and current selection is in table', () => { + const html = '
' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '
12
3
2
1
4
56
'; + + wwe.focus(); + wwe.get$Body().html(html); + + let range = wwe.getEditor().getSelection(); + range.setStart(wwe.get$Body().find('th')[0], 0); + range.collapse(true); + wwe.getEditor().setSelection(range); + + mgr.createRangeBySelectedCells(); + range = wwe.getEditor().getSelection(); + + expect(range.startContainer).toBe(wwe.get$Body().find('th')[0]); + expect(range.endContainer).toBe(wwe.get$Body().find('td')[1]); + }); + it('do not selection on table when current selection is not in table', () => { + const html = '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '
12
3
2
1
4
56
2
'; + + wwe.focus(); + wwe.get$Body().html(html); + + let range = wwe.getEditor().getSelection(); + range.setStart(wwe.get$Body().find('div')[0], 0); + range.collapse(true); + wwe.getEditor().setSelection(range); + + mgr.createRangeBySelectedCells(); + range = wwe.getEditor().getSelection(); + + expect(range.startContainer).toBe(wwe.get$Body().find('div')[0]); + }); + }); }); diff --git a/apps/editor/test/wysiwygCommands/bold.spec.js b/apps/editor/test/wysiwygCommands/bold.spec.js index b714e23ae0..a27dc9b40a 100644 --- a/apps/editor/test/wysiwygCommands/bold.spec.js +++ b/apps/editor/test/wysiwygCommands/bold.spec.js @@ -1,4 +1,5 @@ import Bold from '../../src/js/wysiwygCommands/bold'; +import WwTableSelectionManager from '../../src/js/wwTableSelectionManager'; import WysiwygEditor from '../../src/js/wysiwygEditor'; import EventManager from '../../src/js/eventManager'; @@ -13,6 +14,8 @@ describe('Bold', () => { wwe = new WysiwygEditor($container, new EventManager()); wwe.init(); + + wwe.addManager(WwTableSelectionManager); wwe.getEditor().focus(); }); diff --git a/apps/editor/test/wysiwygCommands/code.spec.js b/apps/editor/test/wysiwygCommands/code.spec.js index e9a5c6c465..9607290404 100644 --- a/apps/editor/test/wysiwygCommands/code.spec.js +++ b/apps/editor/test/wysiwygCommands/code.spec.js @@ -1,5 +1,6 @@ import Code from '../../src/js/wysiwygCommands/code'; import WysiwygEditor from '../../src/js/wysiwygEditor'; +import WwTableSelectionManager from '../../src/js/wwTableSelectionManager'; import EventManager from '../../src/js/eventManager'; describe('Code', () => { @@ -13,6 +14,8 @@ describe('Code', () => { wwe = new WysiwygEditor($container, new EventManager()); wwe.init(); + + wwe.addManager(WwTableSelectionManager); wwe.getEditor().focus(); }); diff --git a/apps/editor/test/wysiwygCommands/italic.spec.js b/apps/editor/test/wysiwygCommands/italic.spec.js index 7a92ba7f2f..1f6bbef5fb 100644 --- a/apps/editor/test/wysiwygCommands/italic.spec.js +++ b/apps/editor/test/wysiwygCommands/italic.spec.js @@ -1,5 +1,6 @@ import Italic from '../../src/js/wysiwygCommands/italic'; import WysiwygEditor from '../../src/js/wysiwygEditor'; +import WwTableSelectionManager from '../../src/js/wwTableSelectionManager'; import EventManager from '../../src/js/eventManager'; describe('Italic', () => { @@ -13,6 +14,8 @@ describe('Italic', () => { wwe = new WysiwygEditor($container, new EventManager()); wwe.init(); + + wwe.addManager(WwTableSelectionManager); wwe.getEditor().focus(); }); diff --git a/apps/editor/test/wysiwygCommands/strike.spec.js b/apps/editor/test/wysiwygCommands/strike.spec.js index 58a6386da8..44ad491be9 100644 --- a/apps/editor/test/wysiwygCommands/strike.spec.js +++ b/apps/editor/test/wysiwygCommands/strike.spec.js @@ -1,5 +1,6 @@ import Strike from '../../src/js/wysiwygCommands/strike'; import WysiwygEditor from '../../src/js/wysiwygEditor'; +import WwTableSelectionManager from '../../src/js/wwTableSelectionManager'; import EventManager from '../../src/js/eventManager'; describe('Strike', () => { @@ -13,6 +14,8 @@ describe('Strike', () => { wwe = new WysiwygEditor($container, new EventManager()); wwe.init(); + + wwe.addManager(WwTableSelectionManager); wwe.getEditor().focus(); });