From 6a844c87c9a8b26bfe7f45efa75c46bd3c80872d Mon Sep 17 00:00:00 2001 From: ksercs Date: Tue, 18 Apr 2023 03:56:30 +0400 Subject: [PATCH 01/12] Table cell and row should not be removed on backspace press or typing even if it is selected (T1062588) --- blots/scroll.js | 6 +- formats/table/index.js | 15 ++++- test/unit/modules/table_main.js | 101 ++++++++++++++++++++++++++++++++ 3 files changed, 120 insertions(+), 2 deletions(-) diff --git a/blots/scroll.js b/blots/scroll.js index 824a42299e..a157ba7e6b 100644 --- a/blots/scroll.js +++ b/blots/scroll.js @@ -3,6 +3,7 @@ import Emitter from '../core/emitter'; import Block, { BlockEmbed } from './block'; import Break from './break'; import Container from './container'; +import { CellLine } from '../formats/table'; function isLine(blot) { return blot instanceof Block || blot instanceof BlockEmbed; @@ -44,7 +45,10 @@ class Scroll extends ScrollBlot { const [last] = this.line(index + length); super.deleteAt(index, length); if (last != null && first !== last && offset > 0) { - if (first instanceof BlockEmbed || last instanceof BlockEmbed) { + if ( + first instanceof BlockEmbed || last instanceof BlockEmbed + || first instanceof CellLine || last instanceof CellLine + ) { this.optimize(); return; } diff --git a/formats/table/index.js b/formats/table/index.js index 900c65cefd..12c3d3f561 100644 --- a/formats/table/index.js +++ b/formats/table/index.js @@ -16,7 +16,7 @@ class CellLine extends Block { const node = super.create(value); CELL_IDENTITY_KEYS.forEach((key) => { const identityMarker = key === 'row' ? tableId : cellId; - node.setAttribute(`${DATA_PREFIX}${key}`, value[key] ?? identityMarker()); + node.setAttribute(`${DATA_PREFIX}${key}`, value?.[key] ?? identityMarker()); }); return node; @@ -196,6 +196,12 @@ class TableCell extends BaseCell { return node; } + deleteAt(index, length) { + this.children.forEachAt(index, length, function (child, offset, childLength) { + child.deleteAt(offset, childLength); + }); + } + format(name, value) { if (name === 'row') { this.domNode.setAttribute(`${DATA_PREFIX}${name}`, value); @@ -210,6 +216,7 @@ class TableCell extends BaseCell { TableCell.blotName = 'tableCell'; TableCell.className = 'ql-table-data-cell'; TableCell.dataAttribute = `${DATA_PREFIX}row`; +TableCell.defaultChild = CellLine; class TableHeaderCell extends BaseCell { static create(value) { @@ -316,6 +323,12 @@ class TableRow extends BaseRow { this.childFormatName = 'table'; } + + deleteAt(index, length) { + this.children.forEachAt(index, length, function (child, offset, childLength) { + child.deleteAt(offset, childLength); + }); + } } TableRow.blotName = 'tableRow'; diff --git a/test/unit/modules/table_main.js b/test/unit/modules/table_main.js index 7b0765bf39..bef8459b3e 100644 --- a/test/unit/modules/table_main.js +++ b/test/unit/modules/table_main.js @@ -1166,6 +1166,107 @@ describe('Table Module', function () { true, ); }); + + describe('deleteAt', function () { + it('should not remove a cell (T1062588)', function () { + this.quill.setSelection(0); + this.table.deleteRow(); + + this.quill.scroll.deleteAt(5, 3); + expect(this.quill.root).toEqualHTML( + ` + + + + + + + + +

b1

b2


+ `, + true, + ); + }); + + it('should not remove a cell if several cells are selected', function () { + this.quill.setSelection(0); + this.table.deleteRow(); + + this.quill.scroll.deleteAt(1, 7); + expect(this.quill.root).toEqualHTML( + ` + + + + + + + + +

b



+ `, + true, + ); + }); + + it('should not remove a row', function () { + this.quill.scroll.deleteAt(5, 12); + expect(this.quill.root).toEqualHTML( + ` + + + + + + + + + + + + + +

a1

a2





+ `, + true, + ); + }); + + it('should remove a table', function () { + this.quill.scroll.deleteAt(0, 18); + expect(this.quill.root).toEqualHTML( + ` +


+ `, + true, + ); + }); + }); + + it('type should not remove a cell if it is selected', function () { + this.quill.updateContents(new Delta().retain(14).delete(5).insert('_INPUT_'), 'user'); + + expect(this.quill.root).toEqualHTML( + ` + + + + + + + + + + + + + +

a1

a2

a3

b1

b2_INPUT_


+ `, + true, + ); + }); }); describe('customize table', function () { From 62fb35686124a4cd079aa16ebd293ff549d50f3a Mon Sep 17 00:00:00 2001 From: ksercs Date: Tue, 18 Apr 2023 13:49:40 +0400 Subject: [PATCH 02/12] fix for header cells --- formats/table/index.js | 1 + test/functional/epic.js | 41 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+) diff --git a/formats/table/index.js b/formats/table/index.js index 12c3d3f561..11331c7e3b 100644 --- a/formats/table/index.js +++ b/formats/table/index.js @@ -243,6 +243,7 @@ TableHeaderCell.tagName = ['TH', 'TD']; TableHeaderCell.className = 'ql-table-header-cell'; TableHeaderCell.blotName = 'tableHeaderCell'; TableHeaderCell.dataAttribute = `${DATA_PREFIX}header-row`; +TableHeaderCell.defaultChild = HeaderCellLine; class BaseRow extends Container { checkMerge() { diff --git a/test/functional/epic.js b/test/functional/epic.js index 28140ab073..2c93b5d69c 100644 --- a/test/functional/epic.js +++ b/test/functional/epic.js @@ -11,6 +11,10 @@ const EMBED = `${GUARD_CHAR})/gi, '$1$5'); +} + describe('quill', function () { it('compose an epic', async function () { const browser = await puppeteer.launch({ @@ -247,6 +251,43 @@ describe('quill', function () { }); }); +describe('table header', function() { + it('cell should not be removed on typing if it is selected', async function () { + const browser = await puppeteer.launch({ + headless: false, + }); + const page = await browser.newPage(); + + await page.goto('http://127.0.0.1:8080/table_header.html'); + await page.waitForSelector('.ql-editor', { timeout: 10000 }); + + await page.click('[data-table-cell="3"]'); + + await page.keyboard.down('Shift'); + await page.keyboard.press('ArrowLeft'); + await page.keyboard.press('ArrowLeft'); + await page.keyboard.up('Shift'); + + await page.keyboard.press('c'); + + const html = await page.$eval('.ql-editor', (e) => e.innerHTML); + const sanitizeHtml = sanitizeTableHtml(html); + expect(sanitizeHtml).toEqual( + ` + + + + + + + + +

1

2c


+ `.replace(/\s/g, '') + ); + }); +}) + function getSelectionInTextNode() { const { anchorNode, anchorOffset, focusNode, focusOffset, From 4778199153d5515f56b772b9d2225902cb91c69d Mon Sep 17 00:00:00 2001 From: ksercs Date: Tue, 18 Apr 2023 13:51:38 +0400 Subject: [PATCH 03/12] add html for functional test --- test/functional/example/table_header.html | 36 +++++++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 test/functional/example/table_header.html diff --git a/test/functional/example/table_header.html b/test/functional/example/table_header.html new file mode 100644 index 0000000000..045e5b0a0d --- /dev/null +++ b/test/functional/example/table_header.html @@ -0,0 +1,36 @@ + + + + + + + DevExtreme-Quill Base Editing + + + + + +
+
+ + + + + + + + +
123
+
+
+ + + + From 34a6564723844f3681241c526af2cfab8622f227 Mon Sep 17 00:00:00 2001 From: ksercs Date: Tue, 18 Apr 2023 13:59:15 +0400 Subject: [PATCH 04/12] fix deleting by backspace for header cell --- formats/table/index.js | 12 +++++------ test/unit/modules/table_main.js | 35 +++++++++++++++++++++++++++++++++ 2 files changed, 41 insertions(+), 6 deletions(-) diff --git a/formats/table/index.js b/formats/table/index.js index 11331c7e3b..e99ce608f9 100644 --- a/formats/table/index.js +++ b/formats/table/index.js @@ -183,6 +183,12 @@ class BaseCell extends Container { } super.optimize(...args); } + + deleteAt(index, length) { + this.children.forEachAt(index, length, function (child, offset, childLength) { + child.deleteAt(offset, childLength); + }); + } } BaseCell.tagName = ['TD', 'TH']; @@ -196,12 +202,6 @@ class TableCell extends BaseCell { return node; } - deleteAt(index, length) { - this.children.forEachAt(index, length, function (child, offset, childLength) { - child.deleteAt(offset, childLength); - }); - } - format(name, value) { if (name === 'row') { this.domNode.setAttribute(`${DATA_PREFIX}${name}`, value); diff --git a/test/unit/modules/table_main.js b/test/unit/modules/table_main.js index bef8459b3e..278cac21e3 100644 --- a/test/unit/modules/table_main.js +++ b/test/unit/modules/table_main.js @@ -1209,6 +1209,41 @@ describe('Table Module', function () { true, ); }); + + it('should not remove a header cell if several cells are selected', function () { + const markup = ` + + + + + +

h1

+

h2

+

h3

+
+ `; + this.quill = this.initialize(Quill, markup, this.container, { + modules: { + table: true, + }, + }); + + this.quill.scroll.deleteAt(3, 7); + expect(this.quill.root).toEqualHTML( + ` + + + + + +

h1

+


+


+
+ `, + true, + ); + }); it('should not remove a row', function () { this.quill.scroll.deleteAt(5, 12); From 9bc907797f216b3031979b2359b1f0966d2f469d Mon Sep 17 00:00:00 2001 From: ksercs Date: Tue, 18 Apr 2023 15:47:30 +0400 Subject: [PATCH 05/12] lint --- blots/scroll.js | 2 +- formats/table/index.js | 4 ++-- test/functional/epic.js | 8 ++++---- test/unit/modules/table_main.js | 12 ++++++------ 4 files changed, 13 insertions(+), 13 deletions(-) diff --git a/blots/scroll.js b/blots/scroll.js index a157ba7e6b..5823d9f6d3 100644 --- a/blots/scroll.js +++ b/blots/scroll.js @@ -46,7 +46,7 @@ class Scroll extends ScrollBlot { super.deleteAt(index, length); if (last != null && first !== last && offset > 0) { if ( - first instanceof BlockEmbed || last instanceof BlockEmbed + first instanceof BlockEmbed || last instanceof BlockEmbed || first instanceof CellLine || last instanceof CellLine ) { this.optimize(); diff --git a/formats/table/index.js b/formats/table/index.js index e99ce608f9..cb4c68bd23 100644 --- a/formats/table/index.js +++ b/formats/table/index.js @@ -185,7 +185,7 @@ class BaseCell extends Container { } deleteAt(index, length) { - this.children.forEachAt(index, length, function (child, offset, childLength) { + this.children.forEachAt(index, length, (child, offset, childLength) => { child.deleteAt(offset, childLength); }); } @@ -326,7 +326,7 @@ class TableRow extends BaseRow { } deleteAt(index, length) { - this.children.forEachAt(index, length, function (child, offset, childLength) { + this.children.forEachAt(index, length, (child, offset, childLength) => { child.deleteAt(offset, childLength); }); } diff --git a/test/functional/epic.js b/test/functional/epic.js index 2c93b5d69c..f5875cd939 100644 --- a/test/functional/epic.js +++ b/test/functional/epic.js @@ -251,7 +251,7 @@ describe('quill', function () { }); }); -describe('table header', function() { +describe('table header', function () { it('cell should not be removed on typing if it is selected', async function () { const browser = await puppeteer.launch({ headless: false, @@ -260,7 +260,7 @@ describe('table header', function() { await page.goto('http://127.0.0.1:8080/table_header.html'); await page.waitForSelector('.ql-editor', { timeout: 10000 }); - + await page.click('[data-table-cell="3"]'); await page.keyboard.down('Shift'); @@ -283,10 +283,10 @@ describe('table header', function() { - `.replace(/\s/g, '') + `.replace(/\s/g, ''), ); }); -}) +}); function getSelectionInTextNode() { const { diff --git a/test/unit/modules/table_main.js b/test/unit/modules/table_main.js index 278cac21e3..9df9bc8c35 100644 --- a/test/unit/modules/table_main.js +++ b/test/unit/modules/table_main.js @@ -1171,7 +1171,7 @@ describe('Table Module', function () { it('should not remove a cell (T1062588)', function () { this.quill.setSelection(0); this.table.deleteRow(); - + this.quill.scroll.deleteAt(5, 3); expect(this.quill.root).toEqualHTML( ` @@ -1188,11 +1188,11 @@ describe('Table Module', function () { true, ); }); - + it('should not remove a cell if several cells are selected', function () { this.quill.setSelection(0); this.table.deleteRow(); - + this.quill.scroll.deleteAt(1, 7); expect(this.quill.root).toEqualHTML( ` @@ -1227,7 +1227,7 @@ describe('Table Module', function () { table: true, }, }); - + this.quill.scroll.deleteAt(3, 7); expect(this.quill.root).toEqualHTML( ` @@ -1244,7 +1244,7 @@ describe('Table Module', function () { true, ); }); - + it('should not remove a row', function () { this.quill.scroll.deleteAt(5, 12); expect(this.quill.root).toEqualHTML( @@ -1267,7 +1267,7 @@ describe('Table Module', function () { true, ); }); - + it('should remove a table', function () { this.quill.scroll.deleteAt(0, 18); expect(this.quill.root).toEqualHTML( From df973763e1f1096b3e6fe5ce16cd7a88f62ed7d8 Mon Sep 17 00:00:00 2001 From: ksercs Date: Tue, 18 Apr 2023 16:19:15 +0400 Subject: [PATCH 06/12] refactor --- blots/scroll.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/blots/scroll.js b/blots/scroll.js index 5823d9f6d3..e65ae7d626 100644 --- a/blots/scroll.js +++ b/blots/scroll.js @@ -47,7 +47,7 @@ class Scroll extends ScrollBlot { if (last != null && first !== last && offset > 0) { if ( first instanceof BlockEmbed || last instanceof BlockEmbed - || first instanceof CellLine || last instanceof CellLine + || first instanceof CellLine || last instanceof CellLine ) { this.optimize(); return; From 00eec21f33747a5d86e2f05f1595f2db863162b1 Mon Sep 17 00:00:00 2001 From: ksercs Date: Tue, 18 Apr 2023 18:58:43 +0400 Subject: [PATCH 07/12] fix cellLine delete when multiline text is in the cell --- blots/scroll.js | 5 ++++- test/unit/modules/table_main.js | 31 +++++++++++++++++++++++++++++++ 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/blots/scroll.js b/blots/scroll.js index e65ae7d626..c1614a1223 100644 --- a/blots/scroll.js +++ b/blots/scroll.js @@ -45,9 +45,12 @@ class Scroll extends ScrollBlot { const [last] = this.line(index + length); super.deleteAt(index, length); if (last != null && first !== last && offset > 0) { + const isCrossCellDelete = first instanceof CellLine + && last instanceof CellLine + && first.parent !== last.parent; if ( first instanceof BlockEmbed || last instanceof BlockEmbed - || first instanceof CellLine || last instanceof CellLine + || isCrossCellDelete ) { this.optimize(); return; diff --git a/test/unit/modules/table_main.js b/test/unit/modules/table_main.js index 9df9bc8c35..5b180b89d8 100644 --- a/test/unit/modules/table_main.js +++ b/test/unit/modules/table_main.js @@ -1189,6 +1189,37 @@ describe('Table Module', function () { ); }); + it('should remove a cell line', function () { + const markup = ` + + + + + + +
a1
a2
+ `; + this.quill = this.initialize(Quill, markup, this.container, { + modules: { + table: true, + }, + }); + + this.quill.scroll.deleteAt(2, 3); + expect(this.quill.root).toEqualHTML( + ` + + + + + + +

a1

+ `, + true, + ); + }); + it('should not remove a cell if several cells are selected', function () { this.quill.setSelection(0); this.table.deleteRow(); From 94d6c7e6b710053461630ab734cd57d06bb6f6d0 Mon Sep 17 00:00:00 2001 From: ksercs Date: Wed, 19 Apr 2023 03:33:19 +0400 Subject: [PATCH 08/12] fix deleting when selection is not only in table --- blots/scroll.js | 18 ++-- modules/keyboard.js | 3 +- test/functional/epic.js | 103 +++++++++++++++++++++- test/functional/example/table.html | 37 ++++++++ test/functional/example/table_header.html | 1 + test/unit/modules/table_main.js | 74 ++++++++++++++-- 6 files changed, 217 insertions(+), 19 deletions(-) create mode 100644 test/functional/example/table.html diff --git a/blots/scroll.js b/blots/scroll.js index c1614a1223..c32291fe4b 100644 --- a/blots/scroll.js +++ b/blots/scroll.js @@ -45,19 +45,15 @@ class Scroll extends ScrollBlot { const [last] = this.line(index + length); super.deleteAt(index, length); if (last != null && first !== last && offset > 0) { - const isCrossCellDelete = first instanceof CellLine - && last instanceof CellLine + const isCrossCellDelete = (first instanceof CellLine || last instanceof CellLine) && first.parent !== last.parent; - if ( - first instanceof BlockEmbed || last instanceof BlockEmbed - || isCrossCellDelete - ) { - this.optimize(); - return; + const includesEmbedBlock = first instanceof BlockEmbed || last instanceof BlockEmbed; + + if (!includesEmbedBlock && !isCrossCellDelete) { + const ref = last.children.head instanceof Break ? null : last.children.head; + first.moveChildren(last, ref); + first.remove(); } - const ref = last.children.head instanceof Break ? null : last.children.head; - first.moveChildren(last, ref); - first.remove(); } this.optimize(); } diff --git a/modules/keyboard.js b/modules/keyboard.js index a8e14d86d1..af5c9e105f 100644 --- a/modules/keyboard.js +++ b/modules/keyboard.js @@ -309,7 +309,8 @@ class Keyboard extends Module { const [prev] = this.quill.getLine(range.index - 1); if (prev) { const isPrevLineEmpty = prev.statics.blotName === 'block' && prev.length() <= 1; - if (!isPrevLineEmpty) { + const isPrevLineTable = prev.statics.blotName.startsWith('table'); + if (!isPrevLineEmpty && !isPrevLineTable) { const curFormats = line.formats(); const prevFormats = this.quill.getFormat(range.index - 1, 1); formats = AttributeMap.diff(curFormats, prevFormats) || {}; diff --git a/test/functional/epic.js b/test/functional/epic.js index f5875cd939..6cdf1d12ec 100644 --- a/test/functional/epic.js +++ b/test/functional/epic.js @@ -251,7 +251,7 @@ describe('quill', function () { }); }); -describe('table header', function () { +describe('table header: ', function () { it('cell should not be removed on typing if it is selected', async function () { const browser = await puppeteer.launch({ headless: false, @@ -283,6 +283,107 @@ describe('table header', function () { +


+ `.replace(/\s/g, ''), + ); + }); + + it('no new cell line should be add to table cell if backspace is pressed on the position after table', async function () { + const browser = await puppeteer.launch({ + headless: false, + }); + const page = await browser.newPage(); + + await page.goto('http://127.0.0.1:8080/table_header.html'); + await page.waitForSelector('.ql-editor', { timeout: 10000 }); + + await page.click('[data-table-cell="3"]'); + await page.keyboard.press('ArrowRight'); + await page.keyboard.press('Backspace'); + + const html = await page.$eval('.ql-editor', (e) => e.innerHTML); + const sanitizeHtml = sanitizeTableHtml(html); + expect(sanitizeHtml).toEqual( + ` + + + + + + + + +

1

2

3

+


+ `.replace(/\s/g, ''), + ); + }); +}); + +describe('table:', function () { + it('cell should not be removed on typing if it is selected', async function () { + const browser = await puppeteer.launch({ + headless: false, + }); + const page = await browser.newPage(); + + await page.goto('http://127.0.0.1:8080/table.html'); + await page.waitForSelector('.ql-editor', { timeout: 10000 }); + + await page.click('[data-table-cell="3"]'); + + await page.keyboard.down('Shift'); + await page.keyboard.press('ArrowLeft'); + await page.keyboard.press('ArrowLeft'); + await page.keyboard.up('Shift'); + + await page.keyboard.press('c'); + + const html = await page.$eval('.ql-editor', (e) => e.innerHTML); + const sanitizeHtml = sanitizeTableHtml(html); + expect(sanitizeHtml).toEqual( + ` + + + + + + + + +

1

2c


+


+ `.replace(/\s/g, ''), + ); + }); + + it('no new cell line should be add to table cell if backspace is pressed on the position after table', async function () { + const browser = await puppeteer.launch({ + headless: false, + }); + const page = await browser.newPage(); + + await page.goto('http://127.0.0.1:8080/table.html'); + await page.waitForSelector('.ql-editor', { timeout: 10000 }); + + await page.click('[data-table-cell="3"]'); + await page.keyboard.press('ArrowRight'); + await page.keyboard.press('Backspace'); + + const html = await page.$eval('.ql-editor', (e) => e.innerHTML); + const sanitizeHtml = sanitizeTableHtml(html); + expect(sanitizeHtml).toEqual( + ` + + + + + + + + +

1

2

3

+


`.replace(/\s/g, ''), ); }); diff --git a/test/functional/example/table.html b/test/functional/example/table.html new file mode 100644 index 0000000000..183b004012 --- /dev/null +++ b/test/functional/example/table.html @@ -0,0 +1,37 @@ + + + + + + + DevExtreme-Quill Base Editing + + + + + +
+
+ + + + + + + + +
123
+


+
+
+ + + + diff --git a/test/functional/example/table_header.html b/test/functional/example/table_header.html index 045e5b0a0d..9900314d16 100644 --- a/test/functional/example/table_header.html +++ b/test/functional/example/table_header.html @@ -21,6 +21,7 @@ +


diff --git a/test/unit/modules/table_main.js b/test/unit/modules/table_main.js index 5b180b89d8..ecab887bae 100644 --- a/test/unit/modules/table_main.js +++ b/test/unit/modules/table_main.js @@ -1246,9 +1246,9 @@ describe('Table Module', function () { - + +

h1

-

h2

-

h3

+

h1

h2

h3

@@ -1265,9 +1265,9 @@ describe('Table Module', function () { - + +

h1

-


-


+

h1



@@ -1299,6 +1299,68 @@ describe('Table Module', function () { ); }); + it('should not move cells content if selection ends after table', function () { + const markup = ` + + + + + + +

h1

+
+ `; + this.quill = this.initialize(Quill, markup, this.container, { + modules: { + table: true, + }, + }); + this.quill.scroll.deleteAt(2, 1); + expect(this.quill.root).toEqualHTML( + ` + + + + + + +

h1

+ `, + true, + ); + }); + + it('should not move header cells content if selection ends after table', function () { + const markup = ` + + + + + + +

h1

+
+ `; + this.quill = this.initialize(Quill, markup, this.container, { + modules: { + table: true, + }, + }); + this.quill.scroll.deleteAt(2, 1); + expect(this.quill.root).toEqualHTML( + ` + + + + + + +

h1

+ `, + true, + ); + }); + it('should remove a table', function () { this.quill.scroll.deleteAt(0, 18); expect(this.quill.root).toEqualHTML( From fe481018e097a0dee412f105046a5407da310e88 Mon Sep 17 00:00:00 2001 From: ksercs Date: Wed, 19 Apr 2023 13:15:12 +0400 Subject: [PATCH 09/12] refactor deleteAt override --- formats/table/index.js | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/formats/table/index.js b/formats/table/index.js index cb4c68bd23..1c72018885 100644 --- a/formats/table/index.js +++ b/formats/table/index.js @@ -11,6 +11,12 @@ const CELL_IDENTITY_KEYS = ['row', 'cell']; const TABLE_TAGS = ['TD', 'TH', 'TR', 'TBODY', 'THEAD', 'TABLE']; const DATA_PREFIX = 'data-table-'; +function deleteChildrenAt(children, index, length) { + children.forEachAt(index, length, (child, offset, childLength) => { + child.deleteAt(offset, childLength); + }); +} + class CellLine extends Block { static create(value) { const node = super.create(value); @@ -185,9 +191,7 @@ class BaseCell extends Container { } deleteAt(index, length) { - this.children.forEachAt(index, length, (child, offset, childLength) => { - child.deleteAt(offset, childLength); - }); + deleteChildrenAt(this.children, index, length); } } BaseCell.tagName = ['TD', 'TH']; @@ -326,9 +330,7 @@ class TableRow extends BaseRow { } deleteAt(index, length) { - this.children.forEachAt(index, length, (child, offset, childLength) => { - child.deleteAt(offset, childLength); - }); + deleteChildrenAt(this.children, index, length); } } TableRow.blotName = 'tableRow'; From 85fe01eb87f59c711c3d866d6dfaa1a3333c36c1 Mon Sep 17 00:00:00 2001 From: ksercs Date: Wed, 19 Apr 2023 13:16:08 +0400 Subject: [PATCH 10/12] udpate backspace behavior when cursor is placed after table --- modules/keyboard.js | 8 +++ test/functional/epic.js | 110 ++++++++++++++++++++++++++------ test/unit/modules/table_main.js | 4 +- 3 files changed, 100 insertions(+), 22 deletions(-) diff --git a/modules/keyboard.js b/modules/keyboard.js index af5c9e105f..0bbba9404c 100644 --- a/modules/keyboard.js +++ b/modules/keyboard.js @@ -310,6 +310,14 @@ class Keyboard extends Module { if (prev) { const isPrevLineEmpty = prev.statics.blotName === 'block' && prev.length() <= 1; const isPrevLineTable = prev.statics.blotName.startsWith('table'); + const isLineEmpty = line.statics.blotName === 'block' && line.length() <= 1; + + if (isPrevLineTable) { + if (isLineEmpty) { + line.remove(); + } + this.quill.setSelection(range.index - 1); + } if (!isPrevLineEmpty && !isPrevLineTable) { const curFormats = line.formats(); const prevFormats = this.quill.getFormat(range.index - 1, 1); diff --git a/test/functional/epic.js b/test/functional/epic.js index 6cdf1d12ec..8bb512290d 100644 --- a/test/functional/epic.js +++ b/test/functional/epic.js @@ -287,41 +287,46 @@ describe('table header: ', function () { `.replace(/\s/g, ''), ); }); +}); - it('no new cell line should be add to table cell if backspace is pressed on the position after table', async function () { +describe('table:', function () { + it('cell should not be removed on typing if it is selected', async function () { const browser = await puppeteer.launch({ headless: false, }); const page = await browser.newPage(); - await page.goto('http://127.0.0.1:8080/table_header.html'); + await page.goto('http://127.0.0.1:8080/table.html'); await page.waitForSelector('.ql-editor', { timeout: 10000 }); await page.click('[data-table-cell="3"]'); - await page.keyboard.press('ArrowRight'); - await page.keyboard.press('Backspace'); + + await page.keyboard.down('Shift'); + await page.keyboard.press('ArrowLeft'); + await page.keyboard.press('ArrowLeft'); + await page.keyboard.up('Shift'); + + await page.keyboard.press('c'); const html = await page.$eval('.ql-editor', (e) => e.innerHTML); const sanitizeHtml = sanitizeTableHtml(html); expect(sanitizeHtml).toEqual( ` - + - - - + + + - +

1

2

3

1

2c



`.replace(/\s/g, ''), ); }); -}); -describe('table:', function () { - it('cell should not be removed on typing if it is selected', async function () { + it('backspace press on the position after table should remove an empty line and not add it to the cell', async function () { const browser = await puppeteer.launch({ headless: false, }); @@ -331,13 +336,40 @@ describe('table:', function () { await page.waitForSelector('.ql-editor', { timeout: 10000 }); await page.click('[data-table-cell="3"]'); + await page.keyboard.press('ArrowRight'); + await page.keyboard.press('Backspace'); - await page.keyboard.down('Shift'); - await page.keyboard.press('ArrowLeft'); - await page.keyboard.press('ArrowLeft'); - await page.keyboard.up('Shift'); + const html = await page.$eval('.ql-editor', (e) => e.innerHTML); + const sanitizeHtml = sanitizeTableHtml(html); + expect(sanitizeHtml).toEqual( + ` + + + + + + + + +

1

2

3

+ `.replace(/\s/g, ''), + ); + }); - await page.keyboard.press('c'); + it('backspace in multiline cell should work as usual', async function () { + const browser = await puppeteer.launch({ + headless: false, + }); + const page = await browser.newPage(); + + await page.goto('http://127.0.0.1:8080/table.html'); + await page.waitForSelector('.ql-editor', { timeout: 10000 }); + + await page.click('[data-table-cell="3"]'); + await page.keyboard.press('4'); + await page.keyboard.press('Enter'); + await page.keyboard.press('Backspace'); + await page.keyboard.press('Backspace'); const html = await page.$eval('.ql-editor', (e) => e.innerHTML); const sanitizeHtml = sanitizeTableHtml(html); @@ -347,8 +379,8 @@ describe('table:', function () {

1

-

2c

-


+

2

+

3

@@ -357,7 +389,7 @@ describe('table:', function () { ); }); - it('no new cell line should be add to table cell if backspace is pressed on the position after table', async function () { + it('backspace in multiline cell should work as usual', async function () { const browser = await puppeteer.launch({ headless: false, }); @@ -367,7 +399,9 @@ describe('table:', function () { await page.waitForSelector('.ql-editor', { timeout: 10000 }); await page.click('[data-table-cell="3"]'); - await page.keyboard.press('ArrowRight'); + await page.keyboard.press('4'); + await page.keyboard.press('Enter'); + await page.keyboard.press('Backspace'); await page.keyboard.press('Backspace'); const html = await page.$eval('.ql-editor', (e) => e.innerHTML); @@ -387,6 +421,40 @@ describe('table:', function () { `.replace(/\s/g, ''), ); }); + + it('backspace press on the position after table should only move a caret to cell if next line is not empty', async function () { + const browser = await puppeteer.launch({ + headless: false, + }); + const page = await browser.newPage(); + + await page.goto('http://127.0.0.1:8080/table.html'); + await page.waitForSelector('.ql-editor', { timeout: 10000 }); + + await page.click('[data-table-cell="3"]'); + await page.keyboard.press('ArrowRight'); + await page.keyboard.press('g'); + await page.keyboard.press('ArrowLeft'); + await page.keyboard.press('Backspace'); + await page.keyboard.press('w'); + + const html = await page.$eval('.ql-editor', (e) => e.innerHTML); + const sanitizeHtml = sanitizeTableHtml(html); + expect(sanitizeHtml).toEqual( + ` + + + + + + + + +

1

2

3w

+

g

+ `.replace(/\s/g, ''), + ); + }); }); function getSelectionInTextNode() { diff --git a/test/unit/modules/table_main.js b/test/unit/modules/table_main.js index ecab887bae..348717a472 100644 --- a/test/unit/modules/table_main.js +++ b/test/unit/modules/table_main.js @@ -1325,6 +1325,7 @@ describe('Table Module', function () { +


`, true, ); @@ -1339,7 +1340,7 @@ describe('Table Module', function () { -
+


`; this.quill = this.initialize(Quill, markup, this.container, { modules: { @@ -1356,6 +1357,7 @@ describe('Table Module', function () { +


`, true, ); From e1d9e8c32222e806bbc3df36cce911d57cd1c679 Mon Sep 17 00:00:00 2001 From: ksercs Date: Wed, 19 Apr 2023 17:01:58 +0400 Subject: [PATCH 11/12] remove duplicated tests --- test/functional/epic.js | 33 --------------------------------- 1 file changed, 33 deletions(-) diff --git a/test/functional/epic.js b/test/functional/epic.js index 8bb512290d..aefc54e56d 100644 --- a/test/functional/epic.js +++ b/test/functional/epic.js @@ -389,39 +389,6 @@ describe('table:', function () { ); }); - it('backspace in multiline cell should work as usual', async function () { - const browser = await puppeteer.launch({ - headless: false, - }); - const page = await browser.newPage(); - - await page.goto('http://127.0.0.1:8080/table.html'); - await page.waitForSelector('.ql-editor', { timeout: 10000 }); - - await page.click('[data-table-cell="3"]'); - await page.keyboard.press('4'); - await page.keyboard.press('Enter'); - await page.keyboard.press('Backspace'); - await page.keyboard.press('Backspace'); - - const html = await page.$eval('.ql-editor', (e) => e.innerHTML); - const sanitizeHtml = sanitizeTableHtml(html); - expect(sanitizeHtml).toEqual( - ` - - - - - - - - -

1

2

3

-


- `.replace(/\s/g, ''), - ); - }); - it('backspace press on the position after table should only move a caret to cell if next line is not empty', async function () { const browser = await puppeteer.launch({ headless: false, From 7894eb48cf40857dd6f5a659c2e0178495acd35e Mon Sep 17 00:00:00 2001 From: ksercs Date: Wed, 19 Apr 2023 17:29:51 +0400 Subject: [PATCH 12/12] fix merge --- test/functional/epic.js | 34 +++++++++++++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/test/functional/epic.js b/test/functional/epic.js index 962526b84b..e94dae1074 100644 --- a/test/functional/epic.js +++ b/test/functional/epic.js @@ -397,7 +397,7 @@ describe('table:', function () { }); const page = await browser.newPage(); - await page.goto(`${HOST}table.html`); + await page.goto(`${HOST}/table.html`); await page.waitForSelector('.ql-editor', { timeout: 10000 }); await page.click('[data-table-cell="3"]'); @@ -424,7 +424,39 @@ describe('table:', function () { `.replace(/\s/g, ''), ); }); + + it('backspace press on the position after table should remove empty line and move caret to a cell if next line is empty', async function () { + const browser = await puppeteer.launch({ + headless: false, + }); + const page = await browser.newPage(); + + await page.goto(`${HOST}/table.html`); + await page.waitForSelector('.ql-editor', { timeout: 10000 }); + + await page.click('[data-table-cell="3"]'); + await page.keyboard.press('ArrowRight'); + await page.keyboard.press('Backspace'); + await page.keyboard.press('w'); + + const html = await page.$eval('.ql-editor', (e) => e.innerHTML); + const sanitizeHtml = sanitizeTableHtml(html); + expect(sanitizeHtml).toEqual( + ` + + + + + + + + +

1

2

3w

+ `.replace(/\s/g, ''), + ); + }); }); + // Copy/paste emulation des not working on Mac. See https://github.com/puppeteer/puppeteer/issues/1313 if (!isMac) { describe('List copy/pasting into table', function () {