From 12436f694d4abf59b8adf325a43a5951b01bfaa0 Mon Sep 17 00:00:00 2001
From: Kamil Piechaczek
Date: Mon, 14 Dec 2020 11:38:53 +0100
Subject: [PATCH 01/40] Aligned to the changes in release tools.
---
package.json | 10 +++++-----
scripts/release/publish.js | 4 ++--
2 files changed, 7 insertions(+), 7 deletions(-)
diff --git a/package.json b/package.json
index 52c837196ce..a287de7eb7c 100644
--- a/package.json
+++ b/package.json
@@ -78,11 +78,11 @@
},
"devDependencies": {
"@ckeditor/ckeditor5-comments": ">=23.0.0",
- "@ckeditor/ckeditor5-dev-docs": "^23.2.0",
- "@ckeditor/ckeditor5-dev-env": "^23.2.0",
- "@ckeditor/ckeditor5-dev-tests": "^23.3.0",
- "@ckeditor/ckeditor5-dev-utils": "^23.2.0",
- "@ckeditor/ckeditor5-dev-webpack-plugin": "^23.2.0",
+ "@ckeditor/ckeditor5-dev-docs": "^24.0.0",
+ "@ckeditor/ckeditor5-dev-env": "^24.0.0",
+ "@ckeditor/ckeditor5-dev-tests": "^24.0.0",
+ "@ckeditor/ckeditor5-dev-utils": "^24.0.0",
+ "@ckeditor/ckeditor5-dev-webpack-plugin": "^24.0.0",
"@ckeditor/ckeditor5-export-pdf": ">=1.0.0",
"@ckeditor/ckeditor5-export-word": ">=1.0.0",
"@ckeditor/ckeditor5-inspector": "^2.2.1",
diff --git a/scripts/release/publish.js b/scripts/release/publish.js
index 52dbfc13908..6d69e46940c 100755
--- a/scripts/release/publish.js
+++ b/scripts/release/publish.js
@@ -28,10 +28,10 @@ require( '@ckeditor/ckeditor5-dev-env' )
cwd: process.cwd(),
packages: 'packages',
releaseBranch: 'release',
- emptyReleases: [
+ customReleases: [
'ckeditor5'
],
- packageJsonForEmptyReleases: {
+ packageJsonForCustomReleases: {
ckeditor5: {
description: 'A set of ready-to-use rich text editors created with a powerful framework. Made with real-time collaborative editing in mind.'
}
From 5e38f21d7b18706affa96ef18dfa69a3c2fb2f39 Mon Sep 17 00:00:00 2001
From: Marek Lewandowski
Date: Tue, 15 Dec 2020 11:31:07 +0100
Subject: [PATCH 02/40] Internal: Removed line length eslint rule, the string
can be simply concatenated to get around this.
---
scripts/release/publish.js | 7 ++-----
1 file changed, 2 insertions(+), 5 deletions(-)
diff --git a/scripts/release/publish.js b/scripts/release/publish.js
index 6d69e46940c..35a39776fee 100755
--- a/scripts/release/publish.js
+++ b/scripts/release/publish.js
@@ -21,8 +21,6 @@
// Use:
// npm run release:publish -- --dry-run
-/* eslint-disable max-len */
-
require( '@ckeditor/ckeditor5-dev-env' )
.releaseSubRepositories( {
cwd: process.cwd(),
@@ -33,10 +31,9 @@ require( '@ckeditor/ckeditor5-dev-env' )
],
packageJsonForCustomReleases: {
ckeditor5: {
- description: 'A set of ready-to-use rich text editors created with a powerful framework. Made with real-time collaborative editing in mind.'
+ description: 'A set of ready-to-use rich text editors created with a powerful framework.' +
+ ' Made with real-time collaborative editing in mind.'
}
},
dryRun: process.argv.includes( '--dry-run' )
} );
-
-/* eslint-enable max-len */
From 8dcde7cffa50acf6e499d1f77feb538e38a74277 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Magdalena=20Chrze=C5=9Bcian?=
Date: Tue, 15 Dec 2020 12:28:11 +0100
Subject: [PATCH 03/40] South position added to drop down panel positions.
---
.../src/ui/specialcharactersnavigationview.js | 1 -
packages/ckeditor5-ui/src/dropdown/dropdownview.js | 13 ++++++++++---
.../theme/components/dropdown/dropdown.css | 7 ++++++-
3 files changed, 16 insertions(+), 5 deletions(-)
diff --git a/packages/ckeditor5-special-characters/src/ui/specialcharactersnavigationview.js b/packages/ckeditor5-special-characters/src/ui/specialcharactersnavigationview.js
index fe63efa957d..ee802418fef 100644
--- a/packages/ckeditor5-special-characters/src/ui/specialcharactersnavigationview.js
+++ b/packages/ckeditor5-special-characters/src/ui/specialcharactersnavigationview.js
@@ -43,7 +43,6 @@ export default class SpecialCharactersNavigationView extends FormHeaderView {
* @member {module:ui/dropdown/dropdownview~DropdownView}
*/
this.groupDropdownView = this._createGroupDropdown( groupNames );
- this.groupDropdownView.panelPosition = locale.uiLanguageDirection === 'rtl' ? 'se' : 'sw';
/**
* @inheritDoc
diff --git a/packages/ckeditor5-ui/src/dropdown/dropdownview.js b/packages/ckeditor5-ui/src/dropdown/dropdownview.js
index 8c836244826..5b66eb8ec06 100644
--- a/packages/ckeditor5-ui/src/dropdown/dropdownview.js
+++ b/packages/ckeditor5-ui/src/dropdown/dropdownview.js
@@ -313,12 +313,12 @@ export default class DropdownView extends View {
* @private
*/
get _panelPositions() {
- const { southEast, southWest, northEast, northWest } = DropdownView.defaultPanelPositions;
+ const { south, southEast, southWest, northEast, northWest } = DropdownView.defaultPanelPositions;
if ( this.locale.uiLanguageDirection === 'ltr' ) {
- return [ southEast, southWest, northEast, northWest ];
+ return [ southEast, southWest, northEast, northWest, south ];
} else {
- return [ southWest, southEast, northWest, northEast ];
+ return [ southWest, southEast, northWest, northEast, south ];
}
}
}
@@ -372,6 +372,13 @@ export default class DropdownView extends View {
* @member {Object} module:ui/dropdown/dropdownview~DropdownView.defaultPanelPositions
*/
DropdownView.defaultPanelPositions = {
+ south: ( buttonRect, panelRect ) => {
+ return {
+ top: buttonRect.bottom,
+ left: buttonRect.left - panelRect.width / 2 + buttonRect.width / 2,
+ name: 's'
+ };
+ },
southEast: buttonRect => {
return {
top: buttonRect.bottom,
diff --git a/packages/ckeditor5-ui/theme/components/dropdown/dropdown.css b/packages/ckeditor5-ui/theme/components/dropdown/dropdown.css
index e8a2bb90e6a..e7e58057316 100644
--- a/packages/ckeditor5-ui/theme/components/dropdown/dropdown.css
+++ b/packages/ckeditor5-ui/theme/components/dropdown/dropdown.css
@@ -45,7 +45,8 @@
}
&.ck-dropdown__panel_se,
- &.ck-dropdown__panel_sw {
+ &.ck-dropdown__panel_sw,
+ &.ck-dropdown__panel_s {
/*
* Using transform: translate3d( 0, 100%, 0 ) causes blurry dropdown on Chrome 67-78+ on non-retina displays.
* See https://github.com/ckeditor/ckeditor5/issues/1053.
@@ -63,6 +64,10 @@
&.ck-dropdown__panel_sw {
right: 0px;
}
+
+ &.ck-dropdown__panel_s {
+ transform: translateX(-50%)
+ }
}
}
From 8f2b6df2c93a51533330929817feed9f28dbbe12 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Magdalena=20Chrze=C5=9Bcian?=
Date: Tue, 15 Dec 2020 13:04:36 +0100
Subject: [PATCH 04/40] South position added to docs.
---
packages/ckeditor5-ui/src/dropdown/dropdownview.js | 7 +++++++
1 file changed, 7 insertions(+)
diff --git a/packages/ckeditor5-ui/src/dropdown/dropdownview.js b/packages/ckeditor5-ui/src/dropdown/dropdownview.js
index 5b66eb8ec06..4f820de92dc 100644
--- a/packages/ckeditor5-ui/src/dropdown/dropdownview.js
+++ b/packages/ckeditor5-ui/src/dropdown/dropdownview.js
@@ -333,6 +333,13 @@ export default class DropdownView extends View {
*
* **South**
*
+ * * `south`
+ *
+ * [ Button ]
+ * +-----------------+
+ * | Panel |
+ * +-----------------+
+ *
* * `southEast`
*
* [ Button ]
From 44f8845ff0c36a91dd67e91268a11353f1d9f391 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Magdalena=20Chrze=C5=9Bcian?=
Date: Tue, 15 Dec 2020 14:55:36 +0100
Subject: [PATCH 05/40] Gropup dropdown position reverted.
---
.../src/ui/specialcharactersnavigationview.js | 1 +
1 file changed, 1 insertion(+)
diff --git a/packages/ckeditor5-special-characters/src/ui/specialcharactersnavigationview.js b/packages/ckeditor5-special-characters/src/ui/specialcharactersnavigationview.js
index ee802418fef..fe63efa957d 100644
--- a/packages/ckeditor5-special-characters/src/ui/specialcharactersnavigationview.js
+++ b/packages/ckeditor5-special-characters/src/ui/specialcharactersnavigationview.js
@@ -43,6 +43,7 @@ export default class SpecialCharactersNavigationView extends FormHeaderView {
* @member {module:ui/dropdown/dropdownview~DropdownView}
*/
this.groupDropdownView = this._createGroupDropdown( groupNames );
+ this.groupDropdownView.panelPosition = locale.uiLanguageDirection === 'rtl' ? 'se' : 'sw';
/**
* @inheritDoc
From 02f9a3f706db59f084d9af7de6ff9dc52290ff9f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Magdalena=20Chrze=C5=9Bcian?=
Date: Tue, 15 Dec 2020 16:59:44 +0100
Subject: [PATCH 06/40] Existing tests adjusted to the new dropdown position.
---
packages/ckeditor5-ui/tests/dropdown/dropdownview.js | 10 +++++-----
1 file changed, 5 insertions(+), 5 deletions(-)
diff --git a/packages/ckeditor5-ui/tests/dropdown/dropdownview.js b/packages/ckeditor5-ui/tests/dropdown/dropdownview.js
index 15429ff62dc..21662a83f9c 100644
--- a/packages/ckeditor5-ui/tests/dropdown/dropdownview.js
+++ b/packages/ckeditor5-ui/tests/dropdown/dropdownview.js
@@ -135,7 +135,7 @@ describe( 'DropdownView', () => {
describe( 'in "auto" mode', () => {
it( 'uses _getOptimalPosition() and a dedicated set of positions (LTR)', () => {
const spy = testUtils.sinon.spy( DropdownView, '_getOptimalPosition' );
- const { southEast, southWest, northEast, northWest } = DropdownView.defaultPanelPositions;
+ const { southEast, southWest, northEast, northWest, south } = DropdownView.defaultPanelPositions;
view.isOpen = true;
@@ -143,7 +143,7 @@ describe( 'DropdownView', () => {
element: panelView.element,
target: buttonView.element,
positions: [
- southEast, southWest, northEast, northWest
+ southEast, southWest, northEast, northWest, south
],
fitInViewport: true
} ) );
@@ -151,7 +151,7 @@ describe( 'DropdownView', () => {
it( 'uses _getOptimalPosition() and a dedicated set of positions (RTL)', () => {
const spy = testUtils.sinon.spy( DropdownView, '_getOptimalPosition' );
- const { southEast, southWest, northEast, northWest } = DropdownView.defaultPanelPositions;
+ const { southEast, southWest, northEast, northWest, south } = DropdownView.defaultPanelPositions;
view.locale.uiLanguageDirection = 'rtl';
view.isOpen = true;
@@ -160,7 +160,7 @@ describe( 'DropdownView', () => {
element: panelView.element,
target: buttonView.element,
positions: [
- southWest, southEast, northWest, northEast
+ southWest, southEast, northWest, northEast, south
],
fitInViewport: true
} ) );
@@ -352,7 +352,7 @@ describe( 'DropdownView', () => {
} );
it( 'should have a proper length', () => {
- expect( Object.keys( positions ) ).to.have.length( 4 );
+ expect( Object.keys( positions ) ).to.have.length( 5 );
} );
it( 'should define the "southEast" position', () => {
From e60d60bc7f86aa195011f60b0615b7e7ca2746f4 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Magdalena=20Chrze=C5=9Bcian?=
Date: Tue, 15 Dec 2020 17:20:34 +0100
Subject: [PATCH 07/40] South dropdown horizontal position respects button
width.
---
packages/ckeditor5-ui/theme/components/dropdown/dropdown.css | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/packages/ckeditor5-ui/theme/components/dropdown/dropdown.css b/packages/ckeditor5-ui/theme/components/dropdown/dropdown.css
index e7e58057316..e2f9f0965f5 100644
--- a/packages/ckeditor5-ui/theme/components/dropdown/dropdown.css
+++ b/packages/ckeditor5-ui/theme/components/dropdown/dropdown.css
@@ -66,7 +66,8 @@
}
&.ck-dropdown__panel_s {
- transform: translateX(-50%)
+ transform: translateX(-50%);
+ left: 50%
}
}
}
From 47c552270779b13d2b6f45a52b2e7ecaa9ad2d44 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Magdalena=20Chrze=C5=9Bcian?=
Date: Tue, 15 Dec 2020 17:21:38 +0100
Subject: [PATCH 08/40] Test for definig the south position.
---
packages/ckeditor5-ui/tests/dropdown/dropdownview.js | 8 ++++++++
1 file changed, 8 insertions(+)
diff --git a/packages/ckeditor5-ui/tests/dropdown/dropdownview.js b/packages/ckeditor5-ui/tests/dropdown/dropdownview.js
index 21662a83f9c..45e63ea220f 100644
--- a/packages/ckeditor5-ui/tests/dropdown/dropdownview.js
+++ b/packages/ckeditor5-ui/tests/dropdown/dropdownview.js
@@ -355,6 +355,14 @@ describe( 'DropdownView', () => {
expect( Object.keys( positions ) ).to.have.length( 5 );
} );
+ it( 'should define the "south" position', () => {
+ expect( positions.south( buttonRect, panelRect ) ).to.deep.equal( {
+ top: 200,
+ left: 125,
+ name: 's'
+ } );
+ } );
+
it( 'should define the "southEast" position', () => {
expect( positions.southEast( buttonRect, panelRect ) ).to.deep.equal( {
top: 200,
From a45220e984c028c8bde68445a961af09f0c0ef78 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Magdalena=20Chrze=C5=9Bcian?=
Date: Wed, 16 Dec 2020 13:38:49 +0100
Subject: [PATCH 09/40] Added new positions and max-width for dropdowns.
---
.../ckeditor5-ui/src/dropdown/dropdownview.js | 48 +++++++++++++++++--
.../theme/components/dropdown/dropdown.css | 36 ++++++++++++--
2 files changed, 78 insertions(+), 6 deletions(-)
diff --git a/packages/ckeditor5-ui/src/dropdown/dropdownview.js b/packages/ckeditor5-ui/src/dropdown/dropdownview.js
index 4f820de92dc..b9493952750 100644
--- a/packages/ckeditor5-ui/src/dropdown/dropdownview.js
+++ b/packages/ckeditor5-ui/src/dropdown/dropdownview.js
@@ -313,12 +313,19 @@ export default class DropdownView extends View {
* @private
*/
get _panelPositions() {
- const { south, southEast, southWest, northEast, northWest } = DropdownView.defaultPanelPositions;
+ const { south, north,
+ southEast, southWest,
+ northEast, northWest,
+ southMiddleEast, southMiddleWest,
+ northMiddleEast, northhMiddleWest
+ } = DropdownView.defaultPanelPositions;
if ( this.locale.uiLanguageDirection === 'ltr' ) {
- return [ southEast, southWest, northEast, northWest, south ];
+ return [ southEast, southMiddleEast, southMiddleWest, southWest, south,
+ northEast, northMiddleEast, northhMiddleWest, northWest, north ];
} else {
- return [ southWest, southEast, northWest, northEast, south ];
+ return [ southWest, southMiddleWest, southMiddleEast, southEast, south,
+ northWest, northhMiddleWest, northMiddleEast, northEast, north ];
}
}
}
@@ -400,6 +407,27 @@ DropdownView.defaultPanelPositions = {
name: 'sw'
};
},
+ southMiddleEast: ( buttonRect, panelRect ) => {
+ return {
+ top: buttonRect.bottom,
+ left: buttonRect.left - panelRect.width / 4 + buttonRect.width / 2,
+ name: 'sme'
+ };
+ },
+ southMiddleWest: ( buttonRect, panelRect ) => {
+ return {
+ top: buttonRect.bottom,
+ left: buttonRect.left - panelRect.width * 3 / 4 + buttonRect.width / 2,
+ name: 'smw'
+ };
+ },
+ north: ( buttonRect, panelRect ) => {
+ return {
+ top: buttonRect.top - panelRect.height,
+ left: buttonRect.left - panelRect.width / 2 + buttonRect.width / 2,
+ name: 'n'
+ };
+ },
northEast: ( buttonRect, panelRect ) => {
return {
top: buttonRect.top - panelRect.height,
@@ -413,6 +441,20 @@ DropdownView.defaultPanelPositions = {
left: buttonRect.left - panelRect.width + buttonRect.width,
name: 'nw'
};
+ },
+ northMiddleEast: ( buttonRect, panelRect ) => {
+ return {
+ top: buttonRect.top - panelRect.height,
+ left: buttonRect.left - panelRect.width / 4 + buttonRect.width / 2,
+ name: 'nme'
+ };
+ },
+ northhMiddleWest: ( buttonRect, panelRect ) => {
+ return {
+ top: buttonRect.top - panelRect.height,
+ left: buttonRect.left - panelRect.width * 3 / 4 + buttonRect.width / 2,
+ name: 'nmw'
+ };
}
};
diff --git a/packages/ckeditor5-ui/theme/components/dropdown/dropdown.css b/packages/ckeditor5-ui/theme/components/dropdown/dropdown.css
index e2f9f0965f5..a6895a4b9f4 100644
--- a/packages/ckeditor5-ui/theme/components/dropdown/dropdown.css
+++ b/packages/ckeditor5-ui/theme/components/dropdown/dropdown.css
@@ -5,6 +5,10 @@
@import "../tooltip/mixins/_tooltip.css";
+:root {
+ --ck-dropdown-max-width: 75vw;
+}
+
.ck.ck-dropdown {
display: inline-block;
position: relative;
@@ -32,6 +36,7 @@
display: none;
z-index: var(--ck-z-modal);
+ max-width: var(--ck-dropdown-max-width);
position: absolute;
@@ -40,12 +45,17 @@
}
&.ck-dropdown__panel_ne,
- &.ck-dropdown__panel_nw {
+ &.ck-dropdown__panel_nw,
+ &.ck-dropdown__panel_n,
+ &.ck-dropdown__panel_nmw,
+ &.ck-dropdown__panel_nme {
bottom: 100%;
}
&.ck-dropdown__panel_se,
&.ck-dropdown__panel_sw,
+ &.ck-dropdown__panel_smw,
+ &.ck-dropdown__panel_sme,
&.ck-dropdown__panel_s {
/*
* Using transform: translate3d( 0, 100%, 0 ) causes blurry dropdown on Chrome 67-78+ on non-retina displays.
@@ -65,9 +75,29 @@
right: 0px;
}
- &.ck-dropdown__panel_s {
+ &.ck-dropdown__panel_s,
+ &.ck-dropdown__panel_smw,
+ &.ck-dropdown__panel_sme,
+ &.ck-dropdown__panel_n,
+ &.ck-dropdown__panel_nmw,
+ &.ck-dropdown__panel_nme {
+ /* Positioning all panels not aligned to button edge relatively to the center of the button */
+ left: 50%;
+ }
+
+ &.ck-dropdown__panel_s,
+ &.ck-dropdown__panel_n {
transform: translateX(-50%);
- left: 50%
+ }
+
+ &.ck-dropdown__panel_smw,
+ &.ck-dropdown__panel_nmw {
+ transform: translateX(-75%);
+ }
+
+ &.ck-dropdown__panel_sme,
+ &.ck-dropdown__panel_nme {
+ transform: translateX(-25%);
}
}
}
From c33539e68ee5207b8e60d50a76190a9364e9c8b2 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Magdalena=20Chrze=C5=9Bcian?=
Date: Wed, 16 Dec 2020 13:41:38 +0100
Subject: [PATCH 10/40] Specyfiing toolbar dropdown selector, so that max-with
doesn't affect nested dropdowns.
---
.../components/dropdown/toolbardropdown.css | 16 +++++++---------
1 file changed, 7 insertions(+), 9 deletions(-)
diff --git a/packages/ckeditor5-ui/theme/components/dropdown/toolbardropdown.css b/packages/ckeditor5-ui/theme/components/dropdown/toolbardropdown.css
index 10ba58514e2..57e4f6376fa 100644
--- a/packages/ckeditor5-ui/theme/components/dropdown/toolbardropdown.css
+++ b/packages/ckeditor5-ui/theme/components/dropdown/toolbardropdown.css
@@ -7,16 +7,14 @@
--ck-toolbar-dropdown-max-width: 60vw;
}
-.ck.ck-toolbar-dropdown {
- & .ck-dropdown__panel {
- /* https://github.com/ckeditor/ckeditor5/issues/5586 */
- width: max-content;
- max-width: var(--ck-toolbar-dropdown-max-width);
+.ck.ck-toolbar-dropdown > .ck-dropdown__panel {
+ /* https://github.com/ckeditor/ckeditor5/issues/5586 */
+ width: max-content;
+ max-width: var(--ck-toolbar-dropdown-max-width);
- & .ck-button {
- &:focus {
- z-index: calc(var(--ck-z-default) + 1);
- }
+ & .ck-button {
+ &:focus {
+ z-index: calc(var(--ck-z-default) + 1);
}
}
}
From 5eb0d2612a2cf9d8c912405aaec89c325fb01a18 Mon Sep 17 00:00:00 2001
From: godai78
Date: Wed, 23 Dec 2020 10:51:05 +0100
Subject: [PATCH 11/40] Docs: WProofreader guide update.
---
docs/_snippets/features/wproofreader.html | 2 +-
docs/_snippets/features/wproofreader.js | 2 ++
.../features/spelling-and-grammar-checking.md | 20 ++++++++++++++++---
3 files changed, 20 insertions(+), 4 deletions(-)
diff --git a/docs/_snippets/features/wproofreader.html b/docs/_snippets/features/wproofreader.html
index 9d3d9fe16e0..d37be14ca54 100644
--- a/docs/_snippets/features/wproofreader.html
+++ b/docs/_snippets/features/wproofreader.html
@@ -1,4 +1,4 @@
-
Typos hapen. We striving to correct them. Hover on the marked words for instant correction suggestions or click the dialog icon in the bottom right corner to have the whole text proofread at once.
+
Typos hapen. We striving to correct them. Hover on the marked words for instant correction suggestions or click the dialoge icon in the bottom right corner to have the whole text proofread at once.
You can also paste your own text here to have its spelling and grammar checked.
diff --git a/docs/_snippets/features/wproofreader.js b/docs/_snippets/features/wproofreader.js
index 59e16fcdbb6..6944743aabd 100644
--- a/docs/_snippets/features/wproofreader.js
+++ b/docs/_snippets/features/wproofreader.js
@@ -38,6 +38,8 @@ ClassicEditor
'mediaEmbed',
'insertTable',
'|',
+ 'wproofreader',
+ '|',
'undo',
'redo'
],
diff --git a/docs/features/spelling-and-grammar-checking.md b/docs/features/spelling-and-grammar-checking.md
index c8a8724c0ae..a4de5f1ef88 100644
--- a/docs/features/spelling-and-grammar-checking.md
+++ b/docs/features/spelling-and-grammar-checking.md
@@ -9,13 +9,21 @@ menu-title: Spelling and grammar checking
The spell checker for CKEditor 5 is a commercial solution provided by our partner, [WebSpellChecker](https://webspellchecker.com/). You can report any issues in its [GitHub repository](https://github.com/WebSpellChecker/wproofreader). The license can be purchased [here](https://ckeditor.com/contact/).
-[WProofreader](https://webspellchecker.com/wsc-proofreader) is an innovative proofreading tool that combines the functionality of "spell check as you type" and "spell check in a dialog" in a modern, distraction-free UI. Spelling and grammar suggestions are available on hover with no clicking needed.
+[WProofreader](https://webspellchecker.com/wsc-proofreader) is an innovative, multi-language proofreading tool that combines the functionality of "spell check as you type" and "spell check in a dialog" in a modern, distraction-free UI. Spelling and grammar suggestions are available on hover with no clicking needed or as a convenient dialog, both with additional in-place replacement suggestions.
+
+Readily available settings include several rules of including or ommiting certain cases. You can choose from a set if predefined languages (more may be added as language packs) and manage additional dictionaries. Words can be added to the user dictionary directly from the suggestion card, too.
+
+If needed, the spellchecker can be easily disabled and enabled again with a click.
## Demo
-Click in the editor below to enable the spelling and grammar checking. Hover an underlined word to display the proofreader suggestions for any of the spelling and grammar mistakes found.
+See the spelling and grammar checking in the editor below.
-The proofreader badge in the bottom right-hand corner shows you the number of mistakes detected. It also gives you access to proofreader settings. If you want to see an overview of all spelling and grammar mistakes, click the "Proofread in dialog" icon in the badge.
+The proofreader badge in the bottom right corner shows you the number of mistakes detected. Hover on an underlined word to display the proofreader suggestions for any of the spelling and grammar mistakes found. If you want to see an overview of all spelling and grammar mistakes, click the "Proofread in dialog" option in the toolbar dropdown. You can access the proofreader settings from the toolbar, too.
+
+
+ The toolbar button has been introduced in version 2.x of the WProofreader. If you are still using version 1.x, the available settings and dialog options are located in the bottom-right indicator.
+
{@snippet features/wproofreader}
@@ -67,6 +75,7 @@ import WProofreader from '@webspellchecker/wproofreader-ckeditor5/src/wproofread
ClassicEditor
.create( editorElement, {
plugins: [ ..., WProofreader ],
+ toolbar: [ ..., 'wproofreader' ]
wproofreader: {
serviceId: 'your-service-ID',
srcUrl: 'https://svc.webspellchecker.net/spellcheck31/wscbundle/wscbundle.js'
@@ -74,6 +83,10 @@ ClassicEditor
} )
```
+
+ Please notice that the toolbar item is only added in versions 2.x or higher. Read more about configuring UI items in the {@link features/toolbar toolbar guide}. For version 1.x simply ommit adding the item to the toolbar configuration.
+
+
Refer to the [official documentation](https://github.com/WebSpellChecker/wproofreader-ckeditor5#install-instructions) for more details about the cloud setup and available configuration options.
### WProofreader Server
@@ -89,6 +102,7 @@ import WProofreader from '@webspellchecker/wproofreader-ckeditor5/src/wproofread
ClassicEditor
.create( editorElement, {
plugins: [ ..., WProofreader ],
+ toolbar: [ ..., 'wproofreader' ]
wproofreader: {
serviceProtocol: 'https',
serviceHost: 'localhost',
From 9e2716a133a4b675013eff69bed4fbb8ca535ba0 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Magdalena=20Chrze=C5=9Bcian?=
Date: Wed, 23 Dec 2020 18:58:29 +0100
Subject: [PATCH 12/40] Special characters grid fixed (#8669)
---
packages/ckeditor5-special-characters/theme/charactergrid.css | 3 ++-
.../theme/ckeditor5-special-characters/charactergrid.css | 1 +
packages/ckeditor5-ui/theme/components/dropdown/dropdown.css | 2 +-
3 files changed, 4 insertions(+), 2 deletions(-)
diff --git a/packages/ckeditor5-special-characters/theme/charactergrid.css b/packages/ckeditor5-special-characters/theme/charactergrid.css
index 559143dff25..0974429841b 100644
--- a/packages/ckeditor5-special-characters/theme/charactergrid.css
+++ b/packages/ckeditor5-special-characters/theme/charactergrid.css
@@ -4,8 +4,9 @@
*/
.ck.ck-character-grid {
+ max-width: 100%;
+
& .ck-character-grid__tiles {
display: grid;
- grid-template-columns: repeat( 10, 1fr );
}
}
diff --git a/packages/ckeditor5-theme-lark/theme/ckeditor5-special-characters/charactergrid.css b/packages/ckeditor5-theme-lark/theme/ckeditor5-special-characters/charactergrid.css
index c9a41554f83..9b9a781f81d 100644
--- a/packages/ckeditor5-theme-lark/theme/ckeditor5-special-characters/charactergrid.css
+++ b/packages/ckeditor5-theme-lark/theme/ckeditor5-special-characters/charactergrid.css
@@ -16,6 +16,7 @@
max-height: 200px;
& .ck-character-grid__tiles {
+ grid-template-columns: repeat(auto-fit, minmax(var(--ck-character-grid-tile-size), 1fr));
margin: var(--ck-spacing-standard) var(--ck-spacing-large);
grid-gap: var(--ck-spacing-standard);
}
diff --git a/packages/ckeditor5-ui/theme/components/dropdown/dropdown.css b/packages/ckeditor5-ui/theme/components/dropdown/dropdown.css
index a6895a4b9f4..f8e4e71c3fd 100644
--- a/packages/ckeditor5-ui/theme/components/dropdown/dropdown.css
+++ b/packages/ckeditor5-ui/theme/components/dropdown/dropdown.css
@@ -81,7 +81,7 @@
&.ck-dropdown__panel_n,
&.ck-dropdown__panel_nmw,
&.ck-dropdown__panel_nme {
- /* Positioning all panels not aligned to button edge relatively to the center of the button */
+ /* Positioning panels relatively to the center of the button */
left: 50%;
}
From ad963f8c2911f2e86e44cf03498dd36ed269d403 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Magdalena=20Chrze=C5=9Bcian?=
Date: Wed, 23 Dec 2020 19:00:13 +0100
Subject: [PATCH 13/40] Added the documentation and set proper order of the
dropdown positions.
---
.../src/dropdown/dropdownpanelview.js | 2 +-
.../ckeditor5-ui/src/dropdown/dropdownview.js | 56 ++++++++++++++++---
2 files changed, 49 insertions(+), 9 deletions(-)
diff --git a/packages/ckeditor5-ui/src/dropdown/dropdownpanelview.js b/packages/ckeditor5-ui/src/dropdown/dropdownpanelview.js
index cdef5846f31..c12e7bebef1 100644
--- a/packages/ckeditor5-ui/src/dropdown/dropdownpanelview.js
+++ b/packages/ckeditor5-ui/src/dropdown/dropdownpanelview.js
@@ -41,7 +41,7 @@ export default class DropdownPanelView extends View {
*
* @observable
* @default 'se'
- * @member {'se'|'sw'|'ne'|'nw'} #position
+ * @member {'s'|'se'|'sw'|'sme'|'smw'|'n'|'ne'|'nw'|'nme'|'nmw'} #position
*/
this.set( 'position', 'se' );
diff --git a/packages/ckeditor5-ui/src/dropdown/dropdownview.js b/packages/ckeditor5-ui/src/dropdown/dropdownview.js
index b9493952750..39aa46bc56d 100644
--- a/packages/ckeditor5-ui/src/dropdown/dropdownview.js
+++ b/packages/ckeditor5-ui/src/dropdown/dropdownview.js
@@ -150,7 +150,7 @@ export default class DropdownView extends View {
*
* @observable
* @default 'auto'
- * @member {'auto'|'se'|'sw'|'ne'|'nw'} #panelPosition
+ * @member {'auto'|'s'|'se'|'sw'|'sme'|'smw'|'n'|'ne'|'nw'|'nme'|'nmw'} #panelPosition
*/
this.set( 'panelPosition', 'auto' );
@@ -313,19 +313,24 @@ export default class DropdownView extends View {
* @private
*/
get _panelPositions() {
- const { south, north,
+ const {
+ south, north,
southEast, southWest,
northEast, northWest,
southMiddleEast, southMiddleWest,
- northMiddleEast, northhMiddleWest
+ northMiddleEast, northMiddleWest
} = DropdownView.defaultPanelPositions;
- if ( this.locale.uiLanguageDirection === 'ltr' ) {
- return [ southEast, southMiddleEast, southMiddleWest, southWest, south,
- northEast, northMiddleEast, northhMiddleWest, northWest, north ];
+ if ( this.locale.uiLanguageDirection !== 'rtl' ) {
+ return [
+ southEast, southWest, southMiddleEast, southMiddleWest, south,
+ northEast, northWest, northMiddleEast, northMiddleWest, north
+ ];
} else {
- return [ southWest, southMiddleWest, southMiddleEast, southEast, south,
- northWest, northhMiddleWest, northMiddleEast, northEast, north ];
+ return [
+ southWest, southEast, southMiddleWest, southMiddleEast, south,
+ northWest, northEast, northMiddleWest, northMiddleEast, north
+ ];
}
}
}
@@ -361,8 +366,29 @@ export default class DropdownView extends View {
* | Panel |
* +-----------------+
*
+ * * `southMiddleEast`
+ *
+ * [ Button ]
+ * +-----------------+
+ * | Panel |
+ * +-----------------+
+ *
+ * * `southMiddleWest`
+ *
+ * [ Button ]
+ * +-----------------+
+ * | Panel |
+ * +-----------------+
+ *
* **North**
*
+ * * `north`
+ *
+ * +-----------------+
+ * | Panel |
+ * +-----------------+
+ * [ Button ]
+ *
* * `northEast`
*
* +-----------------+
@@ -377,6 +403,20 @@ export default class DropdownView extends View {
* +-----------------+
* [ Button ]
*
+ * * `northMiddleEast`
+ *
+ * +-----------------+
+ * | Panel |
+ * +-----------------+
+ * [ Button ]
+ *
+ * * `northMiddleWest`
+ *
+ * +-----------------+
+ * | Panel |
+ * +-----------------+
+ * [ Button ]
+ *
* Positioning functions are compatible with {@link module:utils/dom/position~Position}.
*
* The name that position function returns will be reflected in dropdown panel's class that
From 27c7325fdd489340a05492ca8ed2bce0e6a19715 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Magdalena=20Chrze=C5=9Bcian?=
Date: Wed, 23 Dec 2020 19:04:08 +0100
Subject: [PATCH 14/40] Added tests to new dropdown positions, fixed and
simplified the math.
---
.../ckeditor5-ui/src/dropdown/dropdownview.js | 16 ++--
.../tests/dropdown/dropdownview.js | 80 ++++++++++++++++---
2 files changed, 75 insertions(+), 21 deletions(-)
diff --git a/packages/ckeditor5-ui/src/dropdown/dropdownview.js b/packages/ckeditor5-ui/src/dropdown/dropdownview.js
index 39aa46bc56d..8d7cecd0746 100644
--- a/packages/ckeditor5-ui/src/dropdown/dropdownview.js
+++ b/packages/ckeditor5-ui/src/dropdown/dropdownview.js
@@ -429,7 +429,7 @@ DropdownView.defaultPanelPositions = {
south: ( buttonRect, panelRect ) => {
return {
top: buttonRect.bottom,
- left: buttonRect.left - panelRect.width / 2 + buttonRect.width / 2,
+ left: buttonRect.left - ( panelRect.width - buttonRect.width ) / 2,
name: 's'
};
},
@@ -450,21 +450,21 @@ DropdownView.defaultPanelPositions = {
southMiddleEast: ( buttonRect, panelRect ) => {
return {
top: buttonRect.bottom,
- left: buttonRect.left - panelRect.width / 4 + buttonRect.width / 2,
+ left: buttonRect.left - ( panelRect.width - buttonRect.width ) / 4,
name: 'sme'
};
},
southMiddleWest: ( buttonRect, panelRect ) => {
return {
top: buttonRect.bottom,
- left: buttonRect.left - panelRect.width * 3 / 4 + buttonRect.width / 2,
+ left: buttonRect.left - ( panelRect.width - buttonRect.width ) * 3 / 4,
name: 'smw'
};
},
north: ( buttonRect, panelRect ) => {
return {
top: buttonRect.top - panelRect.height,
- left: buttonRect.left - panelRect.width / 2 + buttonRect.width / 2,
+ left: buttonRect.left - ( panelRect.width - buttonRect.width ) / 2,
name: 'n'
};
},
@@ -477,7 +477,7 @@ DropdownView.defaultPanelPositions = {
},
northWest: ( buttonRect, panelRect ) => {
return {
- top: buttonRect.bottom - panelRect.height,
+ top: buttonRect.top - panelRect.height,
left: buttonRect.left - panelRect.width + buttonRect.width,
name: 'nw'
};
@@ -485,14 +485,14 @@ DropdownView.defaultPanelPositions = {
northMiddleEast: ( buttonRect, panelRect ) => {
return {
top: buttonRect.top - panelRect.height,
- left: buttonRect.left - panelRect.width / 4 + buttonRect.width / 2,
+ left: buttonRect.left - ( panelRect.width - buttonRect.width ) / 4,
name: 'nme'
};
},
- northhMiddleWest: ( buttonRect, panelRect ) => {
+ northMiddleWest: ( buttonRect, panelRect ) => {
return {
top: buttonRect.top - panelRect.height,
- left: buttonRect.left - panelRect.width * 3 / 4 + buttonRect.width / 2,
+ left: buttonRect.left - ( panelRect.width - buttonRect.width ) * 3 / 4,
name: 'nmw'
};
}
diff --git a/packages/ckeditor5-ui/tests/dropdown/dropdownview.js b/packages/ckeditor5-ui/tests/dropdown/dropdownview.js
index 45e63ea220f..49329e1357f 100644
--- a/packages/ckeditor5-ui/tests/dropdown/dropdownview.js
+++ b/packages/ckeditor5-ui/tests/dropdown/dropdownview.js
@@ -135,7 +135,13 @@ describe( 'DropdownView', () => {
describe( 'in "auto" mode', () => {
it( 'uses _getOptimalPosition() and a dedicated set of positions (LTR)', () => {
const spy = testUtils.sinon.spy( DropdownView, '_getOptimalPosition' );
- const { southEast, southWest, northEast, northWest, south } = DropdownView.defaultPanelPositions;
+ const {
+ south, north,
+ southEast, southWest,
+ northEast, northWest,
+ southMiddleEast, southMiddleWest,
+ northMiddleEast, northMiddleWest
+ } = DropdownView.defaultPanelPositions;
view.isOpen = true;
@@ -143,7 +149,8 @@ describe( 'DropdownView', () => {
element: panelView.element,
target: buttonView.element,
positions: [
- southEast, southWest, northEast, northWest, south
+ southEast, southWest, southMiddleEast, southMiddleWest, south,
+ northEast, northWest, northMiddleEast, northMiddleWest, north
],
fitInViewport: true
} ) );
@@ -151,7 +158,13 @@ describe( 'DropdownView', () => {
it( 'uses _getOptimalPosition() and a dedicated set of positions (RTL)', () => {
const spy = testUtils.sinon.spy( DropdownView, '_getOptimalPosition' );
- const { southEast, southWest, northEast, northWest, south } = DropdownView.defaultPanelPositions;
+ const {
+ south, north,
+ southEast, southWest,
+ northEast, northWest,
+ southMiddleEast, southMiddleWest,
+ northMiddleEast, northMiddleWest
+ } = DropdownView.defaultPanelPositions;
view.locale.uiLanguageDirection = 'rtl';
view.isOpen = true;
@@ -160,7 +173,8 @@ describe( 'DropdownView', () => {
element: panelView.element,
target: buttonView.element,
positions: [
- southWest, southEast, northWest, northEast, south
+ southWest, southEast, southMiddleWest, southMiddleEast, south,
+ northWest, northEast, northMiddleWest, northMiddleEast, north
],
fitInViewport: true
} ) );
@@ -335,7 +349,7 @@ describe( 'DropdownView', () => {
buttonRect = {
top: 100,
bottom: 200,
- left: 100,
+ left: 500,
right: 200,
width: 100,
height: 100
@@ -346,19 +360,19 @@ describe( 'DropdownView', () => {
bottom: 0,
left: 0,
right: 0,
- width: 50,
+ width: 400,
height: 50
};
} );
it( 'should have a proper length', () => {
- expect( Object.keys( positions ) ).to.have.length( 5 );
+ expect( Object.keys( positions ) ).to.have.length( 10 );
} );
it( 'should define the "south" position', () => {
expect( positions.south( buttonRect, panelRect ) ).to.deep.equal( {
top: 200,
- left: 125,
+ left: 350,
name: 's'
} );
} );
@@ -366,7 +380,7 @@ describe( 'DropdownView', () => {
it( 'should define the "southEast" position', () => {
expect( positions.southEast( buttonRect, panelRect ) ).to.deep.equal( {
top: 200,
- left: 100,
+ left: 500,
name: 'se'
} );
} );
@@ -374,25 +388,65 @@ describe( 'DropdownView', () => {
it( 'should define the "southWest" position', () => {
expect( positions.southWest( buttonRect, panelRect ) ).to.deep.equal( {
top: 200,
- left: 150,
+ left: 200,
name: 'sw'
} );
} );
+ it( 'should define the "southMiddleEast" position', () => {
+ expect( positions.southMiddleEast( buttonRect, panelRect ) ).to.deep.equal( {
+ top: 200,
+ left: 425,
+ name: 'sme'
+ } );
+ } );
+
+ it( 'should define the "southMiddleWest" position', () => {
+ expect( positions.southMiddleWest( buttonRect, panelRect ) ).to.deep.equal( {
+ top: 200,
+ left: 275,
+ name: 'smw'
+ } );
+ } );
+
+ it( 'should define the "north" position', () => {
+ expect( positions.north( buttonRect, panelRect ) ).to.deep.equal( {
+ top: 50,
+ left: 350,
+ name: 'n'
+ } );
+ } );
+
it( 'should define the "northEast" position', () => {
expect( positions.northEast( buttonRect, panelRect ) ).to.deep.equal( {
top: 50,
- left: 100,
+ left: 500,
name: 'ne'
} );
} );
it( 'should define the "northWest" position', () => {
expect( positions.northWest( buttonRect, panelRect ) ).to.deep.equal( {
- top: 150,
- left: 150,
+ top: 50,
+ left: 200,
name: 'nw'
} );
} );
+
+ it( 'should define the "northMiddleEast" position', () => {
+ expect( positions.northMiddleEast( buttonRect, panelRect ) ).to.deep.equal( {
+ top: 50,
+ left: 425,
+ name: 'nme'
+ } );
+ } );
+
+ it( 'should define the "northMiddleWest" position', () => {
+ expect( positions.northMiddleWest( buttonRect, panelRect ) ).to.deep.equal( {
+ top: 50,
+ left: 275,
+ name: 'nmw'
+ } );
+ } );
} );
} );
From 1cc52a5c781405a13aeab76036c301bf01e96f29 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Magdalena=20Chrze=C5=9Bcian?=
Date: Tue, 29 Dec 2020 16:57:24 +0100
Subject: [PATCH 15/40] Restoring the listening to editor UI updates when the
properties balloon is being shown.
---
.../src/tableproperties/tablepropertiesui.js | 24 ++++++++-----------
1 file changed, 10 insertions(+), 14 deletions(-)
diff --git a/packages/ckeditor5-table/src/tableproperties/tablepropertiesui.js b/packages/ckeditor5-table/src/tableproperties/tablepropertiesui.js
index f0ed5707537..390c5027f0a 100644
--- a/packages/ckeditor5-table/src/tableproperties/tablepropertiesui.js
+++ b/packages/ckeditor5-table/src/tableproperties/tablepropertiesui.js
@@ -151,7 +151,6 @@ export default class TablePropertiesUI extends Plugin {
*/
_createPropertiesView() {
const editor = this.editor;
- const viewDocument = editor.editing.view.document;
const config = editor.config.get( 'table.tableProperties' );
const borderColorsConfig = normalizeColorOptions( config.borderColors );
const localizedBorderColors = getLocalizedColorOptions( editor.locale, borderColorsConfig );
@@ -185,15 +184,6 @@ export default class TablePropertiesUI extends Plugin {
cancel();
} );
- // Reposition the balloon or hide the form if a table is no longer selected.
- this.listenTo( editor.ui, 'update', () => {
- if ( !getTableWidgetAncestor( viewDocument.selection ) ) {
- this._hideView();
- } else if ( this._isViewVisible ) {
- repositionContextualBalloon( editor, 'table' );
- }
- } );
-
// Close on click outside of balloon panel element.
clickOutsideHandler( {
emitter: view,
@@ -281,6 +271,16 @@ export default class TablePropertiesUI extends Plugin {
*/
_showView() {
const editor = this.editor;
+ const viewDocument = editor.editing.view.document;
+
+ // Reposition the balloon or hide the form if a table is no longer selected.
+ this.listenTo( editor.ui, 'update', () => {
+ if ( !getTableWidgetAncestor( viewDocument.selection ) ) {
+ this._hideView();
+ } else if ( this._isViewVisible ) {
+ repositionContextualBalloon( editor, 'table' );
+ }
+ } );
// Update the view with the model values.
this._fillViewFormFromCommandValues();
@@ -303,10 +303,6 @@ export default class TablePropertiesUI extends Plugin {
* @protected
*/
_hideView() {
- if ( !this._isViewInBalloon ) {
- return;
- }
-
const editor = this.editor;
this.stopListening( editor.ui, 'update' );
From 54def8b7841be752cd99e3262a94d43c23d62943 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Magdalena=20Chrze=C5=9Bcian?=
Date: Thu, 31 Dec 2020 10:40:43 +0100
Subject: [PATCH 16/40] Positioning the panels more precisely.
---
.../theme/components/dropdown/dropdown.css | 20 +++++++++++++------
1 file changed, 14 insertions(+), 6 deletions(-)
diff --git a/packages/ckeditor5-ui/theme/components/dropdown/dropdown.css b/packages/ckeditor5-ui/theme/components/dropdown/dropdown.css
index f8e4e71c3fd..ead9feb3863 100644
--- a/packages/ckeditor5-ui/theme/components/dropdown/dropdown.css
+++ b/packages/ckeditor5-ui/theme/components/dropdown/dropdown.css
@@ -76,15 +76,23 @@
}
&.ck-dropdown__panel_s,
- &.ck-dropdown__panel_smw,
- &.ck-dropdown__panel_sme,
- &.ck-dropdown__panel_n,
- &.ck-dropdown__panel_nmw,
- &.ck-dropdown__panel_nme {
- /* Positioning panels relatively to the center of the button */
+ &.ck-dropdown__panel_n {
+ /* Positioning panels relative to the center of the button */
left: 50%;
}
+ &.ck-dropdown__panel_nmw,
+ &.ck-dropdown__panel_smw {
+ /* Positioning panels relative to the middle-west of the button */
+ left: 75%;
+ }
+
+ &.ck-dropdown__panel_nme,
+ &.ck-dropdown__panel_sme {
+ /* Positioning panels relative to the middle-east of the button */
+ left: 25%;
+ }
+
&.ck-dropdown__panel_s,
&.ck-dropdown__panel_n {
transform: translateX(-50%);
From ba3829bad1970d813edd4cb93e7a636e09c6c6df Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Maksymilian=20Barna=C5=9B?=
Date: Thu, 31 Dec 2020 12:19:23 +0100
Subject: [PATCH 17/40] Decorator downcasting should ignore elements without
links.
---
.../ckeditor5-link/src/linkimageediting.js | 5 ++++
.../ckeditor5-link/tests/linkimageediting.js | 28 +++++++++++++++++++
2 files changed, 33 insertions(+)
diff --git a/packages/ckeditor5-link/src/linkimageediting.js b/packages/ckeditor5-link/src/linkimageediting.js
index 0a36adafaff..ce2cb21c2c7 100644
--- a/packages/ckeditor5-link/src/linkimageediting.js
+++ b/packages/ckeditor5-link/src/linkimageediting.js
@@ -214,6 +214,11 @@ function downcastImageLinkManualDecorator( manualDecorators, decorator ) {
const viewFigure = conversionApi.mapper.toViewElement( data.item );
const linkInImage = Array.from( viewFigure.getChildren() ).find( child => child.name === 'a' );
+ // The unlink command is removing the link by the time this dispatcher is ran.
+ if ( !linkInImage ) {
+ return;
+ }
+
for ( const [ key, val ] of toMap( attributes ) ) {
conversionApi.writer.setAttribute( key, val, linkInImage );
}
diff --git a/packages/ckeditor5-link/tests/linkimageediting.js b/packages/ckeditor5-link/tests/linkimageediting.js
index 51c980fa025..1e94870ccd7 100644
--- a/packages/ckeditor5-link/tests/linkimageediting.js
+++ b/packages/ckeditor5-link/tests/linkimageediting.js
@@ -826,6 +826,34 @@ describe( 'LinkImageEditing', () => {
'
'
);
} );
+
+ it( 'should downcast without error if the image already has no link', () => {
+ setModelData( model,
+ '[ ]'
+ );
+
+ editor.execute( 'link', 'https://cksource.com', {
+ linkIsDownloadable: true,
+ linkIsExternal: true,
+ linkIsGallery: true
+ } );
+
+ // Attributes will be removed along with the link, but the downcast will be fired.
+ // The lack of link should not affect the downcasting. #8401.
+ expect( () => {
+ editor.execute( 'unlink', 'https://cksource.com', {
+ linkIsDownloadable: true,
+ linkIsExternal: true,
+ linkIsGallery: true
+ } );
+ } ).to.not.throw();
+
+ expect( editor.getData() ).to.equal(
+ '' +
+ ' ' +
+ ' '
+ );
+ } );
} );
} );
} );
From 15544ac85981e10763837b01a987bf0d9c9ec787 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Maksymilian=20Barna=C5=9B?=
Date: Thu, 31 Dec 2020 12:45:10 +0100
Subject: [PATCH 18/40] Update packages/ckeditor5-link/src/linkimageediting.js
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Co-authored-by: Piotrek Koszuliński
---
packages/ckeditor5-link/src/linkimageediting.js | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/packages/ckeditor5-link/src/linkimageediting.js b/packages/ckeditor5-link/src/linkimageediting.js
index ce2cb21c2c7..abf165038b3 100644
--- a/packages/ckeditor5-link/src/linkimageediting.js
+++ b/packages/ckeditor5-link/src/linkimageediting.js
@@ -214,7 +214,9 @@ function downcastImageLinkManualDecorator( manualDecorators, decorator ) {
const viewFigure = conversionApi.mapper.toViewElement( data.item );
const linkInImage = Array.from( viewFigure.getChildren() ).find( child => child.name === 'a' );
- // The unlink command is removing the link by the time this dispatcher is ran.
+ // The element was removed by the time this converter is executed.
+ // It may happen when the base `linkHref` and decorator attributes are removed
+ // at the same time (see #8401).
if ( !linkInImage ) {
return;
}
From f7ebb0b6c304597d6df5320b40b2a6092e1cb918 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Magdalena=20Chrze=C5=9Bcian?=
Date: Thu, 31 Dec 2020 13:08:52 +0100
Subject: [PATCH 19/40] Added tests for updating the table properties UI view.
---
.../src/tableproperties/tablepropertiesui.js | 24 +++++++----
.../tableproperties/tablepropertiesui.js | 40 +++++++++++++++++++
2 files changed, 57 insertions(+), 7 deletions(-)
diff --git a/packages/ckeditor5-table/src/tableproperties/tablepropertiesui.js b/packages/ckeditor5-table/src/tableproperties/tablepropertiesui.js
index 390c5027f0a..6d14cb8f87b 100644
--- a/packages/ckeditor5-table/src/tableproperties/tablepropertiesui.js
+++ b/packages/ckeditor5-table/src/tableproperties/tablepropertiesui.js
@@ -271,15 +271,9 @@ export default class TablePropertiesUI extends Plugin {
*/
_showView() {
const editor = this.editor;
- const viewDocument = editor.editing.view.document;
- // Reposition the balloon or hide the form if a table is no longer selected.
this.listenTo( editor.ui, 'update', () => {
- if ( !getTableWidgetAncestor( viewDocument.selection ) ) {
- this._hideView();
- } else if ( this._isViewVisible ) {
- repositionContextualBalloon( editor, 'table' );
- }
+ this._updateView();
} );
// Update the view with the model values.
@@ -318,6 +312,22 @@ export default class TablePropertiesUI extends Plugin {
this.editor.editing.view.focus();
}
+ /**
+ * Repositions the {@link #_balloon} or hides the {@link #view} if a table is no longer selected.
+ *
+ * @protected
+ */
+ _updateView() {
+ const editor = this.editor;
+ const viewDocument = editor.editing.view.document;
+
+ if ( !getTableWidgetAncestor( viewDocument.selection ) ) {
+ this._hideView();
+ } else if ( this._isViewVisible ) {
+ repositionContextualBalloon( editor, 'table' );
+ }
+ }
+
/**
* Returns `true` when the {@link #view} is the visible in the {@link #_balloon}.
*
diff --git a/packages/ckeditor5-table/tests/tableproperties/tablepropertiesui.js b/packages/ckeditor5-table/tests/tableproperties/tablepropertiesui.js
index e04b11540fe..3023e506d44 100644
--- a/packages/ckeditor5-table/tests/tableproperties/tablepropertiesui.js
+++ b/packages/ckeditor5-table/tests/tableproperties/tablepropertiesui.js
@@ -507,6 +507,16 @@ describe( 'table properties', () => {
expect( firstBatch ).to.not.equal( secondBatch );
} );
+ it( 'should start listening to EditorUI#update', () => {
+ const spy = sinon.spy( tablePropertiesUI, 'listenTo' );
+
+ tablePropertiesButton.fire( 'execute' );
+ expect( contextualBalloon.visibleView ).to.equal( tablePropertiesView );
+
+ sinon.assert.calledOnce( spy );
+ sinon.assert.calledWith( spy, editor.ui, 'update' );
+ } );
+
describe( 'initial data', () => {
it( 'should be set before adding the form to the the balloon to avoid unnecessary input animations', () => {
const balloonAddSpy = testUtils.sinon.spy( editor.plugins.get( ContextualBalloon ), 'add' );
@@ -609,5 +619,35 @@ describe( 'table properties', () => {
sinon.assert.calledOnce( spy );
} );
} );
+
+ describe( 'Updating the #view', () => {
+ beforeEach( () => {
+ editor.model.change( writer => {
+ writer.setSelection( editor.model.document.getRoot().getChild( 0 ).getChild( 0 ).getChild( 0 ), 0 );
+ } );
+
+ tablePropertiesButton.fire( 'execute' );
+ expect( contextualBalloon.visibleView ).to.equal( tablePropertiesView );
+ } );
+
+ it( 'should reposition the baloon if table is selected', () => {
+ const spy = sinon.spy( contextualBalloon, 'updatePosition' );
+
+ editor.ui.fire( 'update' );
+
+ sinon.assert.calledOnce( spy );
+ } );
+
+ it( 'should hide the view and not reposition the balloon if table is no longer selected', () => {
+ const positionSpy = sinon.spy( contextualBalloon, 'updatePosition' );
+ const hideSpy = sinon.spy( tablePropertiesUI, '_hideView' );
+
+ tablePropertiesView.fire( 'submit' );
+ expect( contextualBalloon.visibleView ).to.be.null;
+
+ sinon.assert.calledOnce( hideSpy );
+ sinon.assert.notCalled( positionSpy );
+ } );
+ } );
} );
} );
From c661d3a78079c0b01eb39a1d04c895544f18501e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Magdalena=20Chrze=C5=9Bcian?=
Date: Mon, 4 Jan 2021 12:26:38 +0100
Subject: [PATCH 20/40] Removed duplicated selectors.
---
.../theme/components/dropdown/dropdown.css | 16 ++--------------
1 file changed, 2 insertions(+), 14 deletions(-)
diff --git a/packages/ckeditor5-ui/theme/components/dropdown/dropdown.css b/packages/ckeditor5-ui/theme/components/dropdown/dropdown.css
index ead9feb3863..5be0de1c8f9 100644
--- a/packages/ckeditor5-ui/theme/components/dropdown/dropdown.css
+++ b/packages/ckeditor5-ui/theme/components/dropdown/dropdown.css
@@ -79,32 +79,20 @@
&.ck-dropdown__panel_n {
/* Positioning panels relative to the center of the button */
left: 50%;
+ transform: translateX(-50%);
}
&.ck-dropdown__panel_nmw,
&.ck-dropdown__panel_smw {
/* Positioning panels relative to the middle-west of the button */
left: 75%;
+ transform: translateX(-75%);
}
&.ck-dropdown__panel_nme,
&.ck-dropdown__panel_sme {
/* Positioning panels relative to the middle-east of the button */
left: 25%;
- }
-
- &.ck-dropdown__panel_s,
- &.ck-dropdown__panel_n {
- transform: translateX(-50%);
- }
-
- &.ck-dropdown__panel_smw,
- &.ck-dropdown__panel_nmw {
- transform: translateX(-75%);
- }
-
- &.ck-dropdown__panel_sme,
- &.ck-dropdown__panel_nme {
transform: translateX(-25%);
}
}
From c2ec930575a0e9d149b2c7ef655debd7dabf9d0e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Magdalena=20Chrze=C5=9Bcian?=
Date: Mon, 4 Jan 2021 18:07:21 +0100
Subject: [PATCH 21/40] Widget required in html-embed plugin.
---
packages/ckeditor5-html-embed/src/htmlembed.js | 3 ++-
packages/ckeditor5-html-embed/tests/htmlembed.js | 5 +++--
2 files changed, 5 insertions(+), 3 deletions(-)
diff --git a/packages/ckeditor5-html-embed/src/htmlembed.js b/packages/ckeditor5-html-embed/src/htmlembed.js
index 21667e39fd2..5fb53953180 100644
--- a/packages/ckeditor5-html-embed/src/htmlembed.js
+++ b/packages/ckeditor5-html-embed/src/htmlembed.js
@@ -10,6 +10,7 @@
import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
import HtmlEmbedEditing from './htmlembedediting';
import HtmlEmbedUI from './htmlembedui';
+import Widget from '@ckeditor/ckeditor5-widget/src/widget';
/**
* The HTML embed feature.
@@ -25,7 +26,7 @@ export default class HtmlEmbed extends Plugin {
* @inheritDoc
*/
static get requires() {
- return [ HtmlEmbedEditing, HtmlEmbedUI ];
+ return [ HtmlEmbedEditing, HtmlEmbedUI, Widget ];
}
/**
diff --git a/packages/ckeditor5-html-embed/tests/htmlembed.js b/packages/ckeditor5-html-embed/tests/htmlembed.js
index 291e79826a3..e5484de28f3 100644
--- a/packages/ckeditor5-html-embed/tests/htmlembed.js
+++ b/packages/ckeditor5-html-embed/tests/htmlembed.js
@@ -6,10 +6,11 @@
import HtmlEmbed from '../src/htmlembed';
import HtmlEmbedUI from '../src/htmlembedui';
import HtmlEmbedEditing from '../src/htmlembedediting';
+import Widget from '@ckeditor/ckeditor5-widget/src/widget';
describe( 'HtmlEmbed', () => {
- it( 'should require HtmlEmbedEditing and HtmlEmbedUI', () => {
- expect( HtmlEmbed.requires ).to.deep.equal( [ HtmlEmbedEditing, HtmlEmbedUI ] );
+ it( 'should require HtmlEmbedEditing, HtmlEmbedUI and Widget', () => {
+ expect( HtmlEmbed.requires ).to.deep.equal( [ HtmlEmbedEditing, HtmlEmbedUI, Widget ] );
} );
it( 'should be named', () => {
From 1fd66b61b6a27555bce2cf3060ddfc56c636352f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Maksymilian=20Barna=C5=9B?=
Date: Tue, 5 Jan 2021 09:33:47 +0100
Subject: [PATCH 22/40] Add/Update tests.
---
packages/ckeditor5-link/src/linkcommand.js | 2 +-
.../ckeditor5-link/tests/linkimageediting.js | 31 +++++++++++++++++--
2 files changed, 29 insertions(+), 4 deletions(-)
diff --git a/packages/ckeditor5-link/src/linkcommand.js b/packages/ckeditor5-link/src/linkcommand.js
index f9703437541..6932ae5e16e 100644
--- a/packages/ckeditor5-link/src/linkcommand.js
+++ b/packages/ckeditor5-link/src/linkcommand.js
@@ -187,7 +187,7 @@ export default class LinkCommand extends Command {
writer.setSelection( writer.createPositionAfter( linkRange.end.nodeBefore ) );
}
// If not then insert text node with `linkHref` attribute in place of caret.
- // However, since selection in collapsed, attribute value will be used as data for text node.
+ // However, since selection is collapsed, attribute value will be used as data for text node.
// So, if `href` is empty, do not create text node.
else if ( href !== '' ) {
const attributes = toMap( selection.getAttributes() );
diff --git a/packages/ckeditor5-link/tests/linkimageediting.js b/packages/ckeditor5-link/tests/linkimageediting.js
index 1e94870ccd7..a05a35dab13 100644
--- a/packages/ckeditor5-link/tests/linkimageediting.js
+++ b/packages/ckeditor5-link/tests/linkimageediting.js
@@ -827,6 +827,7 @@ describe( 'LinkImageEditing', () => {
);
} );
+ // See #8401.
it( 'should downcast without error if the image already has no link', () => {
setModelData( model,
'[ ]'
@@ -839,7 +840,7 @@ describe( 'LinkImageEditing', () => {
} );
// Attributes will be removed along with the link, but the downcast will be fired.
- // The lack of link should not affect the downcasting. #8401.
+ // The lack of link should not affect the downcasting.
expect( () => {
editor.execute( 'unlink', 'https://cksource.com', {
linkIsDownloadable: true,
@@ -850,8 +851,32 @@ describe( 'LinkImageEditing', () => {
expect( editor.getData() ).to.equal(
'' +
- ' ' +
- ' '
+ ' ' +
+ ''
+ );
+ } );
+
+ // See #8401.
+ it( 'order of model updates should not affect converters', () => {
+ setModelData( model,
+ '[ ]'
+ );
+
+ model.change( writer => {
+ const ranges = model.schema.getValidRanges( model.document.selection.getRanges(), 'linkIsDownloadable' );
+
+ for ( const range of ranges ) {
+ // `linkHref` gets processed first, as it is just the first property assigned to the model by `LinkCommand`.
+ // Here we force attributes to be set on a model in a different order,
+ // to force unusual order of downcast converters down the line.
+ writer.setAttribute( 'linkIsDownloadable', true, range );
+ writer.setAttribute( 'linkHref', 'url', range );
+ }
+ } );
+
+ expect( getModelData( model, { withoutSelection: true } ) ).to.equal(
+ ' '
);
} );
} );
From 0f82b01a2d2c9d0a419b6b0930c47827711b14ed Mon Sep 17 00:00:00 2001
From: Mgsy
Date: Tue, 5 Jan 2021 10:20:24 +0100
Subject: [PATCH 23/40] Revert "Merge master to docs-spellchecker."
This reverts commit 9743a19b24cbee5473440a77b763ee7cfc3c77e7, reversing
changes made to 5eb0d2612a2cf9d8c912405aaec89c325fb01a18.
---
.github/ISSUE_TEMPLATE/1-bug-report.md | 2 +-
CHANGELOG.md | 2 +-
docs/_snippets/examples/multi-root-editor.js | 11 +-
docs/assets/tour-balloon.css | 36 ----
docs/assets/tour-balloon.js | 109 ----------
.../guides/integration/configuration.md | 2 +-
docs/features/toolbar.md | 2 -
.../framework/guides/custom-editor-creator.md | 11 +-
package.json | 2 -
.../ckeditor5-autoformat/src/autoformat.js | 13 +-
.../src/blockautoformatediting.js | 7 -
.../ckeditor5-autoformat/tests/autoformat.js | 187 ++----------------
.../tests/manual/autoformat.js | 37 +---
.../src/blockquoteediting.js | 53 ++---
.../tests/integration.js | 47 +----
.../ckeditor5-build-classic/tests/ckeditor.js | 15 --
.../src/classiceditorui.js | 2 +-
.../tests/classiceditorui.js | 18 --
.../src/decouplededitorui.js | 11 +-
.../tests/decouplededitorui.js | 18 --
.../src/inlineeditorui.js | 2 +-
.../tests/inlineeditorui.js | 18 --
.../guides/deep-dive/element-reconversion.md | 10 +-
packages/ckeditor5-engine/src/model/differ.js | 19 +-
.../docs/_snippets/features/html-embed.js | 5 -
.../src/htmlembedediting.js | 2 -
.../tests/htmlembedediting.js | 21 --
...ge-insert-via-pasting-url-into-editor.html | 2 +-
.../src/imagecaption/imagecaptionediting.js | 2 +-
.../tests/imagecaption/imagecaptionediting.js | 14 --
packages/ckeditor5-list/src/listediting.js | 5 +-
packages/ckeditor5-list/tests/listediting.js | 15 --
.../examples/chat-with-mentions.html | 4 -
.../_snippets/examples/chat-with-mentions.js | 27 +--
.../docs/examples/chat-with-mentions.md | 29 +--
.../ckeditor5-mention/src/mentionediting.js | 2 +-
.../ckeditor5-mention/tests/mentionediting.js | 14 --
.../ckeditor5-typing/src/deletecommand.js | 61 +-----
.../ckeditor5-typing/tests/deletecommand.js | 77 +-------
.../src/toolbar/balloon/balloontoolbar.js | 2 +-
.../src/toolbar/block/blocktoolbar.js | 2 +-
.../src/toolbar/normalizetoolbarconfig.js | 10 +-
.../ckeditor5-ui/src/toolbar/toolbarview.js | 153 ++++----------
.../tests/toolbar/normalizetoolbarconfig.js | 17 +-
.../ckeditor5-ui/tests/toolbar/toolbarview.js | 127 +-----------
scripts/docs/snippetadapter.js | 10 -
46 files changed, 152 insertions(+), 1083 deletions(-)
delete mode 100644 docs/assets/tour-balloon.css
delete mode 100644 docs/assets/tour-balloon.js
diff --git a/.github/ISSUE_TEMPLATE/1-bug-report.md b/.github/ISSUE_TEMPLATE/1-bug-report.md
index 63a8f205062..4ab08c757aa 100644
--- a/.github/ISSUE_TEMPLATE/1-bug-report.md
+++ b/.github/ISSUE_TEMPLATE/1-bug-report.md
@@ -25,7 +25,7 @@ _What is the actual result of the above steps?_
* Browser: …
* OS: …
-* First affected CKEditor version: …
+* CKEditor version: …
* Installed CKEditor plugins: …
---
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 4fffa7e826b..41880807eb2 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -41,7 +41,7 @@ The CKEditor 5 Collaboration features changelog can be found here: https://ckedi
### Features
-* **[build-decoupled-document](https://www.npmjs.com/package/@ckeditor/ckeditor5-build-decoupled-document)**: Added new features to the build configuration: [horizontal line](https://ckeditor.com/docs/ckeditor5/latest/features/horizontal-line.html), [page break](https://ckeditor.com/docs/ckeditor5/latest/features/page-break.html), [remove formatting](https://ckeditor.com/docs/ckeditor5/latest/features/remove-format.html), and [special characters](https://ckeditor.com/docs/ckeditor5/latest/features/special-characters.html)). See [#6146](https://github.com/ckeditor/ckeditor5/issues/6146). ([commit](https://github.com/ckeditor/ckeditor5/commit/70157aec7c0ec62b63a51f6bb20764afad443637))
+* **[build-decoupled-document](https://www.npmjs.com/package/@ckeditor/ckeditor5-build-decoupled-document)**: Added new features to the build configuration: [horizontal line](https://ckeditor.com/docs/ckeditor5/latest/features/horizontal-line.html), [page break](https://ckeditor.com/docs/ckeditor5/latest/features/page-break.html), [remove formatting](https://ckeditor.com/docs/ckeditor5/latest/features/remove-format.html), and [special characters](https://ckeditor.com/docs/ckeditor5/latest/features/special-characters.html)) (see [#6146](https://github.com/ckeditor/ckeditor5/issues/6146). ([commit](https://github.com/ckeditor/ckeditor5/commit/70157aec7c0ec62b63a51f6bb20764afad443637))
* **[core](https://www.npmjs.com/package/@ckeditor/ckeditor5-core)**: Introduced the `focus()` method in the base `Editor` class. Closes [#714](https://github.com/ckeditor/ckeditor5/issues/714). ([commit](https://github.com/ckeditor/ckeditor5/commit/dea805153299404a130dcc12aa855cba922a2e86))
* **[engine](https://www.npmjs.com/package/@ckeditor/ckeditor5-engine)**: Introduced the `DataProcessor#registerRawContentMatcher()` API that marks content sections which contain arbitrary character data and should not be parsed during the conversion. See [#8323](https://github.com/ckeditor/ckeditor5/issues/8323). ([commit](https://github.com/ckeditor/ckeditor5/commit/b8538dea19326a04ed0ff4d8b0ab346f6be8fc08))
* **[image](https://www.npmjs.com/package/@ckeditor/ckeditor5-image)**: Support for inserting images by pasting an image URL directly into the editor. Closes [#8236](https://github.com/ckeditor/ckeditor5/issues/8236). ([commit](https://github.com/ckeditor/ckeditor5/commit/908a35ac381c852c466f6144ac25f21f0d5af877))
diff --git a/docs/_snippets/examples/multi-root-editor.js b/docs/_snippets/examples/multi-root-editor.js
index fd980001494..b23a7c8f23d 100644
--- a/docs/_snippets/examples/multi-root-editor.js
+++ b/docs/_snippets/examples/multi-root-editor.js
@@ -14,6 +14,7 @@ import setDataInElement from '@ckeditor/ckeditor5-utils/src/dom/setdatainelement
import mix from '@ckeditor/ckeditor5-utils/src/mix';
import EditorUI from '@ckeditor/ckeditor5-core/src/editor/editorui';
import enableToolbarKeyboardFocus from '@ckeditor/ckeditor5-ui/src/toolbar/enabletoolbarkeyboardfocus';
+import normalizeToolbarConfig from '@ckeditor/ckeditor5-ui/src/toolbar/normalizetoolbarconfig';
import { enablePlaceholder } from '@ckeditor/ckeditor5-engine/src/view/placeholder';
import EditorUIView from '@ckeditor/ckeditor5-ui/src/editorui/editoruiview';
import InlineEditableUIView from '@ckeditor/ckeditor5-ui/src/editableui/inline/inlineeditableuiview';
@@ -158,6 +159,14 @@ class MultirootEditorUI extends EditorUI {
* @member {module:ui/editorui/editoruiview~EditorUIView} #view
*/
this.view = view;
+
+ /**
+ * A normalized `config.toolbar` object.
+ *
+ * @type {Object}
+ * @private
+ */
+ this._toolbarConfig = normalizeToolbarConfig( editor.config.get( 'toolbar' ) );
}
/**
@@ -276,7 +285,7 @@ class MultirootEditorUI extends EditorUI {
const view = this.view;
const toolbar = view.toolbar;
- toolbar.fillFromConfig( editor.config.get( 'toolbar' ), this.componentFactory );
+ toolbar.fillFromConfig( this._toolbarConfig.items, this.componentFactory );
enableToolbarKeyboardFocus( {
origin: editor.editing.view,
diff --git a/docs/assets/tour-balloon.css b/docs/assets/tour-balloon.css
deleted file mode 100644
index 91e3c271acb..00000000000
--- a/docs/assets/tour-balloon.css
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Copyright (c) 2003-2020, CKSource - Frederico Knabben. All rights reserved.
- * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
- */
-
-.tippy-content {
- padding: 8px 6px;
-}
-
-.tippy-content .tippy-content__message {
- display: flex;
- align-items: center;
- min-height: 36px;
- margin-right: 28px;
- padding-left: 44px;
- line-height: 1.5;
-
- background-image: url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz48c3ZnIHdpZHRoPSIzOHB4IiBoZWlnaHQ9IjM3cHgiIHZpZXdCb3g9IjAgMCAzOCAzNyIgdmVyc2lvbj0iMS4xIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIj4gICAgICAgIDx0aXRsZT5oaW50PC90aXRsZT4gICAgPGRlc2M+Q3JlYXRlZCB3aXRoIFNrZXRjaC48L2Rlc2M+ICAgIDxnIGlkPSJQYWdlLTEiIHN0cm9rZT0ibm9uZSIgc3Ryb2tlLXdpZHRoPSIxIiBmaWxsPSJub25lIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiPiAgICAgICAgPGcgaWQ9IkFydGJvYXJkIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgtOTg5LjAwMDAwMCwgLTQzNS4wMDAwMDApIj4gICAgICAgICAgICA8ZyBpZD0icG9wdXAtaW52ZXJ0ZWQiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDk3My41MDAwMDAsIDQwOC4wMDAwMDApIj4gICAgICAgICAgICAgICAgPGcgaWQ9ImhpbnQiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDE2LjAwMDAwMCwgMjcuMDAwMDAwKSI+ICAgICAgICAgICAgICAgICAgICA8Y2lyY2xlIGlkPSJPdmFsIiBmaWxsLW9wYWNpdHk9IjAuMTE5NTkxMzQ2IiBmaWxsPSIjRkZGRkZGIiBjeD0iMTguOTMwMjMyNiIgY3k9IjE4LjUiIHI9IjE4LjUiPjwvY2lyY2xlPiAgICAgICAgICAgICAgICAgICAgPHBhdGggZD0iTTE4Ljg5MTEyMDUsMzAuMjkyMzI1NyBDMTkuNDg4NTg4MSwzMC4yOTIzMjU3IDE5Ljk3MjkzMTIsMjkuODA3OTgyNiAxOS45NzI5MzEyLDI5LjIxMDUxNSBDMTkuOTcyOTMxMiwyOC42MTMwNDc1IDE5LjQ4ODU4ODEsMjkuMDE5MDMyNCAxOC44OTExMjA1LDI5LjAxOTAzMjQgQzE4LjI5MzY1MjksMjkuMDE5MDMyNCAxNy44MDkzMDk4LDI4LjYxMzA0NzUgMTcuODA5MzA5OCwyOS4yMTA1MTUgQzE3LjgwOTMwOTgsMjkuODA3OTgyNiAxOC4yOTM2NTI5LDMwLjI5MjMyNTcgMTguODkxMTIwNSwzMC4yOTIzMjU3IFoiIGlkPSJPdmFsIiBmaWxsPSIjMDAwMDAwIj48L3BhdGg+ICAgICAgICAgICAgICAgICAgICA8cGF0aCBkPSJNMTUuNjA1NzA4MiwyNS4xNDkwNDg2IEwyMi4xNzY1MzI4LDI1LjE0OTA0ODYgTDIyLjA3MTIzNDEsMjcuMzUxMzMxNyBDMjIuMDIwMjU0NiwyOC40MTc1NDg5IDIxLjE0MDk1MTYsMjkuMjU1ODE0IDIwLjA3MzUxNjMsMjkuMjU1ODE0IEwxNy43MDg3MjQ3LDI5LjI1NTgxNCBDMTYuNjQxMjg5NCwyOS4yNTU4MTQgMTUuNzYxOTg2NCwyOC40MTc1NDg5IDE1LjcxMTAwNjksMjcuMzUxMzMxNyBMMTUuNjA1NzA4MiwyNS4xNDkwNDg2IEwxNS42MDU3MDgyLDI1LjE0OTA0ODYgWiIgaWQ9IkNvbWJpbmVkLVNoYXBlIiBmaWxsPSIjQUJBQkFCIj48L3BhdGg+ICAgICAgICAgICAgICAgICAgICA8cGF0aCBkPSJNMTguODkxMTIwNSw5Ljg5NTM0ODg0IEMyMi41MjAwODY3LDkuODk1MzQ4ODQgMjUuNDYxOTQ1LDEyLjc4MDYzMyAyNS40NjE5NDUsMTYuMzM5ODExNCBDMjUuNDYxOTQ1LDE3LjQ5NTAyMTkgMjUuMTUyMDI4NiwxOC41NzkyMzk2IDI0LjYwOTI5MiwxOS41MTY4NTA5IEMyNC41OTEzODY5LDE5LjU5MjczMDIgMjQuNTQ5NzEyNSwxOS42NzI1MzE2IDI0LjQ4MjgxNjUsMTkuNzU1NjE1MiBMMjQuMzYyNTAzNywxOS45MDk3MTk2IEMyNC4yMzYwOTQyLDIwLjA5NTcwODYgMjQuMTAwMDg4MiwyMC4yNzQ4ODY4IDIzLjk1NTE3OTYsMjAuNDQ2NTczNyBDMjMuMTEwMjA1LDIxLjYwNzczNDggMjIuNTE3Nzk5NCwyMi43NDUwNTg1IDIyLjE3NjUzMjgsMjMuODU4MzUxIEwyMi4xNzY1MzI4LDI1LjI5MDM0MjggTDE1LjYwNTcwODIsMjUuMjkwMzQyOCBMMTUuNjA1NzA4MiwyMy44NTgzNTEgQzE1LjI0NTI3MzIsMjIuNzYyNDIyNCAxNC42NjgyMDA5LDIxLjY0MzIwNjQgMTMuODc0NDkxMywyMC41MDA3MDI4IEMxMi45MDQzMjA5LDE5LjM3NzY3MzEgMTIuMzIwMjk2LDE3LjkyNTYzMzggMTIuMzIwMjk2LDE2LjMzOTgxMTQgQzEyLjMyMDI5NiwxMi43ODA2MzMgMTUuMjYyMTU0Myw5Ljg5NTM0ODg0IDE4Ljg5MTEyMDUsOS44OTUzNDg4NCBaIE0xOC40ODA0NDQsMTEuMTI3Mzc4NCBDMTUuNzU4NzE5MywxMS4xMjczNzg0IDEzLjU1MjMyNTYsMTMuMzMzNzcyMiAxMy41NTIzMjU2LDE2LjA1NTQ5NjggQzEzLjU1MjMyNTYsMTYuMjQyMjEzNyAxMy41NjI3MDk1LDE2LjQyNjUwNTIgMTMuNTgyOTMxNCwxNi42MDc4MjU1IEwxMy42MjA1NzQ0LDE2Ljg3NzUwOTIgTDE0LjQ1NTk4NTQsMTYuODc3NjIwOCBDMTQuNDAyMDEyOCwxNi42MTE5OTQ1IDE0LjM3MzY3ODYsMTYuMzM3MDU0NSAxNC4zNzM2Nzg2LDE2LjA1NTQ5NjggQzE0LjM3MzY3ODYsMTMuODUyMTk1OSAxNi4xMDg3NzQ3LDEyLjA1NDE0ODQgMTguMjg3MTE5NiwxMS45NTMyMDE1IEwxOC40ODA0NDQsMTEuOTQ4NzMxNSBMMTguNDgwNDQ0LDExLjEyNzM3ODQgWiIgaWQ9IlNoYXBlIiBmaWxsPSIjRjhDMjcyIj48L3BhdGg+ICAgICAgICAgICAgICAgICAgICA8ZyBpZD0iR3JvdXAtNSIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoNy43NDQxODYsIDE1LjUyNzQ4NCkiIHN0cm9rZT0iI0Y4QzI3MiIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIj4gICAgICAgICAgICAgICAgICAgICAgICA8bGluZSB4MT0iMS42MDM1OTQwOCIgeTE9IjEuNjAzNTk0MDgiIHgyPSIxLjYwMzU5NDA4IiB5Mj0iLTAuNTQ3NTY4NzEiIGlkPSJQYXRoLTItQ29weSIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMS42MDM1OTQsIDAuNTI4MDEzKSByb3RhdGUoOTAuMDAwMDAwKSB0cmFuc2xhdGUoLTEuNjAzNTk0LCAtMC41MjgwMTMpICI+PC9saW5lPiAgICAgICAgICAgICAgICAgICAgICAgIDxsaW5lIHgxPSIyMC4yNjAwNDIzIiB5MT0iMS42MDM1OTQwOCIgeDI9IjIwLjI2MDA0MjMiIHkyPSItMC41NDc1Njg3MSIgaWQ9IlBhdGgtMi1Db3B5LTIiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDIwLjI2MDA0MiwgMC41MjgwMTMpIHJvdGF0ZSg5MC4wMDAwMDApIHRyYW5zbGF0ZSgtMjAuMjYwMDQyLCAtMC41MjgwMTMpICI+PC9saW5lPiAgICAgICAgICAgICAgICAgICAgPC9nPiAgICAgICAgICAgICAgICAgICAgPGcgaWQ9Ikdyb3VwLTUtQ29weSIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMTguNTk5MTg0LCAxNi40NDUyMTMpIHJvdGF0ZSgtMzAuMDAwMDAwKSB0cmFuc2xhdGUoLTE4LjU5OTE4NCwgLTE2LjQ0NTIxMykgdHJhbnNsYXRlKDcuODQzMzcwLCAxNS41ODQ3NDgpIiBzdHJva2U9IiNGOEMyNzIiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCI+ICAgICAgICAgICAgICAgICAgICAgICAgPGxpbmUgeDE9IjEuNjAzNTk0MDgiIHkxPSIxLjYwMzU5NDA4IiB4Mj0iMS42MDM1OTQwOCIgeTI9Ii0wLjU0NzU2ODcxIiBpZD0iUGF0aC0yLUNvcHkiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDEuNjAzNTk0LCAwLjUyODAxMykgcm90YXRlKDkwLjAwMDAwMCkgdHJhbnNsYXRlKC0xLjYwMzU5NCwgLTAuNTI4MDEzKSAiPjwvbGluZT4gICAgICAgICAgICAgICAgICAgICAgICA8bGluZSB4MT0iMjAuMjYwMDQyMyIgeTE9IjEuNjAzNTk0MDgiIHgyPSIyMC4yNjAwNDIzIiB5Mj0iLTAuNTQ3NTY4NzEiIGlkPSJQYXRoLTItQ29weS0yIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgyMC4yNjAwNDIsIDAuNTI4MDEzKSByb3RhdGUoOTAuMDAwMDAwKSB0cmFuc2xhdGUoLTIwLjI2MDA0MiwgLTAuNTI4MDEzKSAiPjwvbGluZT4gICAgICAgICAgICAgICAgICAgIDwvZz4gICAgICAgICAgICAgICAgICAgIDxnIGlkPSJHcm91cC01LUNvcHktMyIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMjMuNDg1MzgyLCA4LjE4MDQzOCkgcm90YXRlKC02MC4wMDAwMDApIHRyYW5zbGF0ZSgtMjMuNDg1MzgyLCAtOC4xODA0MzgpIHRyYW5zbGF0ZSgyMi4xOTQ2ODUsIDcuMzE5OTczKSIgc3Ryb2tlPSIjRjhDMjcyIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiPiAgICAgICAgICAgICAgICAgICAgICAgIDxsaW5lIHgxPSIxLjI1MTU4NTYyIiB5MT0iMS42MDM1OTQwOCIgeDI9IjEuMjUxNTg1NjIiIHkyPSItMC41NDc1Njg3MSIgaWQ9IlBhdGgtMi1Db3B5LTIiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDEuMjUxNTg2LCAwLjUyODAxMykgcm90YXRlKDkwLjAwMDAwMCkgdHJhbnNsYXRlKC0xLjI1MTU4NiwgLTAuNTI4MDEzKSAiPjwvbGluZT4gICAgICAgICAgICAgICAgICAgIDwvZz4gICAgICAgICAgICAgICAgICAgIDxnIGlkPSJHcm91cC01LUNvcHktNCIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMTQuMDk4NDkwLCA4LjAyMzk5MCkgcm90YXRlKC0xMjAuMDAwMDAwKSB0cmFuc2xhdGUoLTE0LjA5ODQ5MCwgLTguMDIzOTkwKSB0cmFuc2xhdGUoMTIuODA3NzkzLCA3LjE2MzUyNSkiIHN0cm9rZT0iI0Y4QzI3MiIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIj4gICAgICAgICAgICAgICAgICAgICAgICA8bGluZSB4MT0iMS4yNTE1ODU2MiIgeTE9IjEuNjAzNTk0MDgiIHgyPSIxLjI1MTU4NTYyIiB5Mj0iLTAuNTQ3NTY4NzEiIGlkPSJQYXRoLTItQ29weS0yIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgxLjI1MTU4NiwgMC41MjgwMTMpIHJvdGF0ZSg5MC4wMDAwMDApIHRyYW5zbGF0ZSgtMS4yNTE1ODYsIC0wLjUyODAxMykgIj48L2xpbmU+ICAgICAgICAgICAgICAgICAgICA8L2c+ICAgICAgICAgICAgICAgICAgICA8ZyBpZD0iR3JvdXAtNS1Db3B5LTIiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDE4Ljg3MDE2MCwgMTYuMTc0MjM3KSByb3RhdGUoLTE1MC4wMDAwMDApIHRyYW5zbGF0ZSgtMTguODcwMTYwLCAtMTYuMTc0MjM3KSB0cmFuc2xhdGUoOC4xMTQzNDYsIDE1LjMxMzc3MikiIHN0cm9rZT0iI0Y4QzI3MiIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIj4gICAgICAgICAgICAgICAgICAgICAgICA8bGluZSB4MT0iMS42MDM1OTQwOCIgeTE9IjEuNjAzNTk0MDgiIHgyPSIxLjYwMzU5NDA4IiB5Mj0iLTAuNTQ3NTY4NzEiIGlkPSJQYXRoLTItQ29weSIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMS42MDM1OTQsIDAuNTI4MDEzKSByb3RhdGUoOTAuMDAwMDAwKSB0cmFuc2xhdGUoLTEuNjAzNTk0LCAtMC41MjgwMTMpICI+PC9saW5lPiAgICAgICAgICAgICAgICAgICAgICAgIDxsaW5lIHgxPSIyMC4yNjAwNDIzIiB5MT0iMS42MDM1OTQwOCIgeDI9IjIwLjI2MDA0MjMiIHkyPSItMC41NDc1Njg3MSIgaWQ9IlBhdGgtMi1Db3B5LTIiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDIwLjI2MDA0MiwgMC41MjgwMTMpIHJvdGF0ZSg5MC4wMDAwMDApIHRyYW5zbGF0ZSgtMjAuMjYwMDQyLCAtMC41MjgwMTMpICI+PC9saW5lPiAgICAgICAgICAgICAgICAgICAgPC9nPiAgICAgICAgICAgICAgICAgICAgPGxpbmUgeDE9IjE5LjAwODQ1NjciIHkxPSI4LjA1NzA4MjQ1IiB4Mj0iMTkuMDA4NDU2NyIgeTI9IjUuOTA1OTE5NjYiIGlkPSJQYXRoLTIiIHN0cm9rZT0iI0Y4QzI3MiIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIj48L2xpbmU+ICAgICAgICAgICAgICAgIDwvZz4gICAgICAgICAgICA8L2c+ICAgICAgICA8L2c+ICAgIDwvZz48L3N2Zz4=);
- background-position: top left;
- background-repeat: no-repeat;
-}
-
-.tippy-content .tippy-content__close-button {
- color: var(--ck-color-text);
- position: absolute;
- top: 0;
- right: 0;
-}
-
-.tippy-content .tippy-content__close-button::after {
- content: '✖';
-}
-
-.tippy-content .ck.ck-button.tippy-content__close-button:hover {
- background: none;
-}
diff --git a/docs/assets/tour-balloon.js b/docs/assets/tour-balloon.js
deleted file mode 100644
index c7b4e9f6f48..00000000000
--- a/docs/assets/tour-balloon.js
+++ /dev/null
@@ -1,109 +0,0 @@
-/**
- * @license Copyright (c) 2003-2020, CKSource - Frederico Knabben. All rights reserved.
- * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
- */
-
-/* global console, window, document */
-
-/**
- * Attaches a tour balloon with a description to any DOM node element.
- *
- * **Tip**: Use the global `findToolbarItem()` method to easily pick toolbar items.
- *
- * Examples:
- *
- * // Using a comparison callback to search for an item.
- * window.attachTourBalloon( {
- * target: window.findToolbarItem( editor.ui.view.toolbar, item => item.label && item.label === 'Insert HTML' ),
- * text: 'Tour text to help users discover the feature.'
- * } );
- *
- * // Using a toolbar item index.
- * window.attachTourBalloon( {
- * target: window.findToolbarItem( editor.ui.view.toolbar, 5 ),
- * text: 'Tour text to help users discover the feature.'
- * } );
- *
- * // Specifying options of tippy.js, e.g. to customize the placement of the balloon.
- * // See https://atomiks.github.io/tippyjs/v6/all-props/ for all options.
- * window.attachTourBalloon( {
- * target: window.findToolbarItem( editor.ui.view.toolbar, 5 ),
- * text: 'Tour text to help users discover the feature.',
- * tippyOptions: {
- * placement: 'bottom-start'
- * }
- * } );
- *
- * @param {Object} options Balloon options.
- * @param {HTMLElement} options.target A DOM node the balloon will point to.
- * @param {String} options.text The description to be shown in the tooltip.
- * @param {Object} [options.tippyOptions] Additional [configuration of tippy.js](https://atomiks.github.io/tippyjs/v6/all-props/).
- */
-window.attachTourBalloon = function( { target, text, tippyOptions } ) {
- if ( !target ) {
- console.warn( '[attachTourBalloon] The target DOM node for the feature tour balloon does not exist.', { text } );
-
- return;
- }
-
- const content = `
- ${ text }
-
- `;
-
- // eslint-disable-next-line no-undef
- const tooltip = tippy( target, {
- content,
- theme: 'light-border',
- placement: 'bottom',
- trigger: 'manual',
- hideOnClick: false,
- allowHTML: true,
- maxWidth: 280,
- showOnCreate: true,
- interactive: true,
- touch: 'hold',
- zIndex: 1,
- appendTo: () => document.body,
- ...tippyOptions
- } );
-
- // eslint-disable-next-line no-undef
- const closeButton = tooltip.popper.querySelector( '.tippy-content__close-button' );
-
- closeButton.addEventListener( 'click', () => {
- tooltip.hide();
- } );
-
- target.addEventListener( 'click', () => {
- tooltip.hide();
- } );
-};
-
-/**
- * Searches for a toolbar item and returns the first one matching the criteria.
- *
- * You can search for toolbar items using a comparison callback:
- *
- * window.findToolbarItem( editor.ui.view.toolbar, item => item.label && item.label.startsWith( 'Insert HTML' ) );
- *
- * Or you pick toolbar items by their index:
- *
- * window.findToolbarItem( editor.ui.view.toolbar, 3 );
- *
- * @param {module:ui/toolbar/toolbarview~ToolbarView} toolbarView Toolbar instance.
- * @param {Number|Function} indexOrCallback Index of a toolbar item or a callback passed to `ViewCollection#find`.
- * @returns {HTMLElement|undefined} HTML element or undefined
- */
-window.findToolbarItem = function( toolbarView, indexOrCallback ) {
- const items = toolbarView.items;
- let item;
-
- if ( typeof indexOrCallback === 'function' ) {
- item = items.find( indexOrCallback );
- } else {
- item = items.get( indexOrCallback );
- }
-
- return item ? item.element : undefined;
-};
diff --git a/docs/builds/guides/integration/configuration.md b/docs/builds/guides/integration/configuration.md
index d8a9c522479..e15a5d9a98d 100644
--- a/docs/builds/guides/integration/configuration.md
+++ b/docs/builds/guides/integration/configuration.md
@@ -51,7 +51,7 @@ ClassicEditor
} );
```
- Be careful when removing plugins from CKEditor builds using `config.removePlugins`. If removed plugins were providing toolbar buttons, the default toolbar configuration included in a build will become invalid. In such case you need to provide the {@link features/toolbar updated toolbar configuration} as in the example above or by providing only items that need to be removed using `config.toolbar.removeItems`.
+ Be careful when removing plugins from CKEditor builds using `config.removePlugins`. If removed plugins were providing toolbar buttons, the default toolbar configuration included in a build will become invalid. In such case you need to provide the {@link features/toolbar updated toolbar configuration} as in the example above.
### List of plugins
diff --git a/docs/features/toolbar.md b/docs/features/toolbar.md
index fde974ef7d1..6f94f96aacc 100644
--- a/docs/features/toolbar.md
+++ b/docs/features/toolbar.md
@@ -71,8 +71,6 @@ toolbar: {
* **`items`** – An array of toolbar item names. Most of the components (buttons, dropdowns, etc.) which can be used as toolbar items are described under the {@link features/index Features} tab. A full list is defined in {@link module:ui/componentfactory~ComponentFactory editor.ui.componentFactory} and can be listed using the following snippet: `Array.from( editor.ui.componentFactory.names() )`. Besides button names, you can also use the dedicated separators for toolbar groups (`'|'`) and toolbar lines (`'-'`).
- * **`removeItems`** – An array of toolbar item names. With this setting you can modify the default toolbar configuration without the need of defining the entire list (you can specify a couple of buttons that you want to remove instead of specifying all the buttons you want to keep). If, after removing an item, toolbar will have two or more consecutive separators (`'|'`), the duplicates will be removed automatically.
-
* **`viewportTopOffset`** – The offset (in pixels) from the top of the viewport used when positioning a sticky toolbar. Useful when a page with which the editor is being integrated has some other sticky or fixed elements (e.g. the top menu). Thanks to setting the toolbar offset, the toolbar will not be positioned underneath or above the page's UI.
* **`shouldNotGroupWhenFull`** – When set to `true`, the toolbar will stop grouping items and let them wrap to the next line when there is not enough space to display them in a single row. This setting is `false` by default, which enables items grouping.
diff --git a/docs/framework/guides/custom-editor-creator.md b/docs/framework/guides/custom-editor-creator.md
index 64e34838cd8..71892d5ae64 100644
--- a/docs/framework/guides/custom-editor-creator.md
+++ b/docs/framework/guides/custom-editor-creator.md
@@ -126,6 +126,7 @@ The `*EditorUI` class is the main UI class which initializes UI components (the
```js
import EditorUI from '@ckeditor/ckeditor5-core/src/editor/editorui';
import enableToolbarKeyboardFocus from '@ckeditor/ckeditor5-ui/src/toolbar/enabletoolbarkeyboardfocus';
+import normalizeToolbarConfig from '@ckeditor/ckeditor5-ui/src/toolbar/normalizetoolbarconfig';
import { enablePlaceholder } from '@ckeditor/ckeditor5-engine/src/view/placeholder';
/**
@@ -150,6 +151,14 @@ class MultirootEditorUI extends EditorUI {
* @member {module:ui/editorui/editoruiview~EditorUIView} #view
*/
this.view = view;
+
+ /**
+ * A normalized `config.toolbar` object.
+ *
+ * @type {Object}
+ * @private
+ */
+ this._toolbarConfig = normalizeToolbarConfig( editor.config.get( 'toolbar' ) );
}
/**
@@ -267,7 +276,7 @@ class MultirootEditorUI extends EditorUI {
const view = this.view;
const toolbar = view.toolbar;
- toolbar.fillFromConfig( editor.config.get( 'toolbar' ), this.componentFactory );
+ toolbar.fillFromConfig( this._toolbarConfig.items, this.componentFactory );
enableToolbarKeyboardFocus( {
origin: editor.editing.view,
diff --git a/package.json b/package.json
index 605c8eb7144..560890d07fb 100644
--- a/package.json
+++ b/package.json
@@ -106,7 +106,6 @@
"minimatch": "^3.0.4",
"mkdirp": "^1.0.4",
"nyc": "^15.0.1",
- "popper": "^1.0.1",
"postcss-loader": "^3.0.0",
"progress-bar-webpack-plugin": "^2.1.0",
"raw-loader": "^4.0.1",
@@ -117,7 +116,6 @@
"stylelint-config-ckeditor5": "^2.0.0",
"svgo": "^1.3.2",
"terser-webpack-plugin": "^3.0.2",
- "tippy.js": "^6.2.7",
"umberto": "^1.6.2",
"upath": "^2.0.0",
"webpack": "^4.43.0"
diff --git a/packages/ckeditor5-autoformat/src/autoformat.js b/packages/ckeditor5-autoformat/src/autoformat.js
index dc90ffc88fd..17b779ef898 100644
--- a/packages/ckeditor5-autoformat/src/autoformat.js
+++ b/packages/ckeditor5-autoformat/src/autoformat.js
@@ -44,7 +44,6 @@ export default class Autoformat extends Plugin {
* When typed:
* - `* ` or `- ` – A paragraph will be changed to a bulleted list.
* - `1. ` or `1) ` – A paragraph will be changed to a numbered list ("1" can be any digit or a list of digits).
- * - `[] ` or `[ ] ` – A paragraph will be changed to a to-do list.
*
* @private
*/
@@ -58,10 +57,6 @@ export default class Autoformat extends Plugin {
if ( commands.get( 'numberedList' ) ) {
blockAutoformatEditing( this.editor, this, /^1[.|)]\s$/, 'numberedList' );
}
-
- if ( commands.get( 'todoList' ) ) {
- blockAutoformatEditing( this.editor, this, /^\[\s?\]\s$/, 'todoList' );
- }
}
/**
@@ -85,8 +80,8 @@ export default class Autoformat extends Plugin {
if ( commands.get( 'bold' ) ) {
const boldCallback = getCallbackFunctionForInlineAutoformat( this.editor, 'bold' );
- inlineAutoformatEditing( this.editor, this, /(?:^|\s)(\*\*)([^*]+)(\*\*)$/g, boldCallback );
- inlineAutoformatEditing( this.editor, this, /(?:^|\s)(__)([^_]+)(__)$/g, boldCallback );
+ inlineAutoformatEditing( this.editor, this, /(\*\*)([^*]+)(\*\*)$/g, boldCallback );
+ inlineAutoformatEditing( this.editor, this, /(__)([^_]+)(__)$/g, boldCallback );
}
if ( commands.get( 'italic' ) ) {
@@ -94,8 +89,8 @@ export default class Autoformat extends Plugin {
// The italic autoformatter cannot be triggered by the bold markers, so we need to check the
// text before the pattern (e.g. `(?:^|[^\*])`).
- inlineAutoformatEditing( this.editor, this, /(?:^|\s)(\*)([^*_]+)(\*)$/g, italicCallback );
- inlineAutoformatEditing( this.editor, this, /(?:^|\s)(_)([^_]+)(_)$/g, italicCallback );
+ inlineAutoformatEditing( this.editor, this, /(?:^|[^*])(\*)([^*_]+)(\*)$/g, italicCallback );
+ inlineAutoformatEditing( this.editor, this, /(?:^|[^_])(_)([^_]+)(_)$/g, italicCallback );
}
if ( commands.get( 'code' ) ) {
diff --git a/packages/ckeditor5-autoformat/src/blockautoformatediting.js b/packages/ckeditor5-autoformat/src/blockautoformatediting.js
index 2957a9f25a4..9aee765557e 100644
--- a/packages/ckeditor5-autoformat/src/blockautoformatediting.js
+++ b/packages/ckeditor5-autoformat/src/blockautoformatediting.js
@@ -96,13 +96,6 @@ export default function blockAutoformatEditing( editor, plugin, pattern, callbac
return;
}
- // Only lists should be formatted inside the lists.
- if ( blockToFormat.is( 'element', 'listItem' ) &&
- ![ 'numberedList', 'bulletedList', 'todoList' ].includes( callbackOrCommand )
- ) {
- return;
- }
-
// In case a command is bound, do not re-execute it over an existing block style which would result with a style removal.
// Instead just drop processing so that autoformat trigger text is not lost. E.g. writing "# " in a level 1 heading.
if ( command && command.value === true ) {
diff --git a/packages/ckeditor5-autoformat/tests/autoformat.js b/packages/ckeditor5-autoformat/tests/autoformat.js
index 88d98bd9df6..a80404d8c69 100644
--- a/packages/ckeditor5-autoformat/tests/autoformat.js
+++ b/packages/ckeditor5-autoformat/tests/autoformat.js
@@ -7,7 +7,6 @@ import Autoformat from '../src/autoformat';
import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph';
import ListEditing from '@ckeditor/ckeditor5-list/src/listediting';
-import TodoListEditing from '@ckeditor/ckeditor5-list/src/todolistediting';
import HeadingEditing from '@ckeditor/ckeditor5-heading/src/headingediting';
import BoldEditing from '@ckeditor/ckeditor5-basic-styles/src/bold/boldediting';
import StrikethroughEditing from '@ckeditor/ckeditor5-basic-styles/src/strikethrough/strikethroughediting';
@@ -38,7 +37,6 @@ describe( 'Autoformat', () => {
Paragraph,
Autoformat,
ListEditing,
- TodoListEditing,
HeadingEditing,
BoldEditing,
ItalicEditing,
@@ -105,15 +103,6 @@ describe( 'Autoformat', () => {
expect( getData( model ) ).to.equal( 'Foo * [] ' );
} );
-
- it( 'should be converted from a to-do list', () => {
- setData( model, '*[] ' );
- model.change( writer => {
- writer.insertText( ' ', doc.selection.getFirstPosition() );
- } );
-
- expect( getData( model ) ).to.equal( '[] ' );
- } );
} );
describe( 'Numbered list', () => {
@@ -197,92 +186,6 @@ describe( 'Autoformat', () => {
expect( getData( model ) ).to.equal( '[] ' );
} );
-
- it( 'should be converted from a to-do list', () => {
- setData( model, '1.[] ' );
- model.change( writer => {
- writer.insertText( ' ', doc.selection.getFirstPosition() );
- } );
-
- expect( getData( model ) ).to.equal( '[] ' );
- } );
- } );
-
- describe( 'To-do list', () => {
- function insertSpace() {
- model.change( writer => {
- writer.insertText( ' ', doc.selection.getFirstPosition() );
- } );
- }
- function insertBrackets( content = '' ) {
- model.change( writer => {
- writer.insertText( '[' + content + ']', doc.selection.getFirstPosition() );
- } );
- }
-
- it( 'should replace empty square brackets', () => {
- setData( model, '[]' );
- insertBrackets();
- insertSpace();
-
- expect( getData( model ) ).to.equal( '[] ' );
- } );
-
- it( 'should replace square brackets with space inside', () => {
- setData( model, '[]' );
- insertBrackets( ' ' );
- insertSpace();
-
- expect( getData( model ) ).to.equal( '[] ' );
- } );
-
- it( 'should be converted from a paragraph', () => {
- setData( model, '[]Sample text ' );
- insertBrackets();
- insertSpace();
-
- expect( getData( model ) ).to.equal( '[]Sample text ' );
- } );
-
- it( 'should be converted from a header', () => {
- setData( model, '[]Header text ' );
- insertBrackets( ' ' );
- insertSpace();
-
- expect( getData( model ) ).to.equal( '[]Header text ' );
- } );
-
- it( 'should be converted from a numbered list', () => {
- setData( model, '[]Sample text ' );
- insertBrackets();
- insertSpace();
-
- expect( getData( model ) ).to.equal( '[]Sample text ' );
- } );
-
- it( 'should not replace the brackets if is not at the beginning of the line', () => {
- setData( model, 'Sample text [] ' );
- insertBrackets( ' ' );
- insertSpace();
-
- expect( getData( model ) ).to.equal( 'Sample text [ ] [] ' );
- } );
-
- it( 'should not replace the brackets if it contains a text', () => {
- setData( model, '[]' );
- insertBrackets( 'Foo' );
- insertSpace();
-
- expect( getData( model ) ).to.equal( '[Foo] [] ' );
- } );
-
- it( 'should not replace the brackets after ', () => {
- setData( model, 'Foo [] ' );
- insertBrackets();
- insertSpace();
-
- expect( getData( model ) ).to.equal( 'Foo [] [] ' );
- } );
} );
describe( 'Heading', () => {
@@ -433,15 +336,6 @@ describe( 'Autoformat', () => {
expect( getData( model ) ).to.equal( '1. > [] ' );
} );
- it( 'should not replace greater-than character when inside to-do list', () => {
- setData( model, '>[] ' );
- model.change( writer => {
- writer.insertText( ' ', doc.selection.getFirstPosition() );
- } );
-
- expect( getData( model ) ).to.equal( '> [] ' );
- } );
-
it( 'should not replace greater-than character after ', () => {
setData( model, 'Foo >[] ' );
model.change( writer => {
@@ -506,15 +400,6 @@ describe( 'Autoformat', () => {
expect( getData( model ) ).to.equal( '1. ```[] ' );
} );
-
- it( 'should not replace triple grave accents when inside todo list', () => {
- setData( model, '``[] ' );
- model.change( writer => {
- writer.insertText( '`', doc.selection.getFirstPosition() );
- } );
-
- expect( getData( model ) ).to.equal( '```[] ' );
- } );
} );
describe( 'Inline autoformat', () => {
@@ -600,64 +485,28 @@ describe( 'Autoformat', () => {
expect( getData( model ) ).to.equal( '**foobar**[] ' );
} );
- describe( 'should not format', () => {
- it( '* without space preceding it', () => {
- setData( model, 'fo*ob*ar[] ' );
-
- model.change( writer => {
- writer.insertText( '*', doc.selection.getFirstPosition() );
- } );
-
- expect( getData( model ) ).to
- .equal( 'fo*ob*ar*[] ' );
- } );
-
- it( '__ without space preceding it', () => {
- setData( model, 'fo__ob__ar_[] ' );
-
- model.change( writer => {
- writer.insertText( '_', doc.selection.getFirstPosition() );
- } );
-
- expect( getData( model ) ).to
- .equal( 'fo__ob__ar__[] ' );
- } );
-
- // https://github.com/ckeditor/ckeditor5/issues/2388
- it( 'snake_case sentences', () => {
- setData( model, 'foo_bar baz[] ' );
-
- model.change( writer => {
- writer.insertText( '_', doc.selection.getFirstPosition() );
- } );
-
- expect( getData( model ) ).to
- .equal( 'foo_bar baz_[] ' );
- } );
- } );
-
describe( 'with code element', () => {
describe( 'should not format (inside)', () => {
it( '* inside', () => {
- setData( model, '<$text code="true">fo *obar[]$text> ' );
+ setData( model, '<$text code="true">fo*obar[]$text> ' );
model.change( writer => {
writer.insertText( '*', { code: true }, doc.selection.getFirstPosition() );
} );
expect( getData( model ) ).to
- .equal( '<$text code="true">fo *obar*[]$text> ' );
+ .equal( '<$text code="true">fo*obar*[]$text> ' );
} );
it( '__ inside', () => {
- setData( model, '<$text code="true">fo __obar_[]$text> ' );
+ setData( model, '<$text code="true">fo__obar_[]$text> ' );
model.change( writer => {
writer.insertText( '_', { code: true }, doc.selection.getFirstPosition() );
} );
expect( getData( model ) ).to
- .equal( '<$text code="true">fo __obar__[]$text> ' );
+ .equal( '<$text code="true">fo__obar__[]$text> ' );
} );
it( '~~ inside', () => {
@@ -685,24 +534,24 @@ describe( 'Autoformat', () => {
describe( 'should not format (across)', () => {
it( '* across', () => {
- setData( model, '<$text code="true">fo *o$text>bar[] ' );
+ setData( model, '<$text code="true">fo*o$text>bar[] ' );
model.change( writer => {
writer.insertText( '*', doc.selection.getFirstPosition() );
} );
expect( getData( model ) ).to
- .equal( '<$text code="true">fo *o$text>bar*[] ' );
+ .equal( '<$text code="true">fo*o$text>bar*[] ' );
} );
it( '__ across', () => {
- setData( model, '<$text code="true">fo __o$text>bar_[] ' );
+ setData( model, '<$text code="true">fo__o$text>bar_[] ' );
model.change( writer => {
writer.insertText( '_', doc.selection.getFirstPosition() );
} );
expect( getData( model ) ).to
- .equal( '<$text code="true">fo __o$text>bar__[] ' );
+ .equal( '<$text code="true">fo__o$text>bar__[] ' );
} );
it( '~~ across', () => {
setData( model, '<$text code="true">fo~~o$text>bar~[] ' );
@@ -728,24 +577,24 @@ describe( 'Autoformat', () => {
describe( 'should format', () => {
it( '* after', () => {
- setData( model, '<$text code="true">fo*o$text>b *ar[] ' );
+ setData( model, '<$text code="true">fo*o$text>b*ar[] ' );
model.change( writer => {
writer.insertText( '*', doc.selection.getFirstPosition() );
} );
expect( getData( model ) ).to
- .equal( '<$text code="true">fo*o$text>b <$text italic="true">ar$text>[] ' );
+ .equal( '<$text code="true">fo*o$text>b<$text italic="true">ar$text>[] ' );
} );
it( '__ after', () => {
- setData( model, '<$text code="true">fo__o$text>b __ar_[] ' );
+ setData( model, '<$text code="true">fo__o$text>b__ar_[] ' );
model.change( writer => {
writer.insertText( '_', doc.selection.getFirstPosition() );
} );
expect( getData( model ) ).to
- .equal( '<$text code="true">fo__o$text>b <$text bold="true">ar$text>[] ' );
+ .equal( '<$text code="true">fo__o$text>b<$text bold="true">ar$text>[] ' );
} );
it( '~~ after', () => {
setData( model, '<$text code="true">fo~~o$text>b~~ar~[] ' );
@@ -820,18 +669,6 @@ describe( 'Autoformat', () => {
expect( getData( model ) ).to.equal( '1. [] ' );
} );
- it( 'should not replace square brackets with to-do list item', () => {
- setData( model, '[] ' );
- model.change( writer => {
- writer.insertText( '[]', doc.selection.getFirstPosition() );
- } );
- model.change( writer => {
- writer.insertText( ' ', doc.selection.getFirstPosition() );
- } );
-
- expect( getData( model ) ).to.equal( '[] [] ' );
- } );
-
it( 'should not replace hash character with heading', () => {
setData( model, '#[] ' );
model.change( writer => {
diff --git a/packages/ckeditor5-autoformat/tests/manual/autoformat.js b/packages/ckeditor5-autoformat/tests/manual/autoformat.js
index 7b885661cfc..ff1067df92d 100644
--- a/packages/ckeditor5-autoformat/tests/manual/autoformat.js
+++ b/packages/ckeditor5-autoformat/tests/manual/autoformat.js
@@ -9,7 +9,6 @@ import ClassicEditor from '@ckeditor/ckeditor5-editor-classic/src/classiceditor'
import Autoformat from '../../src/autoformat';
import Enter from '@ckeditor/ckeditor5-enter/src/enter';
import List from '@ckeditor/ckeditor5-list/src/list';
-import TodoList from '@ckeditor/ckeditor5-list/src/todolist';
import BlockQuote from '@ckeditor/ckeditor5-block-quote/src/blockquote';
import Typing from '@ckeditor/ckeditor5-typing/src/typing';
import Heading from '@ckeditor/ckeditor5-heading/src/heading';
@@ -24,38 +23,10 @@ import ShiftEnter from '@ckeditor/ckeditor5-enter/src/shiftenter';
ClassicEditor
.create( document.querySelector( '#editor' ), {
- plugins: [
- Enter,
- Typing,
- Paragraph,
- Undo,
- Bold,
- Italic,
- Code,
- Strikethrough,
- Heading,
- List,
- TodoList,
- Autoformat,
- BlockQuote,
- CodeBlock,
- ShiftEnter
- ],
- toolbar: [
- 'heading',
- '|',
- 'numberedList',
- 'bulletedList',
- 'todoList',
- 'blockQuote',
- 'codeBlock',
- 'bold',
- 'italic',
- 'code',
- 'strikethrough',
- 'undo',
- 'redo'
- ]
+ plugins: [ Enter, Typing, Paragraph, Undo, Bold, Italic, Code, Strikethrough, Heading, List, Autoformat, BlockQuote, CodeBlock,
+ ShiftEnter ],
+ toolbar: [ 'heading', '|', 'numberedList', 'bulletedList', 'blockQuote', 'codeBlock', 'bold', 'italic', 'code', 'strikethrough',
+ 'undo', 'redo' ]
} )
.then( editor => {
window.editor = editor;
diff --git a/packages/ckeditor5-block-quote/src/blockquoteediting.js b/packages/ckeditor5-block-quote/src/blockquoteediting.js
index bbe4047a280..cfb0472b420 100644
--- a/packages/ckeditor5-block-quote/src/blockquoteediting.js
+++ b/packages/ckeditor5-block-quote/src/blockquoteediting.js
@@ -8,7 +8,6 @@
*/
import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
-import priorities from '@ckeditor/ckeditor5-utils/src/priorities';
import BlockQuoteCommand from './blockquotecommand';
@@ -103,49 +102,31 @@ export default class BlockQuoteEditing extends Plugin {
return false;
} );
+ }
- const viewDocument = this.editor.editing.view.document;
- const selection = editor.model.document.selection;
- const blockQuoteCommand = editor.commands.get( 'blockQuote' );
+ /**
+ * @inheritDoc
+ */
+ afterInit() {
+ const editor = this.editor;
+ const command = editor.commands.get( 'blockQuote' );
// Overwrite default Enter key behavior.
// If Enter key is pressed with selection collapsed in empty block inside a quote, break the quote.
- //
- // Priority normal - 10 to override default handler but not list's feature listener.
- this.listenTo( viewDocument, 'enter', ( evt, data ) => {
- if ( !selection.isCollapsed || !blockQuoteCommand.value ) {
- return;
- }
+ // This listener is added in afterInit in order to register it after list's feature listener.
+ // We can't use a priority for this, because 'low' is already used by the enter feature, unless
+ // we'd use numeric priority in this case.
+ this.listenTo( this.editor.editing.view.document, 'enter', ( evt, data ) => {
+ const doc = this.editor.model.document;
+ const positionParent = doc.selection.getLastPosition().parent;
- const positionParent = selection.getLastPosition().parent;
-
- if ( positionParent.isEmpty ) {
- editor.execute( 'blockQuote' );
- editor.editing.view.scrollToTheSelection();
+ if ( doc.selection.isCollapsed && positionParent.isEmpty && command.value ) {
+ this.editor.execute( 'blockQuote' );
+ this.editor.editing.view.scrollToTheSelection();
data.preventDefault();
evt.stop();
}
- }, { priority: priorities.normal - 10 } );
-
- // Overwrite default Backspace key behavior.
- // If Backspace key is pressed with selection collapsed in first empty block inside a quote, break the quote.
- //
- // Priority high + 5 to override widget's feature listener but not list's feature listener.
- this.listenTo( viewDocument, 'delete', ( evt, data ) => {
- if ( data.direction != 'backward' || !selection.isCollapsed || !blockQuoteCommand.value ) {
- return;
- }
-
- const positionParent = selection.getLastPosition().parent;
-
- if ( positionParent.isEmpty && !positionParent.previousSibling ) {
- editor.execute( 'blockQuote' );
- editor.editing.view.scrollToTheSelection();
-
- data.preventDefault();
- evt.stop();
- }
- }, { priority: priorities.high + 5 } );
+ } );
}
}
diff --git a/packages/ckeditor5-block-quote/tests/integration.js b/packages/ckeditor5-block-quote/tests/integration.js
index 91555ff4beb..3905303fcdf 100644
--- a/packages/ckeditor5-block-quote/tests/integration.js
+++ b/packages/ckeditor5-block-quote/tests/integration.js
@@ -281,7 +281,7 @@ describe( 'BlockQuote integration', () => {
);
} );
- it( 'unwraps empty quote when the backspace key pressed in the first empty paragraph in a quote', () => {
+ it( 'removes empty quote when merging into another quote', () => {
const data = fakeEventData();
setModelData( model,
@@ -295,13 +295,12 @@ describe( 'BlockQuote integration', () => {
expect( getModelData( model ) ).to.equal(
'x ' +
- 'a ' +
- '[] ' +
+ 'a[] ' +
'y '
);
} );
- it( 'unwraps empty quote when the backspace key pressed in the empty paragraph that is the only content of quote', () => {
+ it( 'removes empty quote when merging into a paragraph', () => {
const data = fakeEventData();
setModelData( model,
@@ -313,45 +312,7 @@ describe( 'BlockQuote integration', () => {
viewDocument.fire( 'delete', data );
expect( getModelData( model ) ).to.equal(
- 'x ' +
- '[] ' +
- 'y '
- );
- } );
-
- it( 'unwraps quote from the first paragraph when the backspace key pressed', () => {
- const data = fakeEventData();
-
- setModelData( model,
- 'x ' +
- '[] foo ' +
- 'y '
- );
-
- viewDocument.fire( 'delete', data );
-
- expect( getModelData( model ) ).to.equal(
- 'x ' +
- '[] ' +
- 'foo ' +
- 'y '
- );
- } );
-
- it( 'merges paragraphs in a quote when the backspace key pressed not in the first paragraph', () => {
- const data = fakeEventData();
-
- setModelData( model,
- 'x ' +
- '[] ' +
- 'y '
- );
-
- viewDocument.fire( 'delete', data );
-
- expect( getModelData( model ) ).to.equal(
- 'x ' +
- '[] ' +
+ 'x[] ' +
'y '
);
} );
diff --git a/packages/ckeditor5-build-classic/tests/ckeditor.js b/packages/ckeditor5-build-classic/tests/ckeditor.js
index 48c496d0f5b..97aa49c8739 100644
--- a/packages/ckeditor5-build-classic/tests/ckeditor.js
+++ b/packages/ckeditor5-build-classic/tests/ckeditor.js
@@ -200,21 +200,6 @@ describe( 'ClassicEditor build', () => {
expect( editor.ui.view.stickyPanel.viewportTopOffset ).to.equal( 42 );
} );
} );
-
- it( 'allows removing built-in toolbar items', () => {
- return ClassicEditor
- .create( editorElement, {
- toolbar: {
- removeItems: [ 'italic' ]
- }
- } )
- .then( newEditor => {
- editor = newEditor;
-
- expect( editor.ui.view.toolbar.items.length ).to.equal( 16 );
- expect( editor.ui.view.toolbar.items.find( item => item.label === 'Italic' ) ).to.be.undefined;
- } );
- } );
} );
describeMemoryUsage( () => {
diff --git a/packages/ckeditor5-editor-classic/src/classiceditorui.js b/packages/ckeditor5-editor-classic/src/classiceditorui.js
index 95e79ac8706..6b389a245a8 100644
--- a/packages/ckeditor5-editor-classic/src/classiceditorui.js
+++ b/packages/ckeditor5-editor-classic/src/classiceditorui.js
@@ -148,7 +148,7 @@ export default class ClassicEditorUI extends EditorUI {
view.stickyPanel.viewportTopOffset = this._toolbarConfig.viewportTopOffset;
}
- view.toolbar.fillFromConfig( this._toolbarConfig, this.componentFactory );
+ view.toolbar.fillFromConfig( this._toolbarConfig.items, this.componentFactory );
enableToolbarKeyboardFocus( {
origin: editingView,
diff --git a/packages/ckeditor5-editor-classic/tests/classiceditorui.js b/packages/ckeditor5-editor-classic/tests/classiceditorui.js
index b46ce4cbbcc..7b987415a6d 100644
--- a/packages/ckeditor5-editor-classic/tests/classiceditorui.js
+++ b/packages/ckeditor5-editor-classic/tests/classiceditorui.js
@@ -198,24 +198,6 @@ describe( 'ClassicEditorUI', () => {
return editor.destroy();
} );
} );
-
- it( 'can be removed using config.toolbar.removeItems', () => {
- return VirtualClassicTestEditor
- .create( '', {
- toolbar: {
- items: [ 'foo', 'bar' ],
- removeItems: [ 'bar' ]
- }
- } )
- .then( editor => {
- const items = editor.ui.view.toolbar.items;
-
- expect( items.get( 0 ).name ).to.equal( 'foo' );
- expect( items.length ).to.equal( 1 );
-
- return editor.destroy();
- } );
- } );
} );
} );
diff --git a/packages/ckeditor5-editor-decoupled/src/decouplededitorui.js b/packages/ckeditor5-editor-decoupled/src/decouplededitorui.js
index 565f44f6da8..a0cdd9f971c 100644
--- a/packages/ckeditor5-editor-decoupled/src/decouplededitorui.js
+++ b/packages/ckeditor5-editor-decoupled/src/decouplededitorui.js
@@ -9,6 +9,7 @@
import EditorUI from '@ckeditor/ckeditor5-core/src/editor/editorui';
import enableToolbarKeyboardFocus from '@ckeditor/ckeditor5-ui/src/toolbar/enabletoolbarkeyboardfocus';
+import normalizeToolbarConfig from '@ckeditor/ckeditor5-ui/src/toolbar/normalizetoolbarconfig';
import { enablePlaceholder } from '@ckeditor/ckeditor5-engine/src/view/placeholder';
/**
@@ -33,6 +34,14 @@ export default class DecoupledEditorUI extends EditorUI {
* @member {module:ui/editorui/editoruiview~EditorUIView} #view
*/
this.view = view;
+
+ /**
+ * A normalized `config.toolbar` object.
+ *
+ * @type {Object}
+ * @private
+ */
+ this._toolbarConfig = normalizeToolbarConfig( editor.config.get( 'toolbar' ) );
}
/**
@@ -105,7 +114,7 @@ export default class DecoupledEditorUI extends EditorUI {
const view = this.view;
const toolbar = view.toolbar;
- toolbar.fillFromConfig( editor.config.get( 'toolbar' ), this.componentFactory );
+ toolbar.fillFromConfig( this._toolbarConfig.items, this.componentFactory );
enableToolbarKeyboardFocus( {
origin: editor.editing.view,
diff --git a/packages/ckeditor5-editor-decoupled/tests/decouplededitorui.js b/packages/ckeditor5-editor-decoupled/tests/decouplededitorui.js
index 8edbc51fc98..527844d6b11 100644
--- a/packages/ckeditor5-editor-decoupled/tests/decouplededitorui.js
+++ b/packages/ckeditor5-editor-decoupled/tests/decouplededitorui.js
@@ -170,24 +170,6 @@ describe( 'DecoupledEditorUI', () => {
return editor.destroy();
} );
} );
-
- it( 'can be removed using config.toolbar.removeItems', () => {
- return VirtualDecoupledTestEditor
- .create( '', {
- toolbar: {
- items: [ 'foo', 'bar' ],
- removeItems: [ 'bar' ]
- }
- } )
- .then( editor => {
- const items = editor.ui.view.toolbar.items;
-
- expect( items.get( 0 ).name ).to.equal( 'foo' );
- expect( items.length ).to.equal( 1 );
-
- return editor.destroy();
- } );
- } );
} );
} );
diff --git a/packages/ckeditor5-editor-inline/src/inlineeditorui.js b/packages/ckeditor5-editor-inline/src/inlineeditorui.js
index 07c36b4edcb..cac847a9eae 100644
--- a/packages/ckeditor5-editor-inline/src/inlineeditorui.js
+++ b/packages/ckeditor5-editor-inline/src/inlineeditorui.js
@@ -142,7 +142,7 @@ export default class InlineEditorUI extends EditorUI {
}
} );
- toolbar.fillFromConfig( this._toolbarConfig, this.componentFactory );
+ toolbar.fillFromConfig( this._toolbarConfig.items, this.componentFactory );
enableToolbarKeyboardFocus( {
origin: editingView,
diff --git a/packages/ckeditor5-editor-inline/tests/inlineeditorui.js b/packages/ckeditor5-editor-inline/tests/inlineeditorui.js
index ac9062c4f87..20fe4246a0f 100644
--- a/packages/ckeditor5-editor-inline/tests/inlineeditorui.js
+++ b/packages/ckeditor5-editor-inline/tests/inlineeditorui.js
@@ -223,24 +223,6 @@ describe( 'InlineEditorUI', () => {
return editor.destroy();
} );
} );
-
- it( 'can be removed using config.toolbar.removeItems', () => {
- return VirtualInlineTestEditor
- .create( '', {
- toolbar: {
- items: [ 'foo', 'bar' ],
- removeItems: [ 'bar' ]
- }
- } )
- .then( editor => {
- const items = editor.ui.view.toolbar.items;
-
- expect( items.get( 0 ).name ).to.equal( 'foo' );
- expect( items.length ).to.equal( 1 );
-
- return editor.destroy();
- } );
- } );
} );
it( 'initializes keyboard navigation between view#toolbar and view#editable', () => {
diff --git a/packages/ckeditor5-engine/docs/framework/guides/deep-dive/element-reconversion.md b/packages/ckeditor5-engine/docs/framework/guides/deep-dive/element-reconversion.md
index 14b936d26e3..7c776d14189 100644
--- a/packages/ckeditor5-engine/docs/framework/guides/deep-dive/element-reconversion.md
+++ b/packages/ckeditor5-engine/docs/framework/guides/deep-dive/element-reconversion.md
@@ -115,7 +115,7 @@ In this example implementation you will implement a "card" box which is displaye
A simplified model markup for the side card looks as follows:
```html
-
+
The title
The content
@@ -601,21 +601,21 @@ class ComplexBox extends Plugin {
const editor = this.editor;
// Defines a simple text button.
- editor.ui.componentFactory.add( 'complexBox', locale => {
+ editor.ui.componentFactory.add( 'insertCard', locale => {
const button = new ButtonView( locale );
- const command = editor.commands.get( 'insertComplexBox' );
+ const command = editor.commands.get( 'insertCard' );
button.set( {
withText: true,
icon: false,
- label: 'Complex Box'
+ label: 'Insert card'
} );
button.bind( 'isEnabled' ).to( command );
button.on( 'execute', () => {
- editor.execute( 'insertComplexBox' );
+ editor.execute( 'insertCard' );
editor.editing.view.focus();
} );
diff --git a/packages/ckeditor5-engine/src/model/differ.js b/packages/ckeditor5-engine/src/model/differ.js
index 7d66a775ca0..f57b65ef497 100644
--- a/packages/ckeditor5-engine/src/model/differ.js
+++ b/packages/ckeditor5-engine/src/model/differ.js
@@ -393,7 +393,7 @@ export default class Differ {
}
// Will contain returned results.
- let diffSet = [];
+ const diffSet = [];
// Check all changed elements.
for ( const element of this._changesInElement.keys() ) {
@@ -483,8 +483,8 @@ export default class Differ {
} );
// Glue together multiple changes (mostly on text nodes).
- for ( let i = 1, prevIndex = 0; i < diffSet.length; i++ ) {
- const prevDiff = diffSet[ prevIndex ];
+ for ( let i = 1; i < diffSet.length; i++ ) {
+ const prevDiff = diffSet[ i - 1 ];
const thisDiff = diffSet[ i ];
// Glue remove changes if they happen on text on same position.
@@ -511,20 +511,17 @@ export default class Differ {
prevDiff.attributeNewValue == thisDiff.attributeNewValue;
if ( isConsecutiveTextRemove || isConsecutiveTextAdd || isConsecutiveAttributeChange ) {
- prevDiff.length++;
+ diffSet[ i - 1 ].length++;
if ( isConsecutiveAttributeChange ) {
- prevDiff.range.end = prevDiff.range.end.getShiftedBy( 1 );
+ diffSet[ i - 1 ].range.end = diffSet[ i - 1 ].range.end.getShiftedBy( 1 );
}
- diffSet[ i ] = null;
- } else {
- prevIndex = i;
+ diffSet.splice( i, 1 );
+ i--;
}
}
- diffSet = diffSet.filter( v => v );
-
// Remove `changeCount` property from diff items. It is used only for sorting and is internal thing.
for ( const item of diffSet ) {
delete item.changeCount;
@@ -539,7 +536,7 @@ export default class Differ {
// Cache changes.
this._cachedChangesWithGraveyard = diffSet.slice();
- this._cachedChanges = diffSet.filter( _changesInGraveyardFilter );
+ this._cachedChanges = diffSet.slice().filter( _changesInGraveyardFilter );
if ( options.includeChangesInGraveyard ) {
return this._cachedChangesWithGraveyard;
diff --git a/packages/ckeditor5-html-embed/docs/_snippets/features/html-embed.js b/packages/ckeditor5-html-embed/docs/_snippets/features/html-embed.js
index 343f7fce54d..2abaaa0d3c9 100644
--- a/packages/ckeditor5-html-embed/docs/_snippets/features/html-embed.js
+++ b/packages/ckeditor5-html-embed/docs/_snippets/features/html-embed.js
@@ -163,11 +163,6 @@ ClassicEditor
iframeElement.contentWindow.document.write( html );
iframeElement.contentWindow.document.close();
} );
-
- window.attachTourBalloon( {
- target: window.findToolbarItem( editor.ui.view.toolbar, item => item.label && item.label === 'Insert HTML' ),
- text: 'Click here to insert new HTML snippet.'
- } );
} )
.catch( err => {
console.error( err.stack );
diff --git a/packages/ckeditor5-html-embed/src/htmlembedediting.js b/packages/ckeditor5-html-embed/src/htmlembedediting.js
index f70cd4f7828..aa8809c25c8 100644
--- a/packages/ckeditor5-html-embed/src/htmlembedediting.js
+++ b/packages/ckeditor5-html-embed/src/htmlembedediting.js
@@ -183,8 +183,6 @@ export default class HtmlEmbedEditing extends Plugin {
if ( newValue !== state.getRawHtmlValue() ) {
editor.execute( 'updateHtmlEmbed', newValue );
editor.editing.view.focus();
- } else {
- this.cancel();
}
},
cancel() {
diff --git a/packages/ckeditor5-html-embed/tests/htmlembedediting.js b/packages/ckeditor5-html-embed/tests/htmlembedediting.js
index 64b1c2f0f53..2eaf51837ad 100644
--- a/packages/ckeditor5-html-embed/tests/htmlembedediting.js
+++ b/packages/ckeditor5-html-embed/tests/htmlembedediting.js
@@ -390,27 +390,6 @@ describe( 'HtmlEmbedEditing', () => {
expect( domContentWrapper.querySelectorAll( '.raw-html-embed__edit-button' ) ).to.have.lengthOf( 1 );
} );
- it( 'switches to "preview mode" after clicking save button when there are no changes', () => {
- setModelData( model, ' ' );
-
- let widget = viewDocument.getRoot().getChild( 0 );
- let contentWrapper = widget.getChild( 1 );
- let domContentWrapper = editor.editing.view.domConverter.mapViewToDom( contentWrapper );
-
- widget.getCustomProperty( 'rawHtmlApi' ).makeEditable();
-
- domContentWrapper.querySelector( '.raw-html-embed__save-button' ).click();
-
- // The entire DOM has rendered once again. The references were invalid.
- widget = viewDocument.getRoot().getChild( 0 );
- contentWrapper = widget.getChild( 1 );
- domContentWrapper = editor.editing.view.domConverter.mapViewToDom( contentWrapper );
-
- // There's exactly this button, and nothing else.
- expect( domContentWrapper.querySelectorAll( 'button' ) ).to.have.lengthOf( 1 );
- expect( domContentWrapper.querySelectorAll( '.raw-html-embed__edit-button' ) ).to.have.lengthOf( 1 );
- } );
-
it( 'does not lose editor focus after saving changes', () => {
setModelData( model, ' ' );
const widget = viewDocument.getRoot().getChild( 0 );
diff --git a/packages/ckeditor5-image/docs/_snippets/features/image-insert-via-pasting-url-into-editor.html b/packages/ckeditor5-image/docs/_snippets/features/image-insert-via-pasting-url-into-editor.html
index 50a305a6923..c23d059e0a1 100644
--- a/packages/ckeditor5-image/docs/_snippets/features/image-insert-via-pasting-url-into-editor.html
+++ b/packages/ckeditor5-image/docs/_snippets/features/image-insert-via-pasting-url-into-editor.html
@@ -1,3 +1,3 @@
-
Insert a URL of an image:
+
Insert an URL of an image:
diff --git a/packages/ckeditor5-image/src/imagecaption/imagecaptionediting.js b/packages/ckeditor5-image/src/imagecaption/imagecaptionediting.js
index b5f5b8d2ff4..dadeb895eb1 100644
--- a/packages/ckeditor5-image/src/imagecaption/imagecaptionediting.js
+++ b/packages/ckeditor5-image/src/imagecaption/imagecaptionediting.js
@@ -115,7 +115,7 @@ export default class ImageCaptionEditing extends Plugin {
}
// Is currently any caption selected?
- if ( viewCaption && !this.editor.isReadOnly ) {
+ if ( viewCaption ) {
// Was any caption selected before?
if ( lastCaption ) {
// Same caption as before?
diff --git a/packages/ckeditor5-image/tests/imagecaption/imagecaptionediting.js b/packages/ckeditor5-image/tests/imagecaption/imagecaptionediting.js
index 43f306e303e..254896089f2 100644
--- a/packages/ckeditor5-image/tests/imagecaption/imagecaptionediting.js
+++ b/packages/ckeditor5-image/tests/imagecaption/imagecaptionediting.js
@@ -519,20 +519,6 @@ describe( 'ImageCaptionEditing', () => {
);
} );
- it( 'should not show empty figcaption when image is selected but editor is in the readOnly mode', () => {
- editor.isReadOnly = true;
-
- setModelData( model, '[ ]' );
-
- expect( getViewData( view ) ).to.equal(
- '[' +
- ' ' +
- ' ' +
- ' ]'
- );
- } );
-
describe( 'undo/redo integration', () => {
it( 'should create view element after redo', () => {
setModelData( model, 'foo [foo bar baz] ' );
diff --git a/packages/ckeditor5-list/src/listediting.js b/packages/ckeditor5-list/src/listediting.js
index 9df189d6b03..bb9c6e18671 100644
--- a/packages/ckeditor5-list/src/listediting.js
+++ b/packages/ckeditor5-list/src/listediting.js
@@ -12,7 +12,6 @@ import IndentCommand from './indentcommand';
import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph';
-import priorities from '@ckeditor/ckeditor5-utils/src/priorities';
import {
cleanList,
@@ -135,8 +134,6 @@ export default class ListEditing extends Plugin {
// Overwrite default Backspace key behavior.
// If Backspace key is pressed with selection collapsed on first position in first list item, outdent it. #83
- //
- // Priority high + 10 to override widget and blockquote feature listener.
this.listenTo( viewDocument, 'delete', ( evt, data ) => {
// Check conditions from those that require less computations like those immediately available.
if ( data.direction !== 'backward' ) {
@@ -171,7 +168,7 @@ export default class ListEditing extends Plugin {
data.preventDefault();
evt.stop();
- }, { priority: priorities.high + 10 } );
+ }, { priority: 'high' } );
const getCommandExecuter = commandName => {
return ( data, cancel ) => {
diff --git a/packages/ckeditor5-list/tests/listediting.js b/packages/ckeditor5-list/tests/listediting.js
index 26813e9c5d1..b780478d83b 100644
--- a/packages/ckeditor5-list/tests/listediting.js
+++ b/packages/ckeditor5-list/tests/listediting.js
@@ -291,21 +291,6 @@ describe( 'ListEditing', () => {
sinon.assert.calledWithExactly( editor.execute, 'outdentList' );
} );
-
- it( 'should outdent empty list when list is nested in block quote', () => {
- const domEvtDataStub = { preventDefault() {}, direction: 'backward' };
-
- sinon.spy( editor, 'execute' );
-
- setModelData(
- model,
- 'x [] '
- );
-
- editor.editing.view.document.fire( 'delete', domEvtDataStub );
-
- sinon.assert.calledWithExactly( editor.execute, 'outdentList' );
- } );
} );
describe( 'tab key handling callback', () => {
diff --git a/packages/ckeditor5-mention/docs/_snippets/examples/chat-with-mentions.html b/packages/ckeditor5-mention/docs/_snippets/examples/chat-with-mentions.html
index a62dbbebd29..ef7557d81dc 100644
--- a/packages/ckeditor5-mention/docs/_snippets/examples/chat-with-mentions.html
+++ b/packages/ckeditor5-mention/docs/_snippets/examples/chat-with-mentions.html
@@ -119,10 +119,6 @@
border-top-right-radius: 0;
}
- .chat .chat__editor + .ck.ck-editor .ck-content.highlighted {
- animation: highlight 600ms ease-out;
- }
-
/* ---- In–editor mention list --------------------------------------------------------------- */
.ck-mentions .mention__item {
diff --git a/packages/ckeditor5-mention/docs/_snippets/examples/chat-with-mentions.js b/packages/ckeditor5-mention/docs/_snippets/examples/chat-with-mentions.js
index d135ebea4f6..41746724c88 100644
--- a/packages/ckeditor5-mention/docs/_snippets/examples/chat-with-mentions.js
+++ b/packages/ckeditor5-mention/docs/_snippets/examples/chat-with-mentions.js
@@ -3,7 +3,7 @@
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
-/* globals console, window, document, setTimeout */
+/* globals console, window, document */
import ClassicEditor from '@ckeditor/ckeditor5-build-classic/src/ckeditor';
import Underline from '@ckeditor/ckeditor5-basic-styles/src/underline';
@@ -68,31 +68,11 @@ ClassicEditor
}
} )
.then( editor => {
- const editingView = editor.editing.view;
- const rootElement = editingView.document.getRoot();
-
window.editor = editor;
// Clone the first message in the chat when "Send" is clicked, fill it with new data
// and append to the chat list.
document.querySelector( '.chat-send' ).addEventListener( 'click', () => {
- const message = editor.getData();
-
- if ( !message ) {
- editingView.change( writer => {
- writer.addClass( 'highlighted', rootElement );
- editingView.focus();
- } );
-
- setTimeout( () => {
- editingView.change( writer => {
- writer.removeClass( 'highlighted', rootElement );
- } );
- }, 650 );
-
- return;
- }
-
const clone = document.querySelector( '.chat__posts li' ).cloneNode( true );
clone.classList.add( 'new-post' );
@@ -105,12 +85,9 @@ ClassicEditor
mailtoUser.href = 'mailto:info@cksource.com';
clone.querySelector( '.chat__posts__post__time' ).textContent = 'just now';
- clone.querySelector( '.chat__posts__post__content' ).innerHTML = message;
+ clone.querySelector( '.chat__posts__post__content' ).innerHTML = editor.getData();
document.querySelector( '.chat__posts' ).appendChild( clone );
-
- editor.setData( '' );
- editingView.focus();
} );
} )
.catch( err => {
diff --git a/packages/ckeditor5-mention/docs/examples/chat-with-mentions.md b/packages/ckeditor5-mention/docs/examples/chat-with-mentions.md
index 24124ae4141..7019d8a150b 100644
--- a/packages/ckeditor5-mention/docs/examples/chat-with-mentions.md
+++ b/packages/ckeditor5-mention/docs/examples/chat-with-mentions.md
@@ -141,10 +141,6 @@ The HTML code of the application is listed below:
border-top-right-radius: 0;
}
- .chat .chat__editor + .ck.ck-editor .ck-content.highlighted {
- animation: highlight 600ms ease-out;
- }
-
/* ---- In–editor mention list --------------------------------------------------------------- */
.ck-mentions .mention__item {
@@ -253,31 +249,11 @@ ClassicEditor
}
} )
.then( editor => {
- const editingView = editor.editing.view;
- const rootElement = editingView.document.getRoot();
-
window.editor = editor;
// Clone the first message in the chat when "Send" is clicked, fill it with new data
// and append to the chat list.
document.querySelector( '.chat-send' ).addEventListener( 'click', () => {
- const message = editor.getData();
-
- if ( !message ) {
- editingView.change( writer => {
- writer.addClass( 'highlighted', rootElement );
- editingView.focus();
- } );
-
- setTimeout( () => {
- editingView.change( writer => {
- writer.removeClass( 'highlighted', rootElement );
- } );
- }, 650 );
-
- return;
- }
-
const clone = document.querySelector( '.chat__posts li' ).cloneNode( true );
clone.classList.add( 'new-post' );
@@ -290,12 +266,9 @@ ClassicEditor
mailtoUser.href = 'mailto:info@cksource.com';
clone.querySelector( '.chat__posts__post__time' ).textContent = 'just now';
- clone.querySelector( '.chat__posts__post__content' ).innerHTML = message;
+ clone.querySelector( '.chat__posts__post__content' ).innerHTML = editor.getData();
document.querySelector( '.chat__posts' ).appendChild( clone );
-
- editor.setData( '' );
- editingView.focus();
} );
} )
.catch( err => {
diff --git a/packages/ckeditor5-mention/src/mentionediting.js b/packages/ckeditor5-mention/src/mentionediting.js
index 176436549cf..4f6e67a06a3 100644
--- a/packages/ckeditor5-mention/src/mentionediting.js
+++ b/packages/ckeditor5-mention/src/mentionediting.js
@@ -49,7 +49,7 @@ export default class MentionEditing extends Plugin {
},
model: {
key: 'mention',
- value: viewElement => _toMentionAttribute( viewElement )
+ value: _toMentionAttribute
}
} );
diff --git a/packages/ckeditor5-mention/tests/mentionediting.js b/packages/ckeditor5-mention/tests/mentionediting.js
index b3915378176..0408a3ca0b5 100644
--- a/packages/ckeditor5-mention/tests/mentionediting.js
+++ b/packages/ckeditor5-mention/tests/mentionediting.js
@@ -238,20 +238,6 @@ describe( 'MentionEditing', () => {
expect( editor.getData() ).to.equal( expectedView );
expect( getViewData( editor.editing.view, { withoutSelection: true } ) ).to.equal( expectedView );
} );
-
- // https://github.com/ckeditor/ckeditor5/issues/8370
- it( 'should pass down only relevant attributes', () => {
- editor.setData( 'fooJohn
' );
-
- const textNode = doc.getRoot().getChild( 0 ).getChild( 1 );
- const attributeValue = textNode.getAttribute( 'mention' );
-
- expect( Object.keys( attributeValue ) ).to.have.members( [
- 'id',
- 'uid',
- '_text'
- ] );
- } );
} );
describe( 'selection post-fixer', () => {
diff --git a/packages/ckeditor5-typing/src/deletecommand.js b/packages/ckeditor5-typing/src/deletecommand.js
index 1b5a4b2e8d0..fc19e45acad 100644
--- a/packages/ckeditor5-typing/src/deletecommand.js
+++ b/packages/ckeditor5-typing/src/deletecommand.js
@@ -77,7 +77,6 @@ export default class DeleteCommand extends Command {
this._buffer.lock();
const selection = writer.createSelection( options.selection || doc.selection );
- const sequence = options.sequence || 1;
// Do not replace the whole selected content if selection was collapsed.
// This prevents such situation:
@@ -92,20 +91,12 @@ export default class DeleteCommand extends Command {
}
// Check if deleting in an empty editor. See #61.
- if ( this._shouldEntireContentBeReplacedWithParagraph( sequence ) ) {
+ if ( this._shouldEntireContentBeReplacedWithParagraph( options.sequence || 1 ) ) {
this._replaceEntireContentWithParagraph( writer );
return;
}
- // Check if deleting in the first empty block.
- // See https://github.com/ckeditor/ckeditor5/issues/8137.
- if ( this._shouldReplaceFirstBlockWithParagraph( selection, sequence ) ) {
- this.editor.execute( 'paragraph', { selection } );
-
- return;
- }
-
// If selection is still collapsed, then there's nothing to delete.
if ( selection.isCollapsed ) {
return;
@@ -189,7 +180,6 @@ export default class DeleteCommand extends Command {
* The entire content is replaced with the paragraph. Selection is moved inside the paragraph.
*
* @private
- * @param {module:engine/model/writer~Writer} writer The model writer.
*/
_replaceEntireContentWithParagraph( writer ) {
const model = this.editor.model;
@@ -203,53 +193,4 @@ export default class DeleteCommand extends Command {
writer.setSelection( paragraph, 0 );
}
-
- /**
- * Checks if the selection is inside an empty element that is the first child of the limit element
- * and should be replaced with a paragraph.
- *
- * @private
- * @param {module:engine/model/selection~Selection} selection The selection.
- * @param {Number} sequence A number describing which subsequent delete event it is without the key being released.
- * @returns {Boolean}
- */
- _shouldReplaceFirstBlockWithParagraph( selection, sequence ) {
- const model = this.editor.model;
-
- // Does nothing if user pressed and held the "Backspace" key or it was a "Delete" button.
- if ( sequence > 1 || this.direction != 'backward' ) {
- return false;
- }
-
- if ( !selection.isCollapsed ) {
- return false;
- }
-
- const position = selection.getFirstPosition();
- const limitElement = model.schema.getLimitElement( position );
- const limitElementFirstChild = limitElement.getChild( 0 );
-
- // Only elements that are direct children of the limit element can be replaced.
- // Unwrapping from a block quote should be handled in a dedicated feature.
- if ( position.parent != limitElementFirstChild ) {
- return false;
- }
-
- // A block should be replaced only if it was empty.
- if ( !selection.containsEntireContent( limitElementFirstChild ) ) {
- return false;
- }
-
- // Replace with a paragraph only if it's allowed there.
- if ( !model.schema.checkChild( limitElement, 'paragraph' ) ) {
- return false;
- }
-
- // Does nothing if the limit element already contains only a paragraph.
- if ( limitElementFirstChild.name == 'paragraph' ) {
- return false;
- }
-
- return true;
- }
}
diff --git a/packages/ckeditor5-typing/tests/deletecommand.js b/packages/ckeditor5-typing/tests/deletecommand.js
index f59e37b2494..c6866f8087c 100644
--- a/packages/ckeditor5-typing/tests/deletecommand.js
+++ b/packages/ckeditor5-typing/tests/deletecommand.js
@@ -3,16 +3,13 @@
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
+import VirtualTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/virtualtesteditor';
+import ModelTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/modeltesteditor';
import DeleteCommand from '../src/deletecommand';
import Delete from '../src/delete';
import ChangeBuffer from '../src/utils/changebuffer';
-
-import ParagraphCommand from '@ckeditor/ckeditor5-paragraph/src/paragraphcommand';
-import VirtualTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/virtualtesteditor';
-import ModelTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/modeltesteditor';
-import testUtils from '@ckeditor/ckeditor5-core/tests/_utils/utils';
-
import { getData, setData } from '@ckeditor/ckeditor5-engine/src/dev-utils/model';
+import testUtils from '@ckeditor/ckeditor5-core/tests/_utils/utils';
describe( 'DeleteCommand', () => {
let editor, model, doc;
@@ -29,8 +26,6 @@ describe( 'DeleteCommand', () => {
const command = new DeleteCommand( editor, 'backward' );
editor.commands.add( 'delete', command );
- editor.commands.add( 'paragraph', new ParagraphCommand( editor ) );
-
model.schema.register( 'paragraph', { inheritAllFrom: '$block' } );
model.schema.register( 'heading1', { inheritAllFrom: '$block' } );
} );
@@ -320,71 +315,5 @@ describe( 'DeleteCommand', () => {
expect( getData( model ) ).to.equal( '[] ' );
} );
-
- describe( 'with the empty first block', () => {
- it( 'replaces the first empty block with paragraph', () => {
- setData( model, '[] foo ' );
-
- editor.execute( 'delete' );
-
- expect( getData( model ) ).to.equal( '[] foo ' );
- } );
-
- it( 'does not replace an element when Backspace key is held', () => {
- setData( model, 'foo[] bar ' );
-
- for ( let sequence = 1; sequence < 10; ++sequence ) {
- editor.execute( 'delete', { sequence } );
- }
-
- expect( getData( model ) ).to.equal( '[] bar ' );
- } );
-
- it( 'does not replace with paragraph in another paragraph already occurs in limit element', () => {
- setData( model, '[] foo ' );
-
- const element = doc.getRoot().getNodeByPath( [ 0 ] );
-
- editor.execute( 'delete' );
-
- expect( element ).is.equal( doc.getRoot().getNodeByPath( [ 0 ] ) );
- expect( getData( model ) ).to.equal( '[] foo ' );
- } );
-
- it( 'does not replace an element if a paragraph is not allowed in current position', () => {
- model.schema.addChildCheck( ( ctx, childDef ) => {
- if ( ctx.endsWith( '$root' ) && childDef.name == 'paragraph' ) {
- return false;
- }
- } );
-
- setData( model, '[] foo ' );
-
- editor.execute( 'delete' );
-
- expect( getData( model ) ).to.equal( '[] foo ' );
- } );
-
- it( 'does not replace an element if it\'s not empty', () => {
- setData( model, '[]foo bar ' );
-
- editor.execute( 'delete' );
-
- expect( getData( model ) ).to.equal( '[]foo bar ' );
- } );
-
- it( 'does not replace an element if it\'s wrapped with some other element', () => {
- model.schema.register( 'blockQuote', {
- allowWhere: '$block',
- allowContentOf: '$root'
- } );
-
- setData( model, '[] bar ' );
-
- editor.execute( 'delete' );
-
- expect( getData( model ) ).to.equal( '[] bar ' );
- } );
- } );
} );
} );
diff --git a/packages/ckeditor5-ui/src/toolbar/balloon/balloontoolbar.js b/packages/ckeditor5-ui/src/toolbar/balloon/balloontoolbar.js
index 94e2e797417..165b8d37638 100644
--- a/packages/ckeditor5-ui/src/toolbar/balloon/balloontoolbar.js
+++ b/packages/ckeditor5-ui/src/toolbar/balloon/balloontoolbar.js
@@ -185,7 +185,7 @@ export default class BalloonToolbar extends Plugin {
afterInit() {
const factory = this.editor.ui.componentFactory;
- this.toolbarView.fillFromConfig( this._balloonConfig, factory );
+ this.toolbarView.fillFromConfig( this._balloonConfig.items, factory );
}
/**
diff --git a/packages/ckeditor5-ui/src/toolbar/block/blocktoolbar.js b/packages/ckeditor5-ui/src/toolbar/block/blocktoolbar.js
index 42860733317..773588c8cee 100644
--- a/packages/ckeditor5-ui/src/toolbar/block/blocktoolbar.js
+++ b/packages/ckeditor5-ui/src/toolbar/block/blocktoolbar.js
@@ -179,7 +179,7 @@ export default class BlockToolbar extends Plugin {
const factory = this.editor.ui.componentFactory;
const config = this._blockToolbarConfig;
- this.toolbarView.fillFromConfig( config, factory );
+ this.toolbarView.fillFromConfig( config.items, factory );
// Hide panel before executing each button in the panel.
for ( const item of this.toolbarView.items ) {
diff --git a/packages/ckeditor5-ui/src/toolbar/normalizetoolbarconfig.js b/packages/ckeditor5-ui/src/toolbar/normalizetoolbarconfig.js
index 56f14291597..b07935de123 100644
--- a/packages/ckeditor5-ui/src/toolbar/normalizetoolbarconfig.js
+++ b/packages/ckeditor5-ui/src/toolbar/normalizetoolbarconfig.js
@@ -18,7 +18,6 @@
*
* toolbar: {
* items: [ 'heading', 'bold', 'italic', 'link', ... ],
- * removeItems: [ 'bold' ],
* ...
* }
*
@@ -32,20 +31,17 @@
export default function normalizeToolbarConfig( config ) {
if ( Array.isArray( config ) ) {
return {
- items: config,
- removeItems: []
+ items: config
};
}
if ( !config ) {
return {
- items: [],
- removeItems: []
+ items: []
};
}
return Object.assign( {
- items: [],
- removeItems: []
+ items: []
}, config );
}
diff --git a/packages/ckeditor5-ui/src/toolbar/toolbarview.js b/packages/ckeditor5-ui/src/toolbar/toolbarview.js
index 3fcf50b287e..3244b4d5900 100644
--- a/packages/ckeditor5-ui/src/toolbar/toolbarview.js
+++ b/packages/ckeditor5-ui/src/toolbar/toolbarview.js
@@ -20,7 +20,6 @@ import global from '@ckeditor/ckeditor5-utils/src/dom/global';
import { createDropdown, addToolbarToDropdown } from '../dropdown/utils';
import { logWarning } from '@ckeditor/ckeditor5-utils/src/ckeditorerror';
import verticalDotsIcon from '@ckeditor/ckeditor5-core/theme/icons/three-vertical-dots.svg';
-import normalizeToolbarConfig from './normalizetoolbarconfig';
import '../../theme/components/toolbar/toolbar.css';
@@ -278,125 +277,58 @@ export default class ToolbarView extends View {
* A utility that expands the plain toolbar configuration into
* {@link module:ui/toolbar/toolbarview~ToolbarView#items} using a given component factory.
*
- * @param {Array.|Object} itemsOrConfig The toolbar items or the entire toolbar configuration object.
+ * @param {Array.} config The toolbar items configuration.
* @param {module:ui/componentfactory~ComponentFactory} factory A factory producing toolbar items.
*/
- fillFromConfig( itemsOrConfig, factory ) {
- const config = normalizeToolbarConfig( itemsOrConfig );
-
- const itemsToClean = config.items
- .filter( ( name, idx, items ) => {
- if ( name === '|' ) {
- return true;
- }
-
- // Items listed in `config.removeItems` should not be added to the toolbar.
- if ( config.removeItems.indexOf( name ) !== -1 ) {
- return false;
- }
-
- if ( name === '-' ) {
- // Toolbar line breaks must not be rendered when toolbar grouping is enabled.
- // (https://github.com/ckeditor/ckeditor5/issues/8582)
- if ( this.options.shouldGroupWhenFull ) {
- /**
- * Toolbar line breaks (`-` items) can only work when the automatic button grouping
- * is disabled in the toolbar configuration.
- * To do this, set the `shouldNotGroupWhenFull` option to `true` in the editor configuration:
- *
- * const config = {
- * toolbar: {
- * items: [ ... ],
- * shouldNotGroupWhenFull: true
- * }
- * }
- *
- * Learn more about {@link module:core/editor/editorconfig~EditorConfig#toolbar toolbar configuration}.
- *
- * @error toolbarview-line-break-ignored-when-grouping-items
- */
- logWarning( 'toolbarview-line-break-ignored-when-grouping-items', items );
-
- return false;
- }
-
- return true;
- }
-
- // For the items that cannot be instantiated we are sending warning message. We also filter them out.
- if ( !factory.has( name ) ) {
+ fillFromConfig( config, factory ) {
+ this.items.addMany( config.map( name => {
+ if ( name == '|' ) {
+ return new ToolbarSeparatorView();
+ } else if ( name == '-' ) {
+ if ( this.options.shouldGroupWhenFull ) {
/**
- * There was a problem processing the configuration of the toolbar. The item with the given
- * name does not exist so it was omitted when rendering the toolbar.
- *
- * This warning usually shows up when the {@link module:core/plugin~Plugin} which is supposed
- * to provide a toolbar item has not been loaded or there is a typo in the configuration.
- *
- * Make sure the plugin responsible for this toolbar item is loaded and the toolbar configuration
- * is correct, e.g. {@link module:basic-styles/bold~Bold} is loaded for the `'bold'` toolbar item.
+ * Toolbar line breaks (`-` items) can only work when the automatic button grouping
+ * is disabled in the toolbar configuration.
+ * To do this, set the `shouldNotGroupWhenFull` option to `true` in the editor configuration:
*
- * You can use the following snippet to retrieve all available toolbar items:
+ * const config = {
+ * toolbar: {
+ * items: [ ... ],
+ * shouldNotGroupWhenFull: true
+ * }
+ * }
*
- * Array.from( editor.ui.componentFactory.names() );
+ * Learn more about {@link module:core/editor/editorconfig~EditorConfig#toolbar toolbar configuration}.
*
- * @error toolbarview-item-unavailable
- * @param {String} name The name of the component.
+ * @error toolbarview-line-break-ignored-when-grouping-items
*/
- logWarning( 'toolbarview-item-unavailable', { name } );
-
- return false;
- }
-
- return true;
- } );
-
- const itemsToAdd = this._cleanSeparators( itemsToClean )
- // Instantiate toolbar items.
- .map( name => {
- if ( name === '|' ) {
- return new ToolbarSeparatorView();
- } else if ( name === '-' ) {
- return new ToolbarLineBreakView();
+ logWarning( 'toolbarview-line-break-ignored-when-grouping-items', config );
}
+ return new ToolbarLineBreakView();
+ } else if ( factory.has( name ) ) {
return factory.create( name );
- } );
-
- this.items.addMany( itemsToAdd );
- }
-
- /**
- * Remove leading, trailing, and duplicated separators (`-` and `|`).
- *
- * @private
- * @param {Array.} items
- */
- _cleanSeparators( items ) {
- const nonSeparatorPredicate = item => ( item !== '-' && item !== '|' );
- const count = items.length;
-
- // Find an index of the first item that is not a separator.
- const firstCommandItem = items.findIndex( nonSeparatorPredicate );
-
- // Search from the end of the list, then convert found index back to the original direction.
- const lastCommandItem = count - items
- .slice()
- .reverse()
- .findIndex( nonSeparatorPredicate );
-
- return items
- // Return items without the leading and trailing separators.
- .slice( firstCommandItem, lastCommandItem )
- // Remove duplicated separators.
- .filter( ( name, idx, items ) => {
- // Filter only separators.
- if ( nonSeparatorPredicate( name ) ) {
- return true;
- }
- const isDuplicated = idx > 0 && items[ idx - 1 ] === name;
-
- return !isDuplicated;
- } );
+ } else {
+ /**
+ * There was a problem processing the configuration of the toolbar. The item with the given
+ * name does not exist so it was omitted when rendering the toolbar.
+ *
+ * This warning usually shows up when the {@link module:core/plugin~Plugin} which is supposed
+ * to provide a toolbar item has not been loaded or there is a typo in the configuration.
+ *
+ * Make sure the plugin responsible for this toolbar item is loaded and the toolbar configuration
+ * is correct, e.g. {@link module:basic-styles/bold~Bold} is loaded for the `'bold'` toolbar item.
+ *
+ * You can use the following snippet to retrieve all available toolbar items:
+ *
+ * Array.from( editor.ui.componentFactory.names() );
+ *
+ * @error toolbarview-item-unavailable
+ * @param {String} name The name of the component.
+ */
+ logWarning( 'toolbarview-item-unavailable', { name } );
+ }
+ } ).filter( item => item !== undefined ) );
}
/**
@@ -931,7 +863,6 @@ class DynamicGrouping {
dropdown.buttonView.set( {
label: t( 'Show more items' ),
tooltip: true,
- tooltipPosition: locale.uiLanguageDirection === 'rtl' ? 'se' : 'sw',
icon: verticalDotsIcon
} );
diff --git a/packages/ckeditor5-ui/tests/toolbar/normalizetoolbarconfig.js b/packages/ckeditor5-ui/tests/toolbar/normalizetoolbarconfig.js
index 1d70467330f..f648c777ae0 100644
--- a/packages/ckeditor5-ui/tests/toolbar/normalizetoolbarconfig.js
+++ b/packages/ckeditor5-ui/tests/toolbar/normalizetoolbarconfig.js
@@ -7,16 +7,11 @@ import normalizeToolbarConfig from '../../src/toolbar/normalizetoolbarconfig';
describe( 'normalizeToolbarConfig()', () => {
it( 'normalizes the config specified as an Array', () => {
- const items = [ 'foo', 'bar' ];
- const normalized = normalizeToolbarConfig( items );
+ const cfg = [ 'foo', 'bar' ];
+ const normalized = normalizeToolbarConfig( cfg );
expect( normalized ).to.be.an( 'object' );
- expect( normalized ).to.deep.equal(
- {
- items,
- removeItems: []
- }
- );
+ expect( normalized.items ).to.deep.equal( cfg );
} );
it( 'passes through an already normalized config', () => {
@@ -26,9 +21,7 @@ describe( 'normalizeToolbarConfig()', () => {
};
const normalized = normalizeToolbarConfig( cfg );
- expect( normalized ).to.deep.equal(
- Object.assign( { removeItems: [] }, cfg )
- );
+ expect( normalized ).to.deep.equal( cfg );
} );
it( 'adds missing items property', () => {
@@ -40,7 +33,6 @@ describe( 'normalizeToolbarConfig()', () => {
expect( normalized ).to.deep.equal( {
items: [],
- removeItems: [],
foo: 'bar'
} );
expect( normalized ).to.not.equal( cfg ); // Make sure we don't modify an existing obj.
@@ -51,6 +43,5 @@ describe( 'normalizeToolbarConfig()', () => {
expect( normalized ).to.be.an( 'object' );
expect( normalized.items ).to.be.an( 'array' ).of.length( 0 );
- expect( normalized.removeItems ).to.be.an( 'array' ).of.length( 0 );
} );
} );
diff --git a/packages/ckeditor5-ui/tests/toolbar/toolbarview.js b/packages/ckeditor5-ui/tests/toolbar/toolbarview.js
index 62e43aef06d..f0c192b6a60 100644
--- a/packages/ckeditor5-ui/tests/toolbar/toolbarview.js
+++ b/packages/ckeditor5-ui/tests/toolbar/toolbarview.js
@@ -438,6 +438,7 @@ describe( 'ToolbarView', () => {
view.fillFromConfig( [ 'foo', '-', 'bar', '|', 'foo' ], factory );
const items = view.items;
+
expect( items ).to.have.length( 5 );
expect( items.get( 0 ).name ).to.equal( 'foo' );
expect( items.get( 1 ) ).to.be.instanceOf( ToolbarLineBreakView );
@@ -446,96 +447,6 @@ describe( 'ToolbarView', () => {
expect( items.get( 4 ).name ).to.equal( 'foo' );
} );
- it( 'accepts configuration object', () => {
- view.fillFromConfig( { items: [ 'foo', 'bar', 'foo' ] }, factory );
-
- const items = view.items;
- expect( items ).to.have.length( 3 );
- expect( items.get( 0 ).name ).to.equal( 'foo' );
- expect( items.get( 1 ).name ).to.equal( 'bar' );
- expect( items.get( 2 ).name ).to.equal( 'foo' );
- } );
-
- it( 'removes items listed in `removeItems`', () => {
- view.fillFromConfig(
- {
- items: [ 'foo', 'bar', 'foo' ],
- removeItems: [ 'foo' ]
- },
- factory
- );
-
- const items = view.items;
- expect( items ).to.have.length( 1 );
- expect( items.get( 0 ).name ).to.equal( 'bar' );
- } );
-
- it( 'deduplicates consecutive separators after removing items listed in `removeItems` - the vertical separator case (`|`)', () => {
- view.fillFromConfig(
- {
- items: [ '|', '|', 'foo', '|', 'bar', '|', 'foo' ],
- removeItems: [ 'bar' ]
- },
- factory
- );
-
- const items = view.items;
-
- expect( items ).to.have.length( 3 );
- expect( items.get( 0 ).name ).to.equal( 'foo' );
- expect( items.get( 1 ) ).to.be.instanceOf( ToolbarSeparatorView );
- expect( items.get( 2 ).name ).to.equal( 'foo' );
- } );
-
- it( 'deduplicates consecutive separators after removing items listed in `removeItems` - the line break case (`-`)', () => {
- view.fillFromConfig(
- {
- items: [ '-', '-', 'foo', '-', 'bar', '-', 'foo' ],
- removeItems: [ 'bar' ]
- },
- factory
- );
-
- const items = view.items;
-
- expect( items ).to.have.length( 3 );
- expect( items.get( 0 ).name ).to.equal( 'foo' );
- expect( items.get( 1 ) ).to.be.instanceOf( ToolbarLineBreakView );
- expect( items.get( 2 ).name ).to.equal( 'foo' );
- } );
-
- it( 'removes trailing and leading separators from the item list - the vertical separator case (`|`)', () => {
- view.fillFromConfig(
- {
- items: [ '|', '|', 'foo', '|', 'bar', '|' ]
- },
- factory
- );
-
- const items = view.items;
-
- expect( items ).to.have.length( 3 );
- expect( items.get( 0 ).name ).to.equal( 'foo' );
- expect( items.get( 1 ) ).to.be.instanceOf( ToolbarSeparatorView );
- expect( items.get( 2 ).name ).to.equal( 'bar' );
- } );
-
- it( 'removes trailing and leading separators from the item list - the line break case (`-`)', () => {
- view.fillFromConfig(
- {
- items: [ '-', '-', 'foo', '-', 'bar', '-' ]
- },
- factory
- );
-
- const items = view.items;
-
- expect( items ).to.have.length( 3 );
- expect( items.get( 0 ).name ).to.equal( 'foo' );
- expect( items.get( 1 ) ).to.be.instanceOf( ToolbarLineBreakView );
- expect( items.get( 2 ).name ).to.equal( 'bar' );
- } );
-
it( 'warns if there is no such component in the factory', () => {
const items = view.items;
const consoleWarnStub = sinon.stub( console, 'warn' );
@@ -567,22 +478,6 @@ describe( 'ToolbarView', () => {
sinon.match.string // Link to the documentation.
);
} );
-
- // https://github.com/ckeditor/ckeditor5/issues/8582
- it( 'does not render line separator when the button grouping option is enabled', () => {
- // Catch warn to stop tests from failing in production mode.
- sinon.stub( console, 'warn' );
-
- view.options.shouldGroupWhenFull = true;
-
- view.fillFromConfig( [ 'foo', '-', 'bar' ], factory );
-
- const items = view.items;
-
- expect( items ).to.have.length( 2 );
- expect( items.get( 0 ).name ).to.equal( 'foo' );
- expect( items.get( 1 ).name ).to.equal( 'bar' );
- } );
} );
describe( 'toolbar with static items', () => {
@@ -1087,26 +982,6 @@ describe( 'ToolbarView', () => {
expect( groupedItemsDropdown.buttonView.label ).to.equal( 'Show more items' );
} );
- it( 'tooltip has the proper position depending on the UI language direction (LTR UI)', () => {
- const locale = new Locale( { uiLanguage: 'en' } );
- const view = new ToolbarView( locale, { shouldGroupWhenFull: true } );
- view.render();
-
- expect( view._behavior.groupedItemsDropdown.buttonView.tooltipPosition ).to.equal( 'sw' );
-
- view.destroy();
- } );
-
- it( 'tooltip has the proper position depending on the UI language direction (RTL UI)', () => {
- const locale = new Locale( { uiLanguage: 'ar' } );
- const view = new ToolbarView( locale, { shouldGroupWhenFull: true } );
- view.render();
-
- expect( view._behavior.groupedItemsDropdown.buttonView.tooltipPosition ).to.equal( 'se' );
-
- view.destroy();
- } );
-
it( 'shares its toolbarView#items with grouped items', () => {
view.items.add( focusable() );
view.items.add( focusable() );
diff --git a/scripts/docs/snippetadapter.js b/scripts/docs/snippetadapter.js
index 0a511825488..390170ccfe8 100644
--- a/scripts/docs/snippetadapter.js
+++ b/scripts/docs/snippetadapter.js
@@ -188,18 +188,8 @@ module.exports = function snippetAdapter( snippets, options, umbertoHelpers ) {
jsFiles.push( path.join( snippetData.basePath, 'assets', 'snippet.js' ) );
jsFiles.push( path.join( snippetData.relativeOutputPath, snippetData.snippetName, 'snippet.js' ) );
- jsFiles.push( path.join( snippetData.relativeOutputPath,
- '../../../../../', 'node_modules', '@popperjs', 'core', 'dist', 'umd', 'popper.min.js' ) );
- jsFiles.push( path.join( snippetData.relativeOutputPath,
- '../../../../../', 'node_modules', 'tippy.js', 'dist', 'tippy-bundle.umd.min.js' ) );
- jsFiles.push( path.join( snippetData.basePath, 'assets', 'tour-balloon.js' ) );
cssFiles.push( path.join( snippetData.basePath, 'assets', 'snippet-styles.css' ) );
- cssFiles.push( path.join( snippetData.relativeOutputPath,
- '../../../../../', 'node_modules', 'tippy.js', 'dist', 'tippy.css' ) );
- cssFiles.push( path.join( snippetData.relativeOutputPath,
- '../../../../../', 'node_modules', 'tippy.js', 'themes', 'light-border.css' ) );
- cssFiles.push( path.join( snippetData.basePath, 'assets', 'tour-balloon.css' ) );
if ( wasCSSGenerated ) {
cssFiles.unshift( path.join( snippetData.relativeOutputPath, snippetData.snippetName, 'snippet.css' ) );
From 4d49a063c2573f8d35b344a1b144fb79c2ed5105 Mon Sep 17 00:00:00 2001
From: Mgsy
Date: Tue, 5 Jan 2021 10:46:50 +0100
Subject: [PATCH 24/40] Updated WProofreader version.
---
package.json | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/package.json b/package.json
index 560890d07fb..e970fd715fb 100644
--- a/package.json
+++ b/package.json
@@ -90,7 +90,7 @@
"@ckeditor/ckeditor5-react": "^3.0.0",
"@ckeditor/ckeditor5-real-time-collaboration": ">=24.0.0",
"@ckeditor/ckeditor5-track-changes": ">=24.0.0",
- "@webspellchecker/wproofreader-ckeditor5": "^1.0.5",
+ "@webspellchecker/wproofreader-ckeditor5": "^2.0.1",
"@wiris/mathtype-ckeditor5": "^7.24.0",
"babel-standalone": "^6.26.0",
"cli-table": "^0.3.1",
From 44fbe8d42f22b4066ddb9e82e7ea1270012a29a4 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Maksymilian=20Barna=C5=9B?=
Date: Tue, 5 Jan 2021 11:03:37 +0100
Subject: [PATCH 25/40] Add test.
---
.../ckeditor5-link/tests/linkimageediting.js | 58 +++++++++++++------
1 file changed, 40 insertions(+), 18 deletions(-)
diff --git a/packages/ckeditor5-link/tests/linkimageediting.js b/packages/ckeditor5-link/tests/linkimageediting.js
index a05a35dab13..f0c9e16c7ba 100644
--- a/packages/ckeditor5-link/tests/linkimageediting.js
+++ b/packages/ckeditor5-link/tests/linkimageediting.js
@@ -857,27 +857,49 @@ describe( 'LinkImageEditing', () => {
} );
// See #8401.
- it( 'order of model updates should not affect converters', () => {
- setModelData( model,
- '[ ]'
- );
+ describe( 'order of model updates', () => {
+ it( 'should not affect converters - base link attributes first', () => {
+ setModelData( model,
+ '[ ]'
+ );
- model.change( writer => {
- const ranges = model.schema.getValidRanges( model.document.selection.getRanges(), 'linkIsDownloadable' );
-
- for ( const range of ranges ) {
- // `linkHref` gets processed first, as it is just the first property assigned to the model by `LinkCommand`.
- // Here we force attributes to be set on a model in a different order,
- // to force unusual order of downcast converters down the line.
- writer.setAttribute( 'linkIsDownloadable', true, range );
- writer.setAttribute( 'linkHref', 'url', range );
- }
+ model.change( writer => {
+ const ranges = model.schema.getValidRanges( model.document.selection.getRanges(), 'linkIsDownloadable' );
+
+ for ( const range of ranges ) {
+ // The `linkHref` should be processed first - this is the default order of `LinkCommand`.
+ writer.setAttribute( 'linkHref', 'url', range );
+ writer.setAttribute( 'linkIsDownloadable', true, range );
+ }
+ } );
+
+ expect( getModelData( model, { withoutSelection: true } ) ).to.equal(
+ ' '
+ );
} );
- expect( getModelData( model, { withoutSelection: true } ) ).to.equal(
- ' '
- );
+ it( 'should not affect converters - decorators first', () => {
+ setModelData( model,
+ '[ ]'
+ );
+
+ model.change( writer => {
+ const ranges = model.schema.getValidRanges( model.document.selection.getRanges(), 'linkIsDownloadable' );
+
+ for ( const range of ranges ) {
+ // Here we force attributes to be set on a model in a different order
+ // to force unusual order of downcast converters down the line.
+ // Normally, the `linkHref` gets processed first, as it is just the first property assigned
+ // to the model by `LinkCommand`.
+ writer.setAttribute( 'linkIsDownloadable', true, range );
+ writer.setAttribute( 'linkHref', 'url', range );
+ }
+ } );
+
+ expect( getModelData( model, { withoutSelection: true } ) ).to.equal(
+ ' '
+ );
+ } );
} );
} );
} );
From 464f8fe8625b80369a1087bdad6d9644c4877622 Mon Sep 17 00:00:00 2001
From: godai78
Date: Tue, 5 Jan 2021 12:24:09 +0100
Subject: [PATCH 26/40] Docs: duplicate infobox.
---
docs/features/spelling-and-grammar-checking.md | 6 +-----
1 file changed, 1 insertion(+), 5 deletions(-)
diff --git a/docs/features/spelling-and-grammar-checking.md b/docs/features/spelling-and-grammar-checking.md
index a4de5f1ef88..62009ae3c71 100644
--- a/docs/features/spelling-and-grammar-checking.md
+++ b/docs/features/spelling-and-grammar-checking.md
@@ -22,7 +22,7 @@ See the spelling and grammar checking in the editor below.
The proofreader badge in the bottom right corner shows you the number of mistakes detected. Hover on an underlined word to display the proofreader suggestions for any of the spelling and grammar mistakes found. If you want to see an overview of all spelling and grammar mistakes, click the "Proofread in dialog" option in the toolbar dropdown. You can access the proofreader settings from the toolbar, too.
- The toolbar button has been introduced in version 2.x of the WProofreader. If you are still using version 1.x, the available settings and dialog options are located in the bottom-right indicator.
+ The toolbar button has been introduced in version 2.x of the WProofreader. Read more about configuring UI items in the {@link features/toolbar toolbar guide}. If you are still using version 1.x, the available settings and dialog options are located in the bottom-right indicator.
{@snippet features/wproofreader}
@@ -83,10 +83,6 @@ ClassicEditor
} )
```
-
- Please notice that the toolbar item is only added in versions 2.x or higher. Read more about configuring UI items in the {@link features/toolbar toolbar guide}. For version 1.x simply ommit adding the item to the toolbar configuration.
-
-
Refer to the [official documentation](https://github.com/WebSpellChecker/wproofreader-ckeditor5#install-instructions) for more details about the cloud setup and available configuration options.
### WProofreader Server
From f0985d08558d770b6dab001855558d6cfb6cec72 Mon Sep 17 00:00:00 2001
From: Mgsy
Date: Tue, 5 Jan 2021 12:58:05 +0100
Subject: [PATCH 27/40] Updated spellchecker snippets.
---
docs/features/spelling-and-grammar-checking.md | 7 ++-----
1 file changed, 2 insertions(+), 5 deletions(-)
diff --git a/docs/features/spelling-and-grammar-checking.md b/docs/features/spelling-and-grammar-checking.md
index 62009ae3c71..802b89d8cf6 100644
--- a/docs/features/spelling-and-grammar-checking.md
+++ b/docs/features/spelling-and-grammar-checking.md
@@ -41,16 +41,15 @@ WProofreader is delivered as a CKEditor 5 plugin, so it could be combined into a
npm install --save @webspellchecker/wproofreader-ckeditor5
```
-Then, add it to your plugin list:
+Then, add it to your plugin list and the toolbar configuration:
```js
import WProofreader from '@webspellchecker/wproofreader-ckeditor5/src/wproofreader';
-// ...
ClassicEditor
.create( editorElement, {
plugins: [ ..., WProofreader ],
- // ...
+ toolbar: [ ..., 'wproofreader' ]
} )
.then( ... )
.catch( ... );
@@ -70,7 +69,6 @@ Add the following configuration to your editor:
```js
import WProofreader from '@webspellchecker/wproofreader-ckeditor5/src/wproofreader';
-// ...
ClassicEditor
.create( editorElement, {
@@ -93,7 +91,6 @@ You will need to add the following configuration to your editor:
```js
import WProofreader from '@webspellchecker/wproofreader-ckeditor5/src/wproofreader';
-// ...
ClassicEditor
.create( editorElement, {
From 1fb76e5962973d647666250f8d515e3344890ea4 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Maksymilian=20Barna=C5=9B?=
Date: Thu, 7 Jan 2021 10:42:31 +0100
Subject: [PATCH 28/40] Stop upcasting early. Add/update tests.
---
.../src/converters/tableproperties.js | 12 +++----
.../tests/tableclipboard-paste.js | 2 +-
.../tableproperties/tablepropertiesediting.js | 36 +++++++++++++++++--
3 files changed, 41 insertions(+), 9 deletions(-)
diff --git a/packages/ckeditor5-table/src/converters/tableproperties.js b/packages/ckeditor5-table/src/converters/tableproperties.js
index b5013fb2c52..620a8835ece 100644
--- a/packages/ckeditor5-table/src/converters/tableproperties.js
+++ b/packages/ckeditor5-table/src/converters/tableproperties.js
@@ -38,6 +38,12 @@ export function upcastStyleToAttribute( conversion, modelElement, modelAttribute
*/
export function upcastBorderStyles( conversion, viewElementName ) {
conversion.for( 'upcast' ).add( dispatcher => dispatcher.on( 'element:' + viewElementName, ( evt, data, conversionApi ) => {
+ // If the element was not converted by element-to-element converter,
+ // we should not try to convert the style. See #8393.
+ if ( !data.modelRange ) {
+ return;
+ }
+
// TODO: this is counter-intuitive: ie.: if only `border-top` is defined then `hasStyle( 'border' )` also returns true.
// TODO: this might needs to be fixed in styles normalizer.
const stylesToConsume = [
@@ -60,12 +66,6 @@ export function upcastBorderStyles( conversion, viewElementName ) {
return;
}
- // This can happen when the upcasted table is nested table. As to why it happens, it remains a mystery.
- // Take a look at https://github.com/ckeditor/ckeditor5/issues/6177.
- if ( !data.modelRange ) {
- data = Object.assign( data, conversionApi.convertChildren( data.viewItem, data.modelCursor ) );
- }
-
const modelElement = [ ...data.modelRange.getItems( { shallow: true } ) ].pop();
conversionApi.consumable.consume( data.viewItem, matcherPattern );
diff --git a/packages/ckeditor5-table/tests/tableclipboard-paste.js b/packages/ckeditor5-table/tests/tableclipboard-paste.js
index e0c6ab65805..005b7e9748a 100644
--- a/packages/ckeditor5-table/tests/tableclipboard-paste.js
+++ b/packages/ckeditor5-table/tests/tableclipboard-paste.js
@@ -3968,7 +3968,7 @@ describe( 'table clipboard', () => {
);
assertEqualMarkup( getModelData( model, { withoutSelection: true } ), modelTable( [
- [ 'Test ' ]
+ [ 'Test ' ]
] ) );
} );
} );
diff --git a/packages/ckeditor5-table/tests/tableproperties/tablepropertiesediting.js b/packages/ckeditor5-table/tests/tableproperties/tablepropertiesediting.js
index f17b67ccd45..09ca00b858d 100644
--- a/packages/ckeditor5-table/tests/tableproperties/tablepropertiesediting.js
+++ b/packages/ckeditor5-table/tests/tableproperties/tablepropertiesediting.js
@@ -17,7 +17,7 @@ import TableWidthCommand from '../../src/tableproperties/commands/tablewidthcomm
import TableHeightCommand from '../../src/tableproperties/commands/tableheightcommand';
import TableBackgroundColorCommand from '../../src/tableproperties/commands/tablebackgroundcolorcommand';
-import { setData as setModelData } from '@ckeditor/ckeditor5-engine/src/dev-utils/model';
+import { setData as setModelData, getData as getModelData } from '@ckeditor/ckeditor5-engine/src/dev-utils/model';
import { assertEqualMarkup } from '@ckeditor/ckeditor5-utils/tests/_utils/utils';
import { assertTableStyle, assertTRBLAttribute } from '../_utils/utils';
@@ -211,7 +211,7 @@ describe( 'table properties', () => {
assertTRBLAttribute( table, 'borderWidth', null, null, null, '1px' );
} );
- // https://github.com/ckeditor/ckeditor5/issues/6177
+ // https://github.com/ckeditor/ckeditor5/issues/6177.
it( 'should upcast tables with nested tables in their cells', () => {
editor.setData( '' +
'' +
@@ -228,6 +228,38 @@ describe( 'table properties', () => {
assertTRBLAttribute( table, 'borderStyle', 'solid' );
assertTRBLAttribute( table, 'borderWidth', '1px' );
} );
+
+ // https://github.com/ckeditor/ckeditor5/issues/8393.
+ it( 'should not upcast contents of nested table', () => {
+ editor.setData( '' +
+ '' +
+ 'parent:00 ' +
+ '' +
+ '' +
+ ' ' +
+ ' ' +
+ '
' );
+
+ expect( getModelData( editor.model ) ).to.equal(
+ '[' +
+ '' +
+ '' +
+ '' +
+ 'parent:00' +
+ ' ' +
+ ' ' +
+ '' +
+ '' +
+ 'child:00' +
+ ' ' +
+ ' ' +
+ ' ' +
+ '
]'
+ );
+ } );
} );
describe( 'downcast conversion', () => {
From 9e00d23a6937b66f841e77b725a13dee252891c9 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Pawe=C5=82=20Kwa=C5=9Bnik?=
Date: Thu, 7 Jan 2021 11:25:00 +0100
Subject: [PATCH 29/40] Revert "Revert "Merge master to docs-spellchecker.""
This reverts commit 0f82b01a2d2c9d0a419b6b0930c47827711b14ed.
---
.github/ISSUE_TEMPLATE/1-bug-report.md | 2 +-
CHANGELOG.md | 2 +-
docs/_snippets/examples/multi-root-editor.js | 11 +-
docs/assets/tour-balloon.css | 36 ++++
docs/assets/tour-balloon.js | 109 ++++++++++
.../guides/integration/configuration.md | 2 +-
docs/features/toolbar.md | 2 +
.../framework/guides/custom-editor-creator.md | 11 +-
package.json | 2 +
.../ckeditor5-autoformat/src/autoformat.js | 13 +-
.../src/blockautoformatediting.js | 7 +
.../ckeditor5-autoformat/tests/autoformat.js | 187 ++++++++++++++++--
.../tests/manual/autoformat.js | 37 +++-
.../src/blockquoteediting.js | 53 +++--
.../tests/integration.js | 47 ++++-
.../ckeditor5-build-classic/tests/ckeditor.js | 15 ++
.../src/classiceditorui.js | 2 +-
.../tests/classiceditorui.js | 18 ++
.../src/decouplededitorui.js | 11 +-
.../tests/decouplededitorui.js | 18 ++
.../src/inlineeditorui.js | 2 +-
.../tests/inlineeditorui.js | 18 ++
.../guides/deep-dive/element-reconversion.md | 10 +-
packages/ckeditor5-engine/src/model/differ.js | 19 +-
.../docs/_snippets/features/html-embed.js | 5 +
.../src/htmlembedediting.js | 2 +
.../tests/htmlembedediting.js | 21 ++
...ge-insert-via-pasting-url-into-editor.html | 2 +-
.../src/imagecaption/imagecaptionediting.js | 2 +-
.../tests/imagecaption/imagecaptionediting.js | 14 ++
packages/ckeditor5-list/src/listediting.js | 5 +-
packages/ckeditor5-list/tests/listediting.js | 15 ++
.../examples/chat-with-mentions.html | 4 +
.../_snippets/examples/chat-with-mentions.js | 27 ++-
.../docs/examples/chat-with-mentions.md | 29 ++-
.../ckeditor5-mention/src/mentionediting.js | 2 +-
.../ckeditor5-mention/tests/mentionediting.js | 14 ++
.../ckeditor5-typing/src/deletecommand.js | 61 +++++-
.../ckeditor5-typing/tests/deletecommand.js | 77 +++++++-
.../src/toolbar/balloon/balloontoolbar.js | 2 +-
.../src/toolbar/block/blocktoolbar.js | 2 +-
.../src/toolbar/normalizetoolbarconfig.js | 10 +-
.../ckeditor5-ui/src/toolbar/toolbarview.js | 153 ++++++++++----
.../tests/toolbar/normalizetoolbarconfig.js | 17 +-
.../ckeditor5-ui/tests/toolbar/toolbarview.js | 127 +++++++++++-
scripts/docs/snippetadapter.js | 10 +
46 files changed, 1083 insertions(+), 152 deletions(-)
create mode 100644 docs/assets/tour-balloon.css
create mode 100644 docs/assets/tour-balloon.js
diff --git a/.github/ISSUE_TEMPLATE/1-bug-report.md b/.github/ISSUE_TEMPLATE/1-bug-report.md
index 4ab08c757aa..63a8f205062 100644
--- a/.github/ISSUE_TEMPLATE/1-bug-report.md
+++ b/.github/ISSUE_TEMPLATE/1-bug-report.md
@@ -25,7 +25,7 @@ _What is the actual result of the above steps?_
* Browser: …
* OS: …
-* CKEditor version: …
+* First affected CKEditor version: …
* Installed CKEditor plugins: …
---
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 41880807eb2..4fffa7e826b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -41,7 +41,7 @@ The CKEditor 5 Collaboration features changelog can be found here: https://ckedi
### Features
-* **[build-decoupled-document](https://www.npmjs.com/package/@ckeditor/ckeditor5-build-decoupled-document)**: Added new features to the build configuration: [horizontal line](https://ckeditor.com/docs/ckeditor5/latest/features/horizontal-line.html), [page break](https://ckeditor.com/docs/ckeditor5/latest/features/page-break.html), [remove formatting](https://ckeditor.com/docs/ckeditor5/latest/features/remove-format.html), and [special characters](https://ckeditor.com/docs/ckeditor5/latest/features/special-characters.html)) (see [#6146](https://github.com/ckeditor/ckeditor5/issues/6146). ([commit](https://github.com/ckeditor/ckeditor5/commit/70157aec7c0ec62b63a51f6bb20764afad443637))
+* **[build-decoupled-document](https://www.npmjs.com/package/@ckeditor/ckeditor5-build-decoupled-document)**: Added new features to the build configuration: [horizontal line](https://ckeditor.com/docs/ckeditor5/latest/features/horizontal-line.html), [page break](https://ckeditor.com/docs/ckeditor5/latest/features/page-break.html), [remove formatting](https://ckeditor.com/docs/ckeditor5/latest/features/remove-format.html), and [special characters](https://ckeditor.com/docs/ckeditor5/latest/features/special-characters.html)). See [#6146](https://github.com/ckeditor/ckeditor5/issues/6146). ([commit](https://github.com/ckeditor/ckeditor5/commit/70157aec7c0ec62b63a51f6bb20764afad443637))
* **[core](https://www.npmjs.com/package/@ckeditor/ckeditor5-core)**: Introduced the `focus()` method in the base `Editor` class. Closes [#714](https://github.com/ckeditor/ckeditor5/issues/714). ([commit](https://github.com/ckeditor/ckeditor5/commit/dea805153299404a130dcc12aa855cba922a2e86))
* **[engine](https://www.npmjs.com/package/@ckeditor/ckeditor5-engine)**: Introduced the `DataProcessor#registerRawContentMatcher()` API that marks content sections which contain arbitrary character data and should not be parsed during the conversion. See [#8323](https://github.com/ckeditor/ckeditor5/issues/8323). ([commit](https://github.com/ckeditor/ckeditor5/commit/b8538dea19326a04ed0ff4d8b0ab346f6be8fc08))
* **[image](https://www.npmjs.com/package/@ckeditor/ckeditor5-image)**: Support for inserting images by pasting an image URL directly into the editor. Closes [#8236](https://github.com/ckeditor/ckeditor5/issues/8236). ([commit](https://github.com/ckeditor/ckeditor5/commit/908a35ac381c852c466f6144ac25f21f0d5af877))
diff --git a/docs/_snippets/examples/multi-root-editor.js b/docs/_snippets/examples/multi-root-editor.js
index b23a7c8f23d..fd980001494 100644
--- a/docs/_snippets/examples/multi-root-editor.js
+++ b/docs/_snippets/examples/multi-root-editor.js
@@ -14,7 +14,6 @@ import setDataInElement from '@ckeditor/ckeditor5-utils/src/dom/setdatainelement
import mix from '@ckeditor/ckeditor5-utils/src/mix';
import EditorUI from '@ckeditor/ckeditor5-core/src/editor/editorui';
import enableToolbarKeyboardFocus from '@ckeditor/ckeditor5-ui/src/toolbar/enabletoolbarkeyboardfocus';
-import normalizeToolbarConfig from '@ckeditor/ckeditor5-ui/src/toolbar/normalizetoolbarconfig';
import { enablePlaceholder } from '@ckeditor/ckeditor5-engine/src/view/placeholder';
import EditorUIView from '@ckeditor/ckeditor5-ui/src/editorui/editoruiview';
import InlineEditableUIView from '@ckeditor/ckeditor5-ui/src/editableui/inline/inlineeditableuiview';
@@ -159,14 +158,6 @@ class MultirootEditorUI extends EditorUI {
* @member {module:ui/editorui/editoruiview~EditorUIView} #view
*/
this.view = view;
-
- /**
- * A normalized `config.toolbar` object.
- *
- * @type {Object}
- * @private
- */
- this._toolbarConfig = normalizeToolbarConfig( editor.config.get( 'toolbar' ) );
}
/**
@@ -285,7 +276,7 @@ class MultirootEditorUI extends EditorUI {
const view = this.view;
const toolbar = view.toolbar;
- toolbar.fillFromConfig( this._toolbarConfig.items, this.componentFactory );
+ toolbar.fillFromConfig( editor.config.get( 'toolbar' ), this.componentFactory );
enableToolbarKeyboardFocus( {
origin: editor.editing.view,
diff --git a/docs/assets/tour-balloon.css b/docs/assets/tour-balloon.css
new file mode 100644
index 00000000000..91e3c271acb
--- /dev/null
+++ b/docs/assets/tour-balloon.css
@@ -0,0 +1,36 @@
+/*
+ * Copyright (c) 2003-2020, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
+ */
+
+.tippy-content {
+ padding: 8px 6px;
+}
+
+.tippy-content .tippy-content__message {
+ display: flex;
+ align-items: center;
+ min-height: 36px;
+ margin-right: 28px;
+ padding-left: 44px;
+ line-height: 1.5;
+
+ background-image: url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz48c3ZnIHdpZHRoPSIzOHB4IiBoZWlnaHQ9IjM3cHgiIHZpZXdCb3g9IjAgMCAzOCAzNyIgdmVyc2lvbj0iMS4xIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIj4gICAgICAgIDx0aXRsZT5oaW50PC90aXRsZT4gICAgPGRlc2M+Q3JlYXRlZCB3aXRoIFNrZXRjaC48L2Rlc2M+ICAgIDxnIGlkPSJQYWdlLTEiIHN0cm9rZT0ibm9uZSIgc3Ryb2tlLXdpZHRoPSIxIiBmaWxsPSJub25lIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiPiAgICAgICAgPGcgaWQ9IkFydGJvYXJkIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgtOTg5LjAwMDAwMCwgLTQzNS4wMDAwMDApIj4gICAgICAgICAgICA8ZyBpZD0icG9wdXAtaW52ZXJ0ZWQiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDk3My41MDAwMDAsIDQwOC4wMDAwMDApIj4gICAgICAgICAgICAgICAgPGcgaWQ9ImhpbnQiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDE2LjAwMDAwMCwgMjcuMDAwMDAwKSI+ICAgICAgICAgICAgICAgICAgICA8Y2lyY2xlIGlkPSJPdmFsIiBmaWxsLW9wYWNpdHk9IjAuMTE5NTkxMzQ2IiBmaWxsPSIjRkZGRkZGIiBjeD0iMTguOTMwMjMyNiIgY3k9IjE4LjUiIHI9IjE4LjUiPjwvY2lyY2xlPiAgICAgICAgICAgICAgICAgICAgPHBhdGggZD0iTTE4Ljg5MTEyMDUsMzAuMjkyMzI1NyBDMTkuNDg4NTg4MSwzMC4yOTIzMjU3IDE5Ljk3MjkzMTIsMjkuODA3OTgyNiAxOS45NzI5MzEyLDI5LjIxMDUxNSBDMTkuOTcyOTMxMiwyOC42MTMwNDc1IDE5LjQ4ODU4ODEsMjkuMDE5MDMyNCAxOC44OTExMjA1LDI5LjAxOTAzMjQgQzE4LjI5MzY1MjksMjkuMDE5MDMyNCAxNy44MDkzMDk4LDI4LjYxMzA0NzUgMTcuODA5MzA5OCwyOS4yMTA1MTUgQzE3LjgwOTMwOTgsMjkuODA3OTgyNiAxOC4yOTM2NTI5LDMwLjI5MjMyNTcgMTguODkxMTIwNSwzMC4yOTIzMjU3IFoiIGlkPSJPdmFsIiBmaWxsPSIjMDAwMDAwIj48L3BhdGg+ICAgICAgICAgICAgICAgICAgICA8cGF0aCBkPSJNMTUuNjA1NzA4MiwyNS4xNDkwNDg2IEwyMi4xNzY1MzI4LDI1LjE0OTA0ODYgTDIyLjA3MTIzNDEsMjcuMzUxMzMxNyBDMjIuMDIwMjU0NiwyOC40MTc1NDg5IDIxLjE0MDk1MTYsMjkuMjU1ODE0IDIwLjA3MzUxNjMsMjkuMjU1ODE0IEwxNy43MDg3MjQ3LDI5LjI1NTgxNCBDMTYuNjQxMjg5NCwyOS4yNTU4MTQgMTUuNzYxOTg2NCwyOC40MTc1NDg5IDE1LjcxMTAwNjksMjcuMzUxMzMxNyBMMTUuNjA1NzA4MiwyNS4xNDkwNDg2IEwxNS42MDU3MDgyLDI1LjE0OTA0ODYgWiIgaWQ9IkNvbWJpbmVkLVNoYXBlIiBmaWxsPSIjQUJBQkFCIj48L3BhdGg+ICAgICAgICAgICAgICAgICAgICA8cGF0aCBkPSJNMTguODkxMTIwNSw5Ljg5NTM0ODg0IEMyMi41MjAwODY3LDkuODk1MzQ4ODQgMjUuNDYxOTQ1LDEyLjc4MDYzMyAyNS40NjE5NDUsMTYuMzM5ODExNCBDMjUuNDYxOTQ1LDE3LjQ5NTAyMTkgMjUuMTUyMDI4NiwxOC41NzkyMzk2IDI0LjYwOTI5MiwxOS41MTY4NTA5IEMyNC41OTEzODY5LDE5LjU5MjczMDIgMjQuNTQ5NzEyNSwxOS42NzI1MzE2IDI0LjQ4MjgxNjUsMTkuNzU1NjE1MiBMMjQuMzYyNTAzNywxOS45MDk3MTk2IEMyNC4yMzYwOTQyLDIwLjA5NTcwODYgMjQuMTAwMDg4MiwyMC4yNzQ4ODY4IDIzLjk1NTE3OTYsMjAuNDQ2NTczNyBDMjMuMTEwMjA1LDIxLjYwNzczNDggMjIuNTE3Nzk5NCwyMi43NDUwNTg1IDIyLjE3NjUzMjgsMjMuODU4MzUxIEwyMi4xNzY1MzI4LDI1LjI5MDM0MjggTDE1LjYwNTcwODIsMjUuMjkwMzQyOCBMMTUuNjA1NzA4MiwyMy44NTgzNTEgQzE1LjI0NTI3MzIsMjIuNzYyNDIyNCAxNC42NjgyMDA5LDIxLjY0MzIwNjQgMTMuODc0NDkxMywyMC41MDA3MDI4IEMxMi45MDQzMjA5LDE5LjM3NzY3MzEgMTIuMzIwMjk2LDE3LjkyNTYzMzggMTIuMzIwMjk2LDE2LjMzOTgxMTQgQzEyLjMyMDI5NiwxMi43ODA2MzMgMTUuMjYyMTU0Myw5Ljg5NTM0ODg0IDE4Ljg5MTEyMDUsOS44OTUzNDg4NCBaIE0xOC40ODA0NDQsMTEuMTI3Mzc4NCBDMTUuNzU4NzE5MywxMS4xMjczNzg0IDEzLjU1MjMyNTYsMTMuMzMzNzcyMiAxMy41NTIzMjU2LDE2LjA1NTQ5NjggQzEzLjU1MjMyNTYsMTYuMjQyMjEzNyAxMy41NjI3MDk1LDE2LjQyNjUwNTIgMTMuNTgyOTMxNCwxNi42MDc4MjU1IEwxMy42MjA1NzQ0LDE2Ljg3NzUwOTIgTDE0LjQ1NTk4NTQsMTYuODc3NjIwOCBDMTQuNDAyMDEyOCwxNi42MTE5OTQ1IDE0LjM3MzY3ODYsMTYuMzM3MDU0NSAxNC4zNzM2Nzg2LDE2LjA1NTQ5NjggQzE0LjM3MzY3ODYsMTMuODUyMTk1OSAxNi4xMDg3NzQ3LDEyLjA1NDE0ODQgMTguMjg3MTE5NiwxMS45NTMyMDE1IEwxOC40ODA0NDQsMTEuOTQ4NzMxNSBMMTguNDgwNDQ0LDExLjEyNzM3ODQgWiIgaWQ9IlNoYXBlIiBmaWxsPSIjRjhDMjcyIj48L3BhdGg+ICAgICAgICAgICAgICAgICAgICA8ZyBpZD0iR3JvdXAtNSIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoNy43NDQxODYsIDE1LjUyNzQ4NCkiIHN0cm9rZT0iI0Y4QzI3MiIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIj4gICAgICAgICAgICAgICAgICAgICAgICA8bGluZSB4MT0iMS42MDM1OTQwOCIgeTE9IjEuNjAzNTk0MDgiIHgyPSIxLjYwMzU5NDA4IiB5Mj0iLTAuNTQ3NTY4NzEiIGlkPSJQYXRoLTItQ29weSIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMS42MDM1OTQsIDAuNTI4MDEzKSByb3RhdGUoOTAuMDAwMDAwKSB0cmFuc2xhdGUoLTEuNjAzNTk0LCAtMC41MjgwMTMpICI+PC9saW5lPiAgICAgICAgICAgICAgICAgICAgICAgIDxsaW5lIHgxPSIyMC4yNjAwNDIzIiB5MT0iMS42MDM1OTQwOCIgeDI9IjIwLjI2MDA0MjMiIHkyPSItMC41NDc1Njg3MSIgaWQ9IlBhdGgtMi1Db3B5LTIiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDIwLjI2MDA0MiwgMC41MjgwMTMpIHJvdGF0ZSg5MC4wMDAwMDApIHRyYW5zbGF0ZSgtMjAuMjYwMDQyLCAtMC41MjgwMTMpICI+PC9saW5lPiAgICAgICAgICAgICAgICAgICAgPC9nPiAgICAgICAgICAgICAgICAgICAgPGcgaWQ9Ikdyb3VwLTUtQ29weSIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMTguNTk5MTg0LCAxNi40NDUyMTMpIHJvdGF0ZSgtMzAuMDAwMDAwKSB0cmFuc2xhdGUoLTE4LjU5OTE4NCwgLTE2LjQ0NTIxMykgdHJhbnNsYXRlKDcuODQzMzcwLCAxNS41ODQ3NDgpIiBzdHJva2U9IiNGOEMyNzIiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCI+ICAgICAgICAgICAgICAgICAgICAgICAgPGxpbmUgeDE9IjEuNjAzNTk0MDgiIHkxPSIxLjYwMzU5NDA4IiB4Mj0iMS42MDM1OTQwOCIgeTI9Ii0wLjU0NzU2ODcxIiBpZD0iUGF0aC0yLUNvcHkiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDEuNjAzNTk0LCAwLjUyODAxMykgcm90YXRlKDkwLjAwMDAwMCkgdHJhbnNsYXRlKC0xLjYwMzU5NCwgLTAuNTI4MDEzKSAiPjwvbGluZT4gICAgICAgICAgICAgICAgICAgICAgICA8bGluZSB4MT0iMjAuMjYwMDQyMyIgeTE9IjEuNjAzNTk0MDgiIHgyPSIyMC4yNjAwNDIzIiB5Mj0iLTAuNTQ3NTY4NzEiIGlkPSJQYXRoLTItQ29weS0yIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgyMC4yNjAwNDIsIDAuNTI4MDEzKSByb3RhdGUoOTAuMDAwMDAwKSB0cmFuc2xhdGUoLTIwLjI2MDA0MiwgLTAuNTI4MDEzKSAiPjwvbGluZT4gICAgICAgICAgICAgICAgICAgIDwvZz4gICAgICAgICAgICAgICAgICAgIDxnIGlkPSJHcm91cC01LUNvcHktMyIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMjMuNDg1MzgyLCA4LjE4MDQzOCkgcm90YXRlKC02MC4wMDAwMDApIHRyYW5zbGF0ZSgtMjMuNDg1MzgyLCAtOC4xODA0MzgpIHRyYW5zbGF0ZSgyMi4xOTQ2ODUsIDcuMzE5OTczKSIgc3Ryb2tlPSIjRjhDMjcyIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiPiAgICAgICAgICAgICAgICAgICAgICAgIDxsaW5lIHgxPSIxLjI1MTU4NTYyIiB5MT0iMS42MDM1OTQwOCIgeDI9IjEuMjUxNTg1NjIiIHkyPSItMC41NDc1Njg3MSIgaWQ9IlBhdGgtMi1Db3B5LTIiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDEuMjUxNTg2LCAwLjUyODAxMykgcm90YXRlKDkwLjAwMDAwMCkgdHJhbnNsYXRlKC0xLjI1MTU4NiwgLTAuNTI4MDEzKSAiPjwvbGluZT4gICAgICAgICAgICAgICAgICAgIDwvZz4gICAgICAgICAgICAgICAgICAgIDxnIGlkPSJHcm91cC01LUNvcHktNCIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMTQuMDk4NDkwLCA4LjAyMzk5MCkgcm90YXRlKC0xMjAuMDAwMDAwKSB0cmFuc2xhdGUoLTE0LjA5ODQ5MCwgLTguMDIzOTkwKSB0cmFuc2xhdGUoMTIuODA3NzkzLCA3LjE2MzUyNSkiIHN0cm9rZT0iI0Y4QzI3MiIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIj4gICAgICAgICAgICAgICAgICAgICAgICA8bGluZSB4MT0iMS4yNTE1ODU2MiIgeTE9IjEuNjAzNTk0MDgiIHgyPSIxLjI1MTU4NTYyIiB5Mj0iLTAuNTQ3NTY4NzEiIGlkPSJQYXRoLTItQ29weS0yIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgxLjI1MTU4NiwgMC41MjgwMTMpIHJvdGF0ZSg5MC4wMDAwMDApIHRyYW5zbGF0ZSgtMS4yNTE1ODYsIC0wLjUyODAxMykgIj48L2xpbmU+ICAgICAgICAgICAgICAgICAgICA8L2c+ICAgICAgICAgICAgICAgICAgICA8ZyBpZD0iR3JvdXAtNS1Db3B5LTIiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDE4Ljg3MDE2MCwgMTYuMTc0MjM3KSByb3RhdGUoLTE1MC4wMDAwMDApIHRyYW5zbGF0ZSgtMTguODcwMTYwLCAtMTYuMTc0MjM3KSB0cmFuc2xhdGUoOC4xMTQzNDYsIDE1LjMxMzc3MikiIHN0cm9rZT0iI0Y4QzI3MiIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIj4gICAgICAgICAgICAgICAgICAgICAgICA8bGluZSB4MT0iMS42MDM1OTQwOCIgeTE9IjEuNjAzNTk0MDgiIHgyPSIxLjYwMzU5NDA4IiB5Mj0iLTAuNTQ3NTY4NzEiIGlkPSJQYXRoLTItQ29weSIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMS42MDM1OTQsIDAuNTI4MDEzKSByb3RhdGUoOTAuMDAwMDAwKSB0cmFuc2xhdGUoLTEuNjAzNTk0LCAtMC41MjgwMTMpICI+PC9saW5lPiAgICAgICAgICAgICAgICAgICAgICAgIDxsaW5lIHgxPSIyMC4yNjAwNDIzIiB5MT0iMS42MDM1OTQwOCIgeDI9IjIwLjI2MDA0MjMiIHkyPSItMC41NDc1Njg3MSIgaWQ9IlBhdGgtMi1Db3B5LTIiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDIwLjI2MDA0MiwgMC41MjgwMTMpIHJvdGF0ZSg5MC4wMDAwMDApIHRyYW5zbGF0ZSgtMjAuMjYwMDQyLCAtMC41MjgwMTMpICI+PC9saW5lPiAgICAgICAgICAgICAgICAgICAgPC9nPiAgICAgICAgICAgICAgICAgICAgPGxpbmUgeDE9IjE5LjAwODQ1NjciIHkxPSI4LjA1NzA4MjQ1IiB4Mj0iMTkuMDA4NDU2NyIgeTI9IjUuOTA1OTE5NjYiIGlkPSJQYXRoLTIiIHN0cm9rZT0iI0Y4QzI3MiIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIj48L2xpbmU+ICAgICAgICAgICAgICAgIDwvZz4gICAgICAgICAgICA8L2c+ICAgICAgICA8L2c+ICAgIDwvZz48L3N2Zz4=);
+ background-position: top left;
+ background-repeat: no-repeat;
+}
+
+.tippy-content .tippy-content__close-button {
+ color: var(--ck-color-text);
+ position: absolute;
+ top: 0;
+ right: 0;
+}
+
+.tippy-content .tippy-content__close-button::after {
+ content: '✖';
+}
+
+.tippy-content .ck.ck-button.tippy-content__close-button:hover {
+ background: none;
+}
diff --git a/docs/assets/tour-balloon.js b/docs/assets/tour-balloon.js
new file mode 100644
index 00000000000..c7b4e9f6f48
--- /dev/null
+++ b/docs/assets/tour-balloon.js
@@ -0,0 +1,109 @@
+/**
+ * @license Copyright (c) 2003-2020, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
+ */
+
+/* global console, window, document */
+
+/**
+ * Attaches a tour balloon with a description to any DOM node element.
+ *
+ * **Tip**: Use the global `findToolbarItem()` method to easily pick toolbar items.
+ *
+ * Examples:
+ *
+ * // Using a comparison callback to search for an item.
+ * window.attachTourBalloon( {
+ * target: window.findToolbarItem( editor.ui.view.toolbar, item => item.label && item.label === 'Insert HTML' ),
+ * text: 'Tour text to help users discover the feature.'
+ * } );
+ *
+ * // Using a toolbar item index.
+ * window.attachTourBalloon( {
+ * target: window.findToolbarItem( editor.ui.view.toolbar, 5 ),
+ * text: 'Tour text to help users discover the feature.'
+ * } );
+ *
+ * // Specifying options of tippy.js, e.g. to customize the placement of the balloon.
+ * // See https://atomiks.github.io/tippyjs/v6/all-props/ for all options.
+ * window.attachTourBalloon( {
+ * target: window.findToolbarItem( editor.ui.view.toolbar, 5 ),
+ * text: 'Tour text to help users discover the feature.',
+ * tippyOptions: {
+ * placement: 'bottom-start'
+ * }
+ * } );
+ *
+ * @param {Object} options Balloon options.
+ * @param {HTMLElement} options.target A DOM node the balloon will point to.
+ * @param {String} options.text The description to be shown in the tooltip.
+ * @param {Object} [options.tippyOptions] Additional [configuration of tippy.js](https://atomiks.github.io/tippyjs/v6/all-props/).
+ */
+window.attachTourBalloon = function( { target, text, tippyOptions } ) {
+ if ( !target ) {
+ console.warn( '[attachTourBalloon] The target DOM node for the feature tour balloon does not exist.', { text } );
+
+ return;
+ }
+
+ const content = `
+ ${ text }
+
+ `;
+
+ // eslint-disable-next-line no-undef
+ const tooltip = tippy( target, {
+ content,
+ theme: 'light-border',
+ placement: 'bottom',
+ trigger: 'manual',
+ hideOnClick: false,
+ allowHTML: true,
+ maxWidth: 280,
+ showOnCreate: true,
+ interactive: true,
+ touch: 'hold',
+ zIndex: 1,
+ appendTo: () => document.body,
+ ...tippyOptions
+ } );
+
+ // eslint-disable-next-line no-undef
+ const closeButton = tooltip.popper.querySelector( '.tippy-content__close-button' );
+
+ closeButton.addEventListener( 'click', () => {
+ tooltip.hide();
+ } );
+
+ target.addEventListener( 'click', () => {
+ tooltip.hide();
+ } );
+};
+
+/**
+ * Searches for a toolbar item and returns the first one matching the criteria.
+ *
+ * You can search for toolbar items using a comparison callback:
+ *
+ * window.findToolbarItem( editor.ui.view.toolbar, item => item.label && item.label.startsWith( 'Insert HTML' ) );
+ *
+ * Or you pick toolbar items by their index:
+ *
+ * window.findToolbarItem( editor.ui.view.toolbar, 3 );
+ *
+ * @param {module:ui/toolbar/toolbarview~ToolbarView} toolbarView Toolbar instance.
+ * @param {Number|Function} indexOrCallback Index of a toolbar item or a callback passed to `ViewCollection#find`.
+ * @returns {HTMLElement|undefined} HTML element or undefined
+ */
+window.findToolbarItem = function( toolbarView, indexOrCallback ) {
+ const items = toolbarView.items;
+ let item;
+
+ if ( typeof indexOrCallback === 'function' ) {
+ item = items.find( indexOrCallback );
+ } else {
+ item = items.get( indexOrCallback );
+ }
+
+ return item ? item.element : undefined;
+};
diff --git a/docs/builds/guides/integration/configuration.md b/docs/builds/guides/integration/configuration.md
index e15a5d9a98d..d8a9c522479 100644
--- a/docs/builds/guides/integration/configuration.md
+++ b/docs/builds/guides/integration/configuration.md
@@ -51,7 +51,7 @@ ClassicEditor
} );
```
- Be careful when removing plugins from CKEditor builds using `config.removePlugins`. If removed plugins were providing toolbar buttons, the default toolbar configuration included in a build will become invalid. In such case you need to provide the {@link features/toolbar updated toolbar configuration} as in the example above.
+ Be careful when removing plugins from CKEditor builds using `config.removePlugins`. If removed plugins were providing toolbar buttons, the default toolbar configuration included in a build will become invalid. In such case you need to provide the {@link features/toolbar updated toolbar configuration} as in the example above or by providing only items that need to be removed using `config.toolbar.removeItems`.
### List of plugins
diff --git a/docs/features/toolbar.md b/docs/features/toolbar.md
index 6f94f96aacc..fde974ef7d1 100644
--- a/docs/features/toolbar.md
+++ b/docs/features/toolbar.md
@@ -71,6 +71,8 @@ toolbar: {
* **`items`** – An array of toolbar item names. Most of the components (buttons, dropdowns, etc.) which can be used as toolbar items are described under the {@link features/index Features} tab. A full list is defined in {@link module:ui/componentfactory~ComponentFactory editor.ui.componentFactory} and can be listed using the following snippet: `Array.from( editor.ui.componentFactory.names() )`. Besides button names, you can also use the dedicated separators for toolbar groups (`'|'`) and toolbar lines (`'-'`).
+ * **`removeItems`** – An array of toolbar item names. With this setting you can modify the default toolbar configuration without the need of defining the entire list (you can specify a couple of buttons that you want to remove instead of specifying all the buttons you want to keep). If, after removing an item, toolbar will have two or more consecutive separators (`'|'`), the duplicates will be removed automatically.
+
* **`viewportTopOffset`** – The offset (in pixels) from the top of the viewport used when positioning a sticky toolbar. Useful when a page with which the editor is being integrated has some other sticky or fixed elements (e.g. the top menu). Thanks to setting the toolbar offset, the toolbar will not be positioned underneath or above the page's UI.
* **`shouldNotGroupWhenFull`** – When set to `true`, the toolbar will stop grouping items and let them wrap to the next line when there is not enough space to display them in a single row. This setting is `false` by default, which enables items grouping.
diff --git a/docs/framework/guides/custom-editor-creator.md b/docs/framework/guides/custom-editor-creator.md
index 71892d5ae64..64e34838cd8 100644
--- a/docs/framework/guides/custom-editor-creator.md
+++ b/docs/framework/guides/custom-editor-creator.md
@@ -126,7 +126,6 @@ The `*EditorUI` class is the main UI class which initializes UI components (the
```js
import EditorUI from '@ckeditor/ckeditor5-core/src/editor/editorui';
import enableToolbarKeyboardFocus from '@ckeditor/ckeditor5-ui/src/toolbar/enabletoolbarkeyboardfocus';
-import normalizeToolbarConfig from '@ckeditor/ckeditor5-ui/src/toolbar/normalizetoolbarconfig';
import { enablePlaceholder } from '@ckeditor/ckeditor5-engine/src/view/placeholder';
/**
@@ -151,14 +150,6 @@ class MultirootEditorUI extends EditorUI {
* @member {module:ui/editorui/editoruiview~EditorUIView} #view
*/
this.view = view;
-
- /**
- * A normalized `config.toolbar` object.
- *
- * @type {Object}
- * @private
- */
- this._toolbarConfig = normalizeToolbarConfig( editor.config.get( 'toolbar' ) );
}
/**
@@ -276,7 +267,7 @@ class MultirootEditorUI extends EditorUI {
const view = this.view;
const toolbar = view.toolbar;
- toolbar.fillFromConfig( this._toolbarConfig.items, this.componentFactory );
+ toolbar.fillFromConfig( editor.config.get( 'toolbar' ), this.componentFactory );
enableToolbarKeyboardFocus( {
origin: editor.editing.view,
diff --git a/package.json b/package.json
index e970fd715fb..f8eba51595f 100644
--- a/package.json
+++ b/package.json
@@ -106,6 +106,7 @@
"minimatch": "^3.0.4",
"mkdirp": "^1.0.4",
"nyc": "^15.0.1",
+ "popper": "^1.0.1",
"postcss-loader": "^3.0.0",
"progress-bar-webpack-plugin": "^2.1.0",
"raw-loader": "^4.0.1",
@@ -116,6 +117,7 @@
"stylelint-config-ckeditor5": "^2.0.0",
"svgo": "^1.3.2",
"terser-webpack-plugin": "^3.0.2",
+ "tippy.js": "^6.2.7",
"umberto": "^1.6.2",
"upath": "^2.0.0",
"webpack": "^4.43.0"
diff --git a/packages/ckeditor5-autoformat/src/autoformat.js b/packages/ckeditor5-autoformat/src/autoformat.js
index 17b779ef898..dc90ffc88fd 100644
--- a/packages/ckeditor5-autoformat/src/autoformat.js
+++ b/packages/ckeditor5-autoformat/src/autoformat.js
@@ -44,6 +44,7 @@ export default class Autoformat extends Plugin {
* When typed:
* - `* ` or `- ` – A paragraph will be changed to a bulleted list.
* - `1. ` or `1) ` – A paragraph will be changed to a numbered list ("1" can be any digit or a list of digits).
+ * - `[] ` or `[ ] ` – A paragraph will be changed to a to-do list.
*
* @private
*/
@@ -57,6 +58,10 @@ export default class Autoformat extends Plugin {
if ( commands.get( 'numberedList' ) ) {
blockAutoformatEditing( this.editor, this, /^1[.|)]\s$/, 'numberedList' );
}
+
+ if ( commands.get( 'todoList' ) ) {
+ blockAutoformatEditing( this.editor, this, /^\[\s?\]\s$/, 'todoList' );
+ }
}
/**
@@ -80,8 +85,8 @@ export default class Autoformat extends Plugin {
if ( commands.get( 'bold' ) ) {
const boldCallback = getCallbackFunctionForInlineAutoformat( this.editor, 'bold' );
- inlineAutoformatEditing( this.editor, this, /(\*\*)([^*]+)(\*\*)$/g, boldCallback );
- inlineAutoformatEditing( this.editor, this, /(__)([^_]+)(__)$/g, boldCallback );
+ inlineAutoformatEditing( this.editor, this, /(?:^|\s)(\*\*)([^*]+)(\*\*)$/g, boldCallback );
+ inlineAutoformatEditing( this.editor, this, /(?:^|\s)(__)([^_]+)(__)$/g, boldCallback );
}
if ( commands.get( 'italic' ) ) {
@@ -89,8 +94,8 @@ export default class Autoformat extends Plugin {
// The italic autoformatter cannot be triggered by the bold markers, so we need to check the
// text before the pattern (e.g. `(?:^|[^\*])`).
- inlineAutoformatEditing( this.editor, this, /(?:^|[^*])(\*)([^*_]+)(\*)$/g, italicCallback );
- inlineAutoformatEditing( this.editor, this, /(?:^|[^_])(_)([^_]+)(_)$/g, italicCallback );
+ inlineAutoformatEditing( this.editor, this, /(?:^|\s)(\*)([^*_]+)(\*)$/g, italicCallback );
+ inlineAutoformatEditing( this.editor, this, /(?:^|\s)(_)([^_]+)(_)$/g, italicCallback );
}
if ( commands.get( 'code' ) ) {
diff --git a/packages/ckeditor5-autoformat/src/blockautoformatediting.js b/packages/ckeditor5-autoformat/src/blockautoformatediting.js
index 9aee765557e..2957a9f25a4 100644
--- a/packages/ckeditor5-autoformat/src/blockautoformatediting.js
+++ b/packages/ckeditor5-autoformat/src/blockautoformatediting.js
@@ -96,6 +96,13 @@ export default function blockAutoformatEditing( editor, plugin, pattern, callbac
return;
}
+ // Only lists should be formatted inside the lists.
+ if ( blockToFormat.is( 'element', 'listItem' ) &&
+ ![ 'numberedList', 'bulletedList', 'todoList' ].includes( callbackOrCommand )
+ ) {
+ return;
+ }
+
// In case a command is bound, do not re-execute it over an existing block style which would result with a style removal.
// Instead just drop processing so that autoformat trigger text is not lost. E.g. writing "# " in a level 1 heading.
if ( command && command.value === true ) {
diff --git a/packages/ckeditor5-autoformat/tests/autoformat.js b/packages/ckeditor5-autoformat/tests/autoformat.js
index a80404d8c69..88d98bd9df6 100644
--- a/packages/ckeditor5-autoformat/tests/autoformat.js
+++ b/packages/ckeditor5-autoformat/tests/autoformat.js
@@ -7,6 +7,7 @@ import Autoformat from '../src/autoformat';
import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph';
import ListEditing from '@ckeditor/ckeditor5-list/src/listediting';
+import TodoListEditing from '@ckeditor/ckeditor5-list/src/todolistediting';
import HeadingEditing from '@ckeditor/ckeditor5-heading/src/headingediting';
import BoldEditing from '@ckeditor/ckeditor5-basic-styles/src/bold/boldediting';
import StrikethroughEditing from '@ckeditor/ckeditor5-basic-styles/src/strikethrough/strikethroughediting';
@@ -37,6 +38,7 @@ describe( 'Autoformat', () => {
Paragraph,
Autoformat,
ListEditing,
+ TodoListEditing,
HeadingEditing,
BoldEditing,
ItalicEditing,
@@ -103,6 +105,15 @@ describe( 'Autoformat', () => {
expect( getData( model ) ).to.equal( 'Foo * [] ' );
} );
+
+ it( 'should be converted from a to-do list', () => {
+ setData( model, '*[] ' );
+ model.change( writer => {
+ writer.insertText( ' ', doc.selection.getFirstPosition() );
+ } );
+
+ expect( getData( model ) ).to.equal( '[] ' );
+ } );
} );
describe( 'Numbered list', () => {
@@ -186,6 +197,92 @@ describe( 'Autoformat', () => {
expect( getData( model ) ).to.equal( '[] ' );
} );
+
+ it( 'should be converted from a to-do list', () => {
+ setData( model, '1.[] ' );
+ model.change( writer => {
+ writer.insertText( ' ', doc.selection.getFirstPosition() );
+ } );
+
+ expect( getData( model ) ).to.equal( '[] ' );
+ } );
+ } );
+
+ describe( 'To-do list', () => {
+ function insertSpace() {
+ model.change( writer => {
+ writer.insertText( ' ', doc.selection.getFirstPosition() );
+ } );
+ }
+ function insertBrackets( content = '' ) {
+ model.change( writer => {
+ writer.insertText( '[' + content + ']', doc.selection.getFirstPosition() );
+ } );
+ }
+
+ it( 'should replace empty square brackets', () => {
+ setData( model, '[]' );
+ insertBrackets();
+ insertSpace();
+
+ expect( getData( model ) ).to.equal( '[] ' );
+ } );
+
+ it( 'should replace square brackets with space inside', () => {
+ setData( model, '[]' );
+ insertBrackets( ' ' );
+ insertSpace();
+
+ expect( getData( model ) ).to.equal( '[] ' );
+ } );
+
+ it( 'should be converted from a paragraph', () => {
+ setData( model, '[]Sample text ' );
+ insertBrackets();
+ insertSpace();
+
+ expect( getData( model ) ).to.equal( '[]Sample text ' );
+ } );
+
+ it( 'should be converted from a header', () => {
+ setData( model, '[]Header text ' );
+ insertBrackets( ' ' );
+ insertSpace();
+
+ expect( getData( model ) ).to.equal( '[]Header text ' );
+ } );
+
+ it( 'should be converted from a numbered list', () => {
+ setData( model, '[]Sample text ' );
+ insertBrackets();
+ insertSpace();
+
+ expect( getData( model ) ).to.equal( '[]Sample text ' );
+ } );
+
+ it( 'should not replace the brackets if is not at the beginning of the line', () => {
+ setData( model, 'Sample text [] ' );
+ insertBrackets( ' ' );
+ insertSpace();
+
+ expect( getData( model ) ).to.equal( 'Sample text [ ] [] ' );
+ } );
+
+ it( 'should not replace the brackets if it contains a text', () => {
+ setData( model, '[]' );
+ insertBrackets( 'Foo' );
+ insertSpace();
+
+ expect( getData( model ) ).to.equal( '[Foo] [] ' );
+ } );
+
+ it( 'should not replace the brackets after ', () => {
+ setData( model, 'Foo [] ' );
+ insertBrackets();
+ insertSpace();
+
+ expect( getData( model ) ).to.equal( 'Foo [] [] ' );
+ } );
} );
describe( 'Heading', () => {
@@ -336,6 +433,15 @@ describe( 'Autoformat', () => {
expect( getData( model ) ).to.equal( '1. > [] ' );
} );
+ it( 'should not replace greater-than character when inside to-do list', () => {
+ setData( model, '>[] ' );
+ model.change( writer => {
+ writer.insertText( ' ', doc.selection.getFirstPosition() );
+ } );
+
+ expect( getData( model ) ).to.equal( '> [] ' );
+ } );
+
it( 'should not replace greater-than character after ', () => {
setData( model, 'Foo >[] ' );
model.change( writer => {
@@ -400,6 +506,15 @@ describe( 'Autoformat', () => {
expect( getData( model ) ).to.equal( '1. ```[] ' );
} );
+
+ it( 'should not replace triple grave accents when inside todo list', () => {
+ setData( model, '``[] ' );
+ model.change( writer => {
+ writer.insertText( '`', doc.selection.getFirstPosition() );
+ } );
+
+ expect( getData( model ) ).to.equal( '```[] ' );
+ } );
} );
describe( 'Inline autoformat', () => {
@@ -485,28 +600,64 @@ describe( 'Autoformat', () => {
expect( getData( model ) ).to.equal( '**foobar**[] ' );
} );
+ describe( 'should not format', () => {
+ it( '* without space preceding it', () => {
+ setData( model, 'fo*ob*ar[] ' );
+
+ model.change( writer => {
+ writer.insertText( '*', doc.selection.getFirstPosition() );
+ } );
+
+ expect( getData( model ) ).to
+ .equal( 'fo*ob*ar*[] ' );
+ } );
+
+ it( '__ without space preceding it', () => {
+ setData( model, 'fo__ob__ar_[] ' );
+
+ model.change( writer => {
+ writer.insertText( '_', doc.selection.getFirstPosition() );
+ } );
+
+ expect( getData( model ) ).to
+ .equal( 'fo__ob__ar__[] ' );
+ } );
+
+ // https://github.com/ckeditor/ckeditor5/issues/2388
+ it( 'snake_case sentences', () => {
+ setData( model, 'foo_bar baz[] ' );
+
+ model.change( writer => {
+ writer.insertText( '_', doc.selection.getFirstPosition() );
+ } );
+
+ expect( getData( model ) ).to
+ .equal( 'foo_bar baz_[] ' );
+ } );
+ } );
+
describe( 'with code element', () => {
describe( 'should not format (inside)', () => {
it( '* inside', () => {
- setData( model, '<$text code="true">fo*obar[]$text> ' );
+ setData( model, '<$text code="true">fo *obar[]$text> ' );
model.change( writer => {
writer.insertText( '*', { code: true }, doc.selection.getFirstPosition() );
} );
expect( getData( model ) ).to
- .equal( '<$text code="true">fo*obar*[]$text> ' );
+ .equal( '<$text code="true">fo *obar*[]$text> ' );
} );
it( '__ inside', () => {
- setData( model, '<$text code="true">fo__obar_[]$text> ' );
+ setData( model, '<$text code="true">fo __obar_[]$text> ' );
model.change( writer => {
writer.insertText( '_', { code: true }, doc.selection.getFirstPosition() );
} );
expect( getData( model ) ).to
- .equal( '<$text code="true">fo__obar__[]$text> ' );
+ .equal( '<$text code="true">fo __obar__[]$text> ' );
} );
it( '~~ inside', () => {
@@ -534,24 +685,24 @@ describe( 'Autoformat', () => {
describe( 'should not format (across)', () => {
it( '* across', () => {
- setData( model, '<$text code="true">fo*o$text>bar[] ' );
+ setData( model, '<$text code="true">fo *o$text>bar[] ' );
model.change( writer => {
writer.insertText( '*', doc.selection.getFirstPosition() );
} );
expect( getData( model ) ).to
- .equal( '<$text code="true">fo*o$text>bar*[] ' );
+ .equal( '<$text code="true">fo *o$text>bar*[] ' );
} );
it( '__ across', () => {
- setData( model, '<$text code="true">fo__o$text>bar_[] ' );
+ setData( model, '<$text code="true">fo __o$text>bar_[] ' );
model.change( writer => {
writer.insertText( '_', doc.selection.getFirstPosition() );
} );
expect( getData( model ) ).to
- .equal( '<$text code="true">fo__o$text>bar__[] ' );
+ .equal( '<$text code="true">fo __o$text>bar__[] ' );
} );
it( '~~ across', () => {
setData( model, '<$text code="true">fo~~o$text>bar~[] ' );
@@ -577,24 +728,24 @@ describe( 'Autoformat', () => {
describe( 'should format', () => {
it( '* after', () => {
- setData( model, '<$text code="true">fo*o$text>b*ar[] ' );
+ setData( model, '<$text code="true">fo*o$text>b *ar[] ' );
model.change( writer => {
writer.insertText( '*', doc.selection.getFirstPosition() );
} );
expect( getData( model ) ).to
- .equal( '<$text code="true">fo*o$text>b<$text italic="true">ar$text>[] ' );
+ .equal( '<$text code="true">fo*o$text>b <$text italic="true">ar$text>[] ' );
} );
it( '__ after', () => {
- setData( model, '<$text code="true">fo__o$text>b__ar_[] ' );
+ setData( model, '<$text code="true">fo__o$text>b __ar_[] ' );
model.change( writer => {
writer.insertText( '_', doc.selection.getFirstPosition() );
} );
expect( getData( model ) ).to
- .equal( '<$text code="true">fo__o$text>b<$text bold="true">ar$text>[] ' );
+ .equal( '<$text code="true">fo__o$text>b <$text bold="true">ar$text>[] ' );
} );
it( '~~ after', () => {
setData( model, '<$text code="true">fo~~o$text>b~~ar~[] ' );
@@ -669,6 +820,18 @@ describe( 'Autoformat', () => {
expect( getData( model ) ).to.equal( '1. [] ' );
} );
+ it( 'should not replace square brackets with to-do list item', () => {
+ setData( model, '[] ' );
+ model.change( writer => {
+ writer.insertText( '[]', doc.selection.getFirstPosition() );
+ } );
+ model.change( writer => {
+ writer.insertText( ' ', doc.selection.getFirstPosition() );
+ } );
+
+ expect( getData( model ) ).to.equal( '[] [] ' );
+ } );
+
it( 'should not replace hash character with heading', () => {
setData( model, '#[] ' );
model.change( writer => {
diff --git a/packages/ckeditor5-autoformat/tests/manual/autoformat.js b/packages/ckeditor5-autoformat/tests/manual/autoformat.js
index ff1067df92d..7b885661cfc 100644
--- a/packages/ckeditor5-autoformat/tests/manual/autoformat.js
+++ b/packages/ckeditor5-autoformat/tests/manual/autoformat.js
@@ -9,6 +9,7 @@ import ClassicEditor from '@ckeditor/ckeditor5-editor-classic/src/classiceditor'
import Autoformat from '../../src/autoformat';
import Enter from '@ckeditor/ckeditor5-enter/src/enter';
import List from '@ckeditor/ckeditor5-list/src/list';
+import TodoList from '@ckeditor/ckeditor5-list/src/todolist';
import BlockQuote from '@ckeditor/ckeditor5-block-quote/src/blockquote';
import Typing from '@ckeditor/ckeditor5-typing/src/typing';
import Heading from '@ckeditor/ckeditor5-heading/src/heading';
@@ -23,10 +24,38 @@ import ShiftEnter from '@ckeditor/ckeditor5-enter/src/shiftenter';
ClassicEditor
.create( document.querySelector( '#editor' ), {
- plugins: [ Enter, Typing, Paragraph, Undo, Bold, Italic, Code, Strikethrough, Heading, List, Autoformat, BlockQuote, CodeBlock,
- ShiftEnter ],
- toolbar: [ 'heading', '|', 'numberedList', 'bulletedList', 'blockQuote', 'codeBlock', 'bold', 'italic', 'code', 'strikethrough',
- 'undo', 'redo' ]
+ plugins: [
+ Enter,
+ Typing,
+ Paragraph,
+ Undo,
+ Bold,
+ Italic,
+ Code,
+ Strikethrough,
+ Heading,
+ List,
+ TodoList,
+ Autoformat,
+ BlockQuote,
+ CodeBlock,
+ ShiftEnter
+ ],
+ toolbar: [
+ 'heading',
+ '|',
+ 'numberedList',
+ 'bulletedList',
+ 'todoList',
+ 'blockQuote',
+ 'codeBlock',
+ 'bold',
+ 'italic',
+ 'code',
+ 'strikethrough',
+ 'undo',
+ 'redo'
+ ]
} )
.then( editor => {
window.editor = editor;
diff --git a/packages/ckeditor5-block-quote/src/blockquoteediting.js b/packages/ckeditor5-block-quote/src/blockquoteediting.js
index cfb0472b420..bbe4047a280 100644
--- a/packages/ckeditor5-block-quote/src/blockquoteediting.js
+++ b/packages/ckeditor5-block-quote/src/blockquoteediting.js
@@ -8,6 +8,7 @@
*/
import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
+import priorities from '@ckeditor/ckeditor5-utils/src/priorities';
import BlockQuoteCommand from './blockquotecommand';
@@ -102,31 +103,49 @@ export default class BlockQuoteEditing extends Plugin {
return false;
} );
- }
- /**
- * @inheritDoc
- */
- afterInit() {
- const editor = this.editor;
- const command = editor.commands.get( 'blockQuote' );
+ const viewDocument = this.editor.editing.view.document;
+ const selection = editor.model.document.selection;
+ const blockQuoteCommand = editor.commands.get( 'blockQuote' );
// Overwrite default Enter key behavior.
// If Enter key is pressed with selection collapsed in empty block inside a quote, break the quote.
- // This listener is added in afterInit in order to register it after list's feature listener.
- // We can't use a priority for this, because 'low' is already used by the enter feature, unless
- // we'd use numeric priority in this case.
- this.listenTo( this.editor.editing.view.document, 'enter', ( evt, data ) => {
- const doc = this.editor.model.document;
- const positionParent = doc.selection.getLastPosition().parent;
+ //
+ // Priority normal - 10 to override default handler but not list's feature listener.
+ this.listenTo( viewDocument, 'enter', ( evt, data ) => {
+ if ( !selection.isCollapsed || !blockQuoteCommand.value ) {
+ return;
+ }
- if ( doc.selection.isCollapsed && positionParent.isEmpty && command.value ) {
- this.editor.execute( 'blockQuote' );
- this.editor.editing.view.scrollToTheSelection();
+ const positionParent = selection.getLastPosition().parent;
+
+ if ( positionParent.isEmpty ) {
+ editor.execute( 'blockQuote' );
+ editor.editing.view.scrollToTheSelection();
data.preventDefault();
evt.stop();
}
- } );
+ }, { priority: priorities.normal - 10 } );
+
+ // Overwrite default Backspace key behavior.
+ // If Backspace key is pressed with selection collapsed in first empty block inside a quote, break the quote.
+ //
+ // Priority high + 5 to override widget's feature listener but not list's feature listener.
+ this.listenTo( viewDocument, 'delete', ( evt, data ) => {
+ if ( data.direction != 'backward' || !selection.isCollapsed || !blockQuoteCommand.value ) {
+ return;
+ }
+
+ const positionParent = selection.getLastPosition().parent;
+
+ if ( positionParent.isEmpty && !positionParent.previousSibling ) {
+ editor.execute( 'blockQuote' );
+ editor.editing.view.scrollToTheSelection();
+
+ data.preventDefault();
+ evt.stop();
+ }
+ }, { priority: priorities.high + 5 } );
}
}
diff --git a/packages/ckeditor5-block-quote/tests/integration.js b/packages/ckeditor5-block-quote/tests/integration.js
index 3905303fcdf..91555ff4beb 100644
--- a/packages/ckeditor5-block-quote/tests/integration.js
+++ b/packages/ckeditor5-block-quote/tests/integration.js
@@ -281,7 +281,7 @@ describe( 'BlockQuote integration', () => {
);
} );
- it( 'removes empty quote when merging into another quote', () => {
+ it( 'unwraps empty quote when the backspace key pressed in the first empty paragraph in a quote', () => {
const data = fakeEventData();
setModelData( model,
@@ -295,12 +295,13 @@ describe( 'BlockQuote integration', () => {
expect( getModelData( model ) ).to.equal(
'x ' +
- 'a[] ' +
+ 'a ' +
+ '[] ' +
'y '
);
} );
- it( 'removes empty quote when merging into a paragraph', () => {
+ it( 'unwraps empty quote when the backspace key pressed in the empty paragraph that is the only content of quote', () => {
const data = fakeEventData();
setModelData( model,
@@ -312,7 +313,45 @@ describe( 'BlockQuote integration', () => {
viewDocument.fire( 'delete', data );
expect( getModelData( model ) ).to.equal(
- 'x[] ' +
+ 'x ' +
+ '[] ' +
+ 'y '
+ );
+ } );
+
+ it( 'unwraps quote from the first paragraph when the backspace key pressed', () => {
+ const data = fakeEventData();
+
+ setModelData( model,
+ 'x ' +
+ '[] foo ' +
+ 'y '
+ );
+
+ viewDocument.fire( 'delete', data );
+
+ expect( getModelData( model ) ).to.equal(
+ 'x ' +
+ '[] ' +
+ 'foo ' +
+ 'y '
+ );
+ } );
+
+ it( 'merges paragraphs in a quote when the backspace key pressed not in the first paragraph', () => {
+ const data = fakeEventData();
+
+ setModelData( model,
+ 'x ' +
+ '[] ' +
+ 'y '
+ );
+
+ viewDocument.fire( 'delete', data );
+
+ expect( getModelData( model ) ).to.equal(
+ 'x ' +
+ '[] ' +
'y '
);
} );
diff --git a/packages/ckeditor5-build-classic/tests/ckeditor.js b/packages/ckeditor5-build-classic/tests/ckeditor.js
index 97aa49c8739..48c496d0f5b 100644
--- a/packages/ckeditor5-build-classic/tests/ckeditor.js
+++ b/packages/ckeditor5-build-classic/tests/ckeditor.js
@@ -200,6 +200,21 @@ describe( 'ClassicEditor build', () => {
expect( editor.ui.view.stickyPanel.viewportTopOffset ).to.equal( 42 );
} );
} );
+
+ it( 'allows removing built-in toolbar items', () => {
+ return ClassicEditor
+ .create( editorElement, {
+ toolbar: {
+ removeItems: [ 'italic' ]
+ }
+ } )
+ .then( newEditor => {
+ editor = newEditor;
+
+ expect( editor.ui.view.toolbar.items.length ).to.equal( 16 );
+ expect( editor.ui.view.toolbar.items.find( item => item.label === 'Italic' ) ).to.be.undefined;
+ } );
+ } );
} );
describeMemoryUsage( () => {
diff --git a/packages/ckeditor5-editor-classic/src/classiceditorui.js b/packages/ckeditor5-editor-classic/src/classiceditorui.js
index 6b389a245a8..95e79ac8706 100644
--- a/packages/ckeditor5-editor-classic/src/classiceditorui.js
+++ b/packages/ckeditor5-editor-classic/src/classiceditorui.js
@@ -148,7 +148,7 @@ export default class ClassicEditorUI extends EditorUI {
view.stickyPanel.viewportTopOffset = this._toolbarConfig.viewportTopOffset;
}
- view.toolbar.fillFromConfig( this._toolbarConfig.items, this.componentFactory );
+ view.toolbar.fillFromConfig( this._toolbarConfig, this.componentFactory );
enableToolbarKeyboardFocus( {
origin: editingView,
diff --git a/packages/ckeditor5-editor-classic/tests/classiceditorui.js b/packages/ckeditor5-editor-classic/tests/classiceditorui.js
index 7b987415a6d..b46ce4cbbcc 100644
--- a/packages/ckeditor5-editor-classic/tests/classiceditorui.js
+++ b/packages/ckeditor5-editor-classic/tests/classiceditorui.js
@@ -198,6 +198,24 @@ describe( 'ClassicEditorUI', () => {
return editor.destroy();
} );
} );
+
+ it( 'can be removed using config.toolbar.removeItems', () => {
+ return VirtualClassicTestEditor
+ .create( '', {
+ toolbar: {
+ items: [ 'foo', 'bar' ],
+ removeItems: [ 'bar' ]
+ }
+ } )
+ .then( editor => {
+ const items = editor.ui.view.toolbar.items;
+
+ expect( items.get( 0 ).name ).to.equal( 'foo' );
+ expect( items.length ).to.equal( 1 );
+
+ return editor.destroy();
+ } );
+ } );
} );
} );
diff --git a/packages/ckeditor5-editor-decoupled/src/decouplededitorui.js b/packages/ckeditor5-editor-decoupled/src/decouplededitorui.js
index a0cdd9f971c..565f44f6da8 100644
--- a/packages/ckeditor5-editor-decoupled/src/decouplededitorui.js
+++ b/packages/ckeditor5-editor-decoupled/src/decouplededitorui.js
@@ -9,7 +9,6 @@
import EditorUI from '@ckeditor/ckeditor5-core/src/editor/editorui';
import enableToolbarKeyboardFocus from '@ckeditor/ckeditor5-ui/src/toolbar/enabletoolbarkeyboardfocus';
-import normalizeToolbarConfig from '@ckeditor/ckeditor5-ui/src/toolbar/normalizetoolbarconfig';
import { enablePlaceholder } from '@ckeditor/ckeditor5-engine/src/view/placeholder';
/**
@@ -34,14 +33,6 @@ export default class DecoupledEditorUI extends EditorUI {
* @member {module:ui/editorui/editoruiview~EditorUIView} #view
*/
this.view = view;
-
- /**
- * A normalized `config.toolbar` object.
- *
- * @type {Object}
- * @private
- */
- this._toolbarConfig = normalizeToolbarConfig( editor.config.get( 'toolbar' ) );
}
/**
@@ -114,7 +105,7 @@ export default class DecoupledEditorUI extends EditorUI {
const view = this.view;
const toolbar = view.toolbar;
- toolbar.fillFromConfig( this._toolbarConfig.items, this.componentFactory );
+ toolbar.fillFromConfig( editor.config.get( 'toolbar' ), this.componentFactory );
enableToolbarKeyboardFocus( {
origin: editor.editing.view,
diff --git a/packages/ckeditor5-editor-decoupled/tests/decouplededitorui.js b/packages/ckeditor5-editor-decoupled/tests/decouplededitorui.js
index 527844d6b11..8edbc51fc98 100644
--- a/packages/ckeditor5-editor-decoupled/tests/decouplededitorui.js
+++ b/packages/ckeditor5-editor-decoupled/tests/decouplededitorui.js
@@ -170,6 +170,24 @@ describe( 'DecoupledEditorUI', () => {
return editor.destroy();
} );
} );
+
+ it( 'can be removed using config.toolbar.removeItems', () => {
+ return VirtualDecoupledTestEditor
+ .create( '', {
+ toolbar: {
+ items: [ 'foo', 'bar' ],
+ removeItems: [ 'bar' ]
+ }
+ } )
+ .then( editor => {
+ const items = editor.ui.view.toolbar.items;
+
+ expect( items.get( 0 ).name ).to.equal( 'foo' );
+ expect( items.length ).to.equal( 1 );
+
+ return editor.destroy();
+ } );
+ } );
} );
} );
diff --git a/packages/ckeditor5-editor-inline/src/inlineeditorui.js b/packages/ckeditor5-editor-inline/src/inlineeditorui.js
index cac847a9eae..07c36b4edcb 100644
--- a/packages/ckeditor5-editor-inline/src/inlineeditorui.js
+++ b/packages/ckeditor5-editor-inline/src/inlineeditorui.js
@@ -142,7 +142,7 @@ export default class InlineEditorUI extends EditorUI {
}
} );
- toolbar.fillFromConfig( this._toolbarConfig.items, this.componentFactory );
+ toolbar.fillFromConfig( this._toolbarConfig, this.componentFactory );
enableToolbarKeyboardFocus( {
origin: editingView,
diff --git a/packages/ckeditor5-editor-inline/tests/inlineeditorui.js b/packages/ckeditor5-editor-inline/tests/inlineeditorui.js
index 20fe4246a0f..ac9062c4f87 100644
--- a/packages/ckeditor5-editor-inline/tests/inlineeditorui.js
+++ b/packages/ckeditor5-editor-inline/tests/inlineeditorui.js
@@ -223,6 +223,24 @@ describe( 'InlineEditorUI', () => {
return editor.destroy();
} );
} );
+
+ it( 'can be removed using config.toolbar.removeItems', () => {
+ return VirtualInlineTestEditor
+ .create( '', {
+ toolbar: {
+ items: [ 'foo', 'bar' ],
+ removeItems: [ 'bar' ]
+ }
+ } )
+ .then( editor => {
+ const items = editor.ui.view.toolbar.items;
+
+ expect( items.get( 0 ).name ).to.equal( 'foo' );
+ expect( items.length ).to.equal( 1 );
+
+ return editor.destroy();
+ } );
+ } );
} );
it( 'initializes keyboard navigation between view#toolbar and view#editable', () => {
diff --git a/packages/ckeditor5-engine/docs/framework/guides/deep-dive/element-reconversion.md b/packages/ckeditor5-engine/docs/framework/guides/deep-dive/element-reconversion.md
index 7c776d14189..14b936d26e3 100644
--- a/packages/ckeditor5-engine/docs/framework/guides/deep-dive/element-reconversion.md
+++ b/packages/ckeditor5-engine/docs/framework/guides/deep-dive/element-reconversion.md
@@ -115,7 +115,7 @@ In this example implementation you will implement a "card" box which is displaye
A simplified model markup for the side card looks as follows:
```html
-
+
The title
The content
@@ -601,21 +601,21 @@ class ComplexBox extends Plugin {
const editor = this.editor;
// Defines a simple text button.
- editor.ui.componentFactory.add( 'insertCard', locale => {
+ editor.ui.componentFactory.add( 'complexBox', locale => {
const button = new ButtonView( locale );
- const command = editor.commands.get( 'insertCard' );
+ const command = editor.commands.get( 'insertComplexBox' );
button.set( {
withText: true,
icon: false,
- label: 'Insert card'
+ label: 'Complex Box'
} );
button.bind( 'isEnabled' ).to( command );
button.on( 'execute', () => {
- editor.execute( 'insertCard' );
+ editor.execute( 'insertComplexBox' );
editor.editing.view.focus();
} );
diff --git a/packages/ckeditor5-engine/src/model/differ.js b/packages/ckeditor5-engine/src/model/differ.js
index f57b65ef497..7d66a775ca0 100644
--- a/packages/ckeditor5-engine/src/model/differ.js
+++ b/packages/ckeditor5-engine/src/model/differ.js
@@ -393,7 +393,7 @@ export default class Differ {
}
// Will contain returned results.
- const diffSet = [];
+ let diffSet = [];
// Check all changed elements.
for ( const element of this._changesInElement.keys() ) {
@@ -483,8 +483,8 @@ export default class Differ {
} );
// Glue together multiple changes (mostly on text nodes).
- for ( let i = 1; i < diffSet.length; i++ ) {
- const prevDiff = diffSet[ i - 1 ];
+ for ( let i = 1, prevIndex = 0; i < diffSet.length; i++ ) {
+ const prevDiff = diffSet[ prevIndex ];
const thisDiff = diffSet[ i ];
// Glue remove changes if they happen on text on same position.
@@ -511,17 +511,20 @@ export default class Differ {
prevDiff.attributeNewValue == thisDiff.attributeNewValue;
if ( isConsecutiveTextRemove || isConsecutiveTextAdd || isConsecutiveAttributeChange ) {
- diffSet[ i - 1 ].length++;
+ prevDiff.length++;
if ( isConsecutiveAttributeChange ) {
- diffSet[ i - 1 ].range.end = diffSet[ i - 1 ].range.end.getShiftedBy( 1 );
+ prevDiff.range.end = prevDiff.range.end.getShiftedBy( 1 );
}
- diffSet.splice( i, 1 );
- i--;
+ diffSet[ i ] = null;
+ } else {
+ prevIndex = i;
}
}
+ diffSet = diffSet.filter( v => v );
+
// Remove `changeCount` property from diff items. It is used only for sorting and is internal thing.
for ( const item of diffSet ) {
delete item.changeCount;
@@ -536,7 +539,7 @@ export default class Differ {
// Cache changes.
this._cachedChangesWithGraveyard = diffSet.slice();
- this._cachedChanges = diffSet.slice().filter( _changesInGraveyardFilter );
+ this._cachedChanges = diffSet.filter( _changesInGraveyardFilter );
if ( options.includeChangesInGraveyard ) {
return this._cachedChangesWithGraveyard;
diff --git a/packages/ckeditor5-html-embed/docs/_snippets/features/html-embed.js b/packages/ckeditor5-html-embed/docs/_snippets/features/html-embed.js
index 2abaaa0d3c9..343f7fce54d 100644
--- a/packages/ckeditor5-html-embed/docs/_snippets/features/html-embed.js
+++ b/packages/ckeditor5-html-embed/docs/_snippets/features/html-embed.js
@@ -163,6 +163,11 @@ ClassicEditor
iframeElement.contentWindow.document.write( html );
iframeElement.contentWindow.document.close();
} );
+
+ window.attachTourBalloon( {
+ target: window.findToolbarItem( editor.ui.view.toolbar, item => item.label && item.label === 'Insert HTML' ),
+ text: 'Click here to insert new HTML snippet.'
+ } );
} )
.catch( err => {
console.error( err.stack );
diff --git a/packages/ckeditor5-html-embed/src/htmlembedediting.js b/packages/ckeditor5-html-embed/src/htmlembedediting.js
index aa8809c25c8..f70cd4f7828 100644
--- a/packages/ckeditor5-html-embed/src/htmlembedediting.js
+++ b/packages/ckeditor5-html-embed/src/htmlembedediting.js
@@ -183,6 +183,8 @@ export default class HtmlEmbedEditing extends Plugin {
if ( newValue !== state.getRawHtmlValue() ) {
editor.execute( 'updateHtmlEmbed', newValue );
editor.editing.view.focus();
+ } else {
+ this.cancel();
}
},
cancel() {
diff --git a/packages/ckeditor5-html-embed/tests/htmlembedediting.js b/packages/ckeditor5-html-embed/tests/htmlembedediting.js
index 2eaf51837ad..64b1c2f0f53 100644
--- a/packages/ckeditor5-html-embed/tests/htmlembedediting.js
+++ b/packages/ckeditor5-html-embed/tests/htmlembedediting.js
@@ -390,6 +390,27 @@ describe( 'HtmlEmbedEditing', () => {
expect( domContentWrapper.querySelectorAll( '.raw-html-embed__edit-button' ) ).to.have.lengthOf( 1 );
} );
+ it( 'switches to "preview mode" after clicking save button when there are no changes', () => {
+ setModelData( model, ' ' );
+
+ let widget = viewDocument.getRoot().getChild( 0 );
+ let contentWrapper = widget.getChild( 1 );
+ let domContentWrapper = editor.editing.view.domConverter.mapViewToDom( contentWrapper );
+
+ widget.getCustomProperty( 'rawHtmlApi' ).makeEditable();
+
+ domContentWrapper.querySelector( '.raw-html-embed__save-button' ).click();
+
+ // The entire DOM has rendered once again. The references were invalid.
+ widget = viewDocument.getRoot().getChild( 0 );
+ contentWrapper = widget.getChild( 1 );
+ domContentWrapper = editor.editing.view.domConverter.mapViewToDom( contentWrapper );
+
+ // There's exactly this button, and nothing else.
+ expect( domContentWrapper.querySelectorAll( 'button' ) ).to.have.lengthOf( 1 );
+ expect( domContentWrapper.querySelectorAll( '.raw-html-embed__edit-button' ) ).to.have.lengthOf( 1 );
+ } );
+
it( 'does not lose editor focus after saving changes', () => {
setModelData( model, ' ' );
const widget = viewDocument.getRoot().getChild( 0 );
diff --git a/packages/ckeditor5-image/docs/_snippets/features/image-insert-via-pasting-url-into-editor.html b/packages/ckeditor5-image/docs/_snippets/features/image-insert-via-pasting-url-into-editor.html
index c23d059e0a1..50a305a6923 100644
--- a/packages/ckeditor5-image/docs/_snippets/features/image-insert-via-pasting-url-into-editor.html
+++ b/packages/ckeditor5-image/docs/_snippets/features/image-insert-via-pasting-url-into-editor.html
@@ -1,3 +1,3 @@
-
Insert an URL of an image:
+
Insert a URL of an image:
diff --git a/packages/ckeditor5-image/src/imagecaption/imagecaptionediting.js b/packages/ckeditor5-image/src/imagecaption/imagecaptionediting.js
index dadeb895eb1..b5f5b8d2ff4 100644
--- a/packages/ckeditor5-image/src/imagecaption/imagecaptionediting.js
+++ b/packages/ckeditor5-image/src/imagecaption/imagecaptionediting.js
@@ -115,7 +115,7 @@ export default class ImageCaptionEditing extends Plugin {
}
// Is currently any caption selected?
- if ( viewCaption ) {
+ if ( viewCaption && !this.editor.isReadOnly ) {
// Was any caption selected before?
if ( lastCaption ) {
// Same caption as before?
diff --git a/packages/ckeditor5-image/tests/imagecaption/imagecaptionediting.js b/packages/ckeditor5-image/tests/imagecaption/imagecaptionediting.js
index 254896089f2..43f306e303e 100644
--- a/packages/ckeditor5-image/tests/imagecaption/imagecaptionediting.js
+++ b/packages/ckeditor5-image/tests/imagecaption/imagecaptionediting.js
@@ -519,6 +519,20 @@ describe( 'ImageCaptionEditing', () => {
);
} );
+ it( 'should not show empty figcaption when image is selected but editor is in the readOnly mode', () => {
+ editor.isReadOnly = true;
+
+ setModelData( model, '[ ]' );
+
+ expect( getViewData( view ) ).to.equal(
+ '[' +
+ ' ' +
+ ' ' +
+ ' ]'
+ );
+ } );
+
describe( 'undo/redo integration', () => {
it( 'should create view element after redo', () => {
setModelData( model, 'foo [foo bar baz] ' );
diff --git a/packages/ckeditor5-list/src/listediting.js b/packages/ckeditor5-list/src/listediting.js
index bb9c6e18671..9df189d6b03 100644
--- a/packages/ckeditor5-list/src/listediting.js
+++ b/packages/ckeditor5-list/src/listediting.js
@@ -12,6 +12,7 @@ import IndentCommand from './indentcommand';
import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph';
+import priorities from '@ckeditor/ckeditor5-utils/src/priorities';
import {
cleanList,
@@ -134,6 +135,8 @@ export default class ListEditing extends Plugin {
// Overwrite default Backspace key behavior.
// If Backspace key is pressed with selection collapsed on first position in first list item, outdent it. #83
+ //
+ // Priority high + 10 to override widget and blockquote feature listener.
this.listenTo( viewDocument, 'delete', ( evt, data ) => {
// Check conditions from those that require less computations like those immediately available.
if ( data.direction !== 'backward' ) {
@@ -168,7 +171,7 @@ export default class ListEditing extends Plugin {
data.preventDefault();
evt.stop();
- }, { priority: 'high' } );
+ }, { priority: priorities.high + 10 } );
const getCommandExecuter = commandName => {
return ( data, cancel ) => {
diff --git a/packages/ckeditor5-list/tests/listediting.js b/packages/ckeditor5-list/tests/listediting.js
index b780478d83b..26813e9c5d1 100644
--- a/packages/ckeditor5-list/tests/listediting.js
+++ b/packages/ckeditor5-list/tests/listediting.js
@@ -291,6 +291,21 @@ describe( 'ListEditing', () => {
sinon.assert.calledWithExactly( editor.execute, 'outdentList' );
} );
+
+ it( 'should outdent empty list when list is nested in block quote', () => {
+ const domEvtDataStub = { preventDefault() {}, direction: 'backward' };
+
+ sinon.spy( editor, 'execute' );
+
+ setModelData(
+ model,
+ 'x [] '
+ );
+
+ editor.editing.view.document.fire( 'delete', domEvtDataStub );
+
+ sinon.assert.calledWithExactly( editor.execute, 'outdentList' );
+ } );
} );
describe( 'tab key handling callback', () => {
diff --git a/packages/ckeditor5-mention/docs/_snippets/examples/chat-with-mentions.html b/packages/ckeditor5-mention/docs/_snippets/examples/chat-with-mentions.html
index ef7557d81dc..a62dbbebd29 100644
--- a/packages/ckeditor5-mention/docs/_snippets/examples/chat-with-mentions.html
+++ b/packages/ckeditor5-mention/docs/_snippets/examples/chat-with-mentions.html
@@ -119,6 +119,10 @@
border-top-right-radius: 0;
}
+ .chat .chat__editor + .ck.ck-editor .ck-content.highlighted {
+ animation: highlight 600ms ease-out;
+ }
+
/* ---- In–editor mention list --------------------------------------------------------------- */
.ck-mentions .mention__item {
diff --git a/packages/ckeditor5-mention/docs/_snippets/examples/chat-with-mentions.js b/packages/ckeditor5-mention/docs/_snippets/examples/chat-with-mentions.js
index 41746724c88..d135ebea4f6 100644
--- a/packages/ckeditor5-mention/docs/_snippets/examples/chat-with-mentions.js
+++ b/packages/ckeditor5-mention/docs/_snippets/examples/chat-with-mentions.js
@@ -3,7 +3,7 @@
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
-/* globals console, window, document */
+/* globals console, window, document, setTimeout */
import ClassicEditor from '@ckeditor/ckeditor5-build-classic/src/ckeditor';
import Underline from '@ckeditor/ckeditor5-basic-styles/src/underline';
@@ -68,11 +68,31 @@ ClassicEditor
}
} )
.then( editor => {
+ const editingView = editor.editing.view;
+ const rootElement = editingView.document.getRoot();
+
window.editor = editor;
// Clone the first message in the chat when "Send" is clicked, fill it with new data
// and append to the chat list.
document.querySelector( '.chat-send' ).addEventListener( 'click', () => {
+ const message = editor.getData();
+
+ if ( !message ) {
+ editingView.change( writer => {
+ writer.addClass( 'highlighted', rootElement );
+ editingView.focus();
+ } );
+
+ setTimeout( () => {
+ editingView.change( writer => {
+ writer.removeClass( 'highlighted', rootElement );
+ } );
+ }, 650 );
+
+ return;
+ }
+
const clone = document.querySelector( '.chat__posts li' ).cloneNode( true );
clone.classList.add( 'new-post' );
@@ -85,9 +105,12 @@ ClassicEditor
mailtoUser.href = 'mailto:info@cksource.com';
clone.querySelector( '.chat__posts__post__time' ).textContent = 'just now';
- clone.querySelector( '.chat__posts__post__content' ).innerHTML = editor.getData();
+ clone.querySelector( '.chat__posts__post__content' ).innerHTML = message;
document.querySelector( '.chat__posts' ).appendChild( clone );
+
+ editor.setData( '' );
+ editingView.focus();
} );
} )
.catch( err => {
diff --git a/packages/ckeditor5-mention/docs/examples/chat-with-mentions.md b/packages/ckeditor5-mention/docs/examples/chat-with-mentions.md
index 7019d8a150b..24124ae4141 100644
--- a/packages/ckeditor5-mention/docs/examples/chat-with-mentions.md
+++ b/packages/ckeditor5-mention/docs/examples/chat-with-mentions.md
@@ -141,6 +141,10 @@ The HTML code of the application is listed below:
border-top-right-radius: 0;
}
+ .chat .chat__editor + .ck.ck-editor .ck-content.highlighted {
+ animation: highlight 600ms ease-out;
+ }
+
/* ---- In–editor mention list --------------------------------------------------------------- */
.ck-mentions .mention__item {
@@ -249,11 +253,31 @@ ClassicEditor
}
} )
.then( editor => {
+ const editingView = editor.editing.view;
+ const rootElement = editingView.document.getRoot();
+
window.editor = editor;
// Clone the first message in the chat when "Send" is clicked, fill it with new data
// and append to the chat list.
document.querySelector( '.chat-send' ).addEventListener( 'click', () => {
+ const message = editor.getData();
+
+ if ( !message ) {
+ editingView.change( writer => {
+ writer.addClass( 'highlighted', rootElement );
+ editingView.focus();
+ } );
+
+ setTimeout( () => {
+ editingView.change( writer => {
+ writer.removeClass( 'highlighted', rootElement );
+ } );
+ }, 650 );
+
+ return;
+ }
+
const clone = document.querySelector( '.chat__posts li' ).cloneNode( true );
clone.classList.add( 'new-post' );
@@ -266,9 +290,12 @@ ClassicEditor
mailtoUser.href = 'mailto:info@cksource.com';
clone.querySelector( '.chat__posts__post__time' ).textContent = 'just now';
- clone.querySelector( '.chat__posts__post__content' ).innerHTML = editor.getData();
+ clone.querySelector( '.chat__posts__post__content' ).innerHTML = message;
document.querySelector( '.chat__posts' ).appendChild( clone );
+
+ editor.setData( '' );
+ editingView.focus();
} );
} )
.catch( err => {
diff --git a/packages/ckeditor5-mention/src/mentionediting.js b/packages/ckeditor5-mention/src/mentionediting.js
index 4f6e67a06a3..176436549cf 100644
--- a/packages/ckeditor5-mention/src/mentionediting.js
+++ b/packages/ckeditor5-mention/src/mentionediting.js
@@ -49,7 +49,7 @@ export default class MentionEditing extends Plugin {
},
model: {
key: 'mention',
- value: _toMentionAttribute
+ value: viewElement => _toMentionAttribute( viewElement )
}
} );
diff --git a/packages/ckeditor5-mention/tests/mentionediting.js b/packages/ckeditor5-mention/tests/mentionediting.js
index 0408a3ca0b5..b3915378176 100644
--- a/packages/ckeditor5-mention/tests/mentionediting.js
+++ b/packages/ckeditor5-mention/tests/mentionediting.js
@@ -238,6 +238,20 @@ describe( 'MentionEditing', () => {
expect( editor.getData() ).to.equal( expectedView );
expect( getViewData( editor.editing.view, { withoutSelection: true } ) ).to.equal( expectedView );
} );
+
+ // https://github.com/ckeditor/ckeditor5/issues/8370
+ it( 'should pass down only relevant attributes', () => {
+ editor.setData( 'fooJohn
' );
+
+ const textNode = doc.getRoot().getChild( 0 ).getChild( 1 );
+ const attributeValue = textNode.getAttribute( 'mention' );
+
+ expect( Object.keys( attributeValue ) ).to.have.members( [
+ 'id',
+ 'uid',
+ '_text'
+ ] );
+ } );
} );
describe( 'selection post-fixer', () => {
diff --git a/packages/ckeditor5-typing/src/deletecommand.js b/packages/ckeditor5-typing/src/deletecommand.js
index fc19e45acad..1b5a4b2e8d0 100644
--- a/packages/ckeditor5-typing/src/deletecommand.js
+++ b/packages/ckeditor5-typing/src/deletecommand.js
@@ -77,6 +77,7 @@ export default class DeleteCommand extends Command {
this._buffer.lock();
const selection = writer.createSelection( options.selection || doc.selection );
+ const sequence = options.sequence || 1;
// Do not replace the whole selected content if selection was collapsed.
// This prevents such situation:
@@ -91,12 +92,20 @@ export default class DeleteCommand extends Command {
}
// Check if deleting in an empty editor. See #61.
- if ( this._shouldEntireContentBeReplacedWithParagraph( options.sequence || 1 ) ) {
+ if ( this._shouldEntireContentBeReplacedWithParagraph( sequence ) ) {
this._replaceEntireContentWithParagraph( writer );
return;
}
+ // Check if deleting in the first empty block.
+ // See https://github.com/ckeditor/ckeditor5/issues/8137.
+ if ( this._shouldReplaceFirstBlockWithParagraph( selection, sequence ) ) {
+ this.editor.execute( 'paragraph', { selection } );
+
+ return;
+ }
+
// If selection is still collapsed, then there's nothing to delete.
if ( selection.isCollapsed ) {
return;
@@ -180,6 +189,7 @@ export default class DeleteCommand extends Command {
* The entire content is replaced with the paragraph. Selection is moved inside the paragraph.
*
* @private
+ * @param {module:engine/model/writer~Writer} writer The model writer.
*/
_replaceEntireContentWithParagraph( writer ) {
const model = this.editor.model;
@@ -193,4 +203,53 @@ export default class DeleteCommand extends Command {
writer.setSelection( paragraph, 0 );
}
+
+ /**
+ * Checks if the selection is inside an empty element that is the first child of the limit element
+ * and should be replaced with a paragraph.
+ *
+ * @private
+ * @param {module:engine/model/selection~Selection} selection The selection.
+ * @param {Number} sequence A number describing which subsequent delete event it is without the key being released.
+ * @returns {Boolean}
+ */
+ _shouldReplaceFirstBlockWithParagraph( selection, sequence ) {
+ const model = this.editor.model;
+
+ // Does nothing if user pressed and held the "Backspace" key or it was a "Delete" button.
+ if ( sequence > 1 || this.direction != 'backward' ) {
+ return false;
+ }
+
+ if ( !selection.isCollapsed ) {
+ return false;
+ }
+
+ const position = selection.getFirstPosition();
+ const limitElement = model.schema.getLimitElement( position );
+ const limitElementFirstChild = limitElement.getChild( 0 );
+
+ // Only elements that are direct children of the limit element can be replaced.
+ // Unwrapping from a block quote should be handled in a dedicated feature.
+ if ( position.parent != limitElementFirstChild ) {
+ return false;
+ }
+
+ // A block should be replaced only if it was empty.
+ if ( !selection.containsEntireContent( limitElementFirstChild ) ) {
+ return false;
+ }
+
+ // Replace with a paragraph only if it's allowed there.
+ if ( !model.schema.checkChild( limitElement, 'paragraph' ) ) {
+ return false;
+ }
+
+ // Does nothing if the limit element already contains only a paragraph.
+ if ( limitElementFirstChild.name == 'paragraph' ) {
+ return false;
+ }
+
+ return true;
+ }
}
diff --git a/packages/ckeditor5-typing/tests/deletecommand.js b/packages/ckeditor5-typing/tests/deletecommand.js
index c6866f8087c..f59e37b2494 100644
--- a/packages/ckeditor5-typing/tests/deletecommand.js
+++ b/packages/ckeditor5-typing/tests/deletecommand.js
@@ -3,14 +3,17 @@
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
-import VirtualTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/virtualtesteditor';
-import ModelTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/modeltesteditor';
import DeleteCommand from '../src/deletecommand';
import Delete from '../src/delete';
import ChangeBuffer from '../src/utils/changebuffer';
-import { getData, setData } from '@ckeditor/ckeditor5-engine/src/dev-utils/model';
+
+import ParagraphCommand from '@ckeditor/ckeditor5-paragraph/src/paragraphcommand';
+import VirtualTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/virtualtesteditor';
+import ModelTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/modeltesteditor';
import testUtils from '@ckeditor/ckeditor5-core/tests/_utils/utils';
+import { getData, setData } from '@ckeditor/ckeditor5-engine/src/dev-utils/model';
+
describe( 'DeleteCommand', () => {
let editor, model, doc;
@@ -26,6 +29,8 @@ describe( 'DeleteCommand', () => {
const command = new DeleteCommand( editor, 'backward' );
editor.commands.add( 'delete', command );
+ editor.commands.add( 'paragraph', new ParagraphCommand( editor ) );
+
model.schema.register( 'paragraph', { inheritAllFrom: '$block' } );
model.schema.register( 'heading1', { inheritAllFrom: '$block' } );
} );
@@ -315,5 +320,71 @@ describe( 'DeleteCommand', () => {
expect( getData( model ) ).to.equal( '[] ' );
} );
+
+ describe( 'with the empty first block', () => {
+ it( 'replaces the first empty block with paragraph', () => {
+ setData( model, '[] foo ' );
+
+ editor.execute( 'delete' );
+
+ expect( getData( model ) ).to.equal( '[] foo ' );
+ } );
+
+ it( 'does not replace an element when Backspace key is held', () => {
+ setData( model, 'foo[] bar ' );
+
+ for ( let sequence = 1; sequence < 10; ++sequence ) {
+ editor.execute( 'delete', { sequence } );
+ }
+
+ expect( getData( model ) ).to.equal( '[] bar ' );
+ } );
+
+ it( 'does not replace with paragraph in another paragraph already occurs in limit element', () => {
+ setData( model, '[] foo ' );
+
+ const element = doc.getRoot().getNodeByPath( [ 0 ] );
+
+ editor.execute( 'delete' );
+
+ expect( element ).is.equal( doc.getRoot().getNodeByPath( [ 0 ] ) );
+ expect( getData( model ) ).to.equal( '[] foo ' );
+ } );
+
+ it( 'does not replace an element if a paragraph is not allowed in current position', () => {
+ model.schema.addChildCheck( ( ctx, childDef ) => {
+ if ( ctx.endsWith( '$root' ) && childDef.name == 'paragraph' ) {
+ return false;
+ }
+ } );
+
+ setData( model, '[] foo ' );
+
+ editor.execute( 'delete' );
+
+ expect( getData( model ) ).to.equal( '[] foo ' );
+ } );
+
+ it( 'does not replace an element if it\'s not empty', () => {
+ setData( model, '[]foo bar ' );
+
+ editor.execute( 'delete' );
+
+ expect( getData( model ) ).to.equal( '[]foo bar ' );
+ } );
+
+ it( 'does not replace an element if it\'s wrapped with some other element', () => {
+ model.schema.register( 'blockQuote', {
+ allowWhere: '$block',
+ allowContentOf: '$root'
+ } );
+
+ setData( model, '[] bar ' );
+
+ editor.execute( 'delete' );
+
+ expect( getData( model ) ).to.equal( '[] bar ' );
+ } );
+ } );
} );
} );
diff --git a/packages/ckeditor5-ui/src/toolbar/balloon/balloontoolbar.js b/packages/ckeditor5-ui/src/toolbar/balloon/balloontoolbar.js
index 165b8d37638..94e2e797417 100644
--- a/packages/ckeditor5-ui/src/toolbar/balloon/balloontoolbar.js
+++ b/packages/ckeditor5-ui/src/toolbar/balloon/balloontoolbar.js
@@ -185,7 +185,7 @@ export default class BalloonToolbar extends Plugin {
afterInit() {
const factory = this.editor.ui.componentFactory;
- this.toolbarView.fillFromConfig( this._balloonConfig.items, factory );
+ this.toolbarView.fillFromConfig( this._balloonConfig, factory );
}
/**
diff --git a/packages/ckeditor5-ui/src/toolbar/block/blocktoolbar.js b/packages/ckeditor5-ui/src/toolbar/block/blocktoolbar.js
index 773588c8cee..42860733317 100644
--- a/packages/ckeditor5-ui/src/toolbar/block/blocktoolbar.js
+++ b/packages/ckeditor5-ui/src/toolbar/block/blocktoolbar.js
@@ -179,7 +179,7 @@ export default class BlockToolbar extends Plugin {
const factory = this.editor.ui.componentFactory;
const config = this._blockToolbarConfig;
- this.toolbarView.fillFromConfig( config.items, factory );
+ this.toolbarView.fillFromConfig( config, factory );
// Hide panel before executing each button in the panel.
for ( const item of this.toolbarView.items ) {
diff --git a/packages/ckeditor5-ui/src/toolbar/normalizetoolbarconfig.js b/packages/ckeditor5-ui/src/toolbar/normalizetoolbarconfig.js
index b07935de123..56f14291597 100644
--- a/packages/ckeditor5-ui/src/toolbar/normalizetoolbarconfig.js
+++ b/packages/ckeditor5-ui/src/toolbar/normalizetoolbarconfig.js
@@ -18,6 +18,7 @@
*
* toolbar: {
* items: [ 'heading', 'bold', 'italic', 'link', ... ],
+ * removeItems: [ 'bold' ],
* ...
* }
*
@@ -31,17 +32,20 @@
export default function normalizeToolbarConfig( config ) {
if ( Array.isArray( config ) ) {
return {
- items: config
+ items: config,
+ removeItems: []
};
}
if ( !config ) {
return {
- items: []
+ items: [],
+ removeItems: []
};
}
return Object.assign( {
- items: []
+ items: [],
+ removeItems: []
}, config );
}
diff --git a/packages/ckeditor5-ui/src/toolbar/toolbarview.js b/packages/ckeditor5-ui/src/toolbar/toolbarview.js
index 3244b4d5900..3fcf50b287e 100644
--- a/packages/ckeditor5-ui/src/toolbar/toolbarview.js
+++ b/packages/ckeditor5-ui/src/toolbar/toolbarview.js
@@ -20,6 +20,7 @@ import global from '@ckeditor/ckeditor5-utils/src/dom/global';
import { createDropdown, addToolbarToDropdown } from '../dropdown/utils';
import { logWarning } from '@ckeditor/ckeditor5-utils/src/ckeditorerror';
import verticalDotsIcon from '@ckeditor/ckeditor5-core/theme/icons/three-vertical-dots.svg';
+import normalizeToolbarConfig from './normalizetoolbarconfig';
import '../../theme/components/toolbar/toolbar.css';
@@ -277,58 +278,125 @@ export default class ToolbarView extends View {
* A utility that expands the plain toolbar configuration into
* {@link module:ui/toolbar/toolbarview~ToolbarView#items} using a given component factory.
*
- * @param {Array.} config The toolbar items configuration.
+ * @param {Array.|Object} itemsOrConfig The toolbar items or the entire toolbar configuration object.
* @param {module:ui/componentfactory~ComponentFactory} factory A factory producing toolbar items.
*/
- fillFromConfig( config, factory ) {
- this.items.addMany( config.map( name => {
- if ( name == '|' ) {
- return new ToolbarSeparatorView();
- } else if ( name == '-' ) {
- if ( this.options.shouldGroupWhenFull ) {
+ fillFromConfig( itemsOrConfig, factory ) {
+ const config = normalizeToolbarConfig( itemsOrConfig );
+
+ const itemsToClean = config.items
+ .filter( ( name, idx, items ) => {
+ if ( name === '|' ) {
+ return true;
+ }
+
+ // Items listed in `config.removeItems` should not be added to the toolbar.
+ if ( config.removeItems.indexOf( name ) !== -1 ) {
+ return false;
+ }
+
+ if ( name === '-' ) {
+ // Toolbar line breaks must not be rendered when toolbar grouping is enabled.
+ // (https://github.com/ckeditor/ckeditor5/issues/8582)
+ if ( this.options.shouldGroupWhenFull ) {
+ /**
+ * Toolbar line breaks (`-` items) can only work when the automatic button grouping
+ * is disabled in the toolbar configuration.
+ * To do this, set the `shouldNotGroupWhenFull` option to `true` in the editor configuration:
+ *
+ * const config = {
+ * toolbar: {
+ * items: [ ... ],
+ * shouldNotGroupWhenFull: true
+ * }
+ * }
+ *
+ * Learn more about {@link module:core/editor/editorconfig~EditorConfig#toolbar toolbar configuration}.
+ *
+ * @error toolbarview-line-break-ignored-when-grouping-items
+ */
+ logWarning( 'toolbarview-line-break-ignored-when-grouping-items', items );
+
+ return false;
+ }
+
+ return true;
+ }
+
+ // For the items that cannot be instantiated we are sending warning message. We also filter them out.
+ if ( !factory.has( name ) ) {
/**
- * Toolbar line breaks (`-` items) can only work when the automatic button grouping
- * is disabled in the toolbar configuration.
- * To do this, set the `shouldNotGroupWhenFull` option to `true` in the editor configuration:
+ * There was a problem processing the configuration of the toolbar. The item with the given
+ * name does not exist so it was omitted when rendering the toolbar.
+ *
+ * This warning usually shows up when the {@link module:core/plugin~Plugin} which is supposed
+ * to provide a toolbar item has not been loaded or there is a typo in the configuration.
+ *
+ * Make sure the plugin responsible for this toolbar item is loaded and the toolbar configuration
+ * is correct, e.g. {@link module:basic-styles/bold~Bold} is loaded for the `'bold'` toolbar item.
*
- * const config = {
- * toolbar: {
- * items: [ ... ],
- * shouldNotGroupWhenFull: true
- * }
- * }
+ * You can use the following snippet to retrieve all available toolbar items:
*
- * Learn more about {@link module:core/editor/editorconfig~EditorConfig#toolbar toolbar configuration}.
+ * Array.from( editor.ui.componentFactory.names() );
*
- * @error toolbarview-line-break-ignored-when-grouping-items
+ * @error toolbarview-item-unavailable
+ * @param {String} name The name of the component.
*/
- logWarning( 'toolbarview-line-break-ignored-when-grouping-items', config );
+ logWarning( 'toolbarview-item-unavailable', { name } );
+
+ return false;
+ }
+
+ return true;
+ } );
+
+ const itemsToAdd = this._cleanSeparators( itemsToClean )
+ // Instantiate toolbar items.
+ .map( name => {
+ if ( name === '|' ) {
+ return new ToolbarSeparatorView();
+ } else if ( name === '-' ) {
+ return new ToolbarLineBreakView();
}
- return new ToolbarLineBreakView();
- } else if ( factory.has( name ) ) {
return factory.create( name );
- } else {
- /**
- * There was a problem processing the configuration of the toolbar. The item with the given
- * name does not exist so it was omitted when rendering the toolbar.
- *
- * This warning usually shows up when the {@link module:core/plugin~Plugin} which is supposed
- * to provide a toolbar item has not been loaded or there is a typo in the configuration.
- *
- * Make sure the plugin responsible for this toolbar item is loaded and the toolbar configuration
- * is correct, e.g. {@link module:basic-styles/bold~Bold} is loaded for the `'bold'` toolbar item.
- *
- * You can use the following snippet to retrieve all available toolbar items:
- *
- * Array.from( editor.ui.componentFactory.names() );
- *
- * @error toolbarview-item-unavailable
- * @param {String} name The name of the component.
- */
- logWarning( 'toolbarview-item-unavailable', { name } );
- }
- } ).filter( item => item !== undefined ) );
+ } );
+
+ this.items.addMany( itemsToAdd );
+ }
+
+ /**
+ * Remove leading, trailing, and duplicated separators (`-` and `|`).
+ *
+ * @private
+ * @param {Array.} items
+ */
+ _cleanSeparators( items ) {
+ const nonSeparatorPredicate = item => ( item !== '-' && item !== '|' );
+ const count = items.length;
+
+ // Find an index of the first item that is not a separator.
+ const firstCommandItem = items.findIndex( nonSeparatorPredicate );
+
+ // Search from the end of the list, then convert found index back to the original direction.
+ const lastCommandItem = count - items
+ .slice()
+ .reverse()
+ .findIndex( nonSeparatorPredicate );
+
+ return items
+ // Return items without the leading and trailing separators.
+ .slice( firstCommandItem, lastCommandItem )
+ // Remove duplicated separators.
+ .filter( ( name, idx, items ) => {
+ // Filter only separators.
+ if ( nonSeparatorPredicate( name ) ) {
+ return true;
+ }
+ const isDuplicated = idx > 0 && items[ idx - 1 ] === name;
+
+ return !isDuplicated;
+ } );
}
/**
@@ -863,6 +931,7 @@ class DynamicGrouping {
dropdown.buttonView.set( {
label: t( 'Show more items' ),
tooltip: true,
+ tooltipPosition: locale.uiLanguageDirection === 'rtl' ? 'se' : 'sw',
icon: verticalDotsIcon
} );
diff --git a/packages/ckeditor5-ui/tests/toolbar/normalizetoolbarconfig.js b/packages/ckeditor5-ui/tests/toolbar/normalizetoolbarconfig.js
index f648c777ae0..1d70467330f 100644
--- a/packages/ckeditor5-ui/tests/toolbar/normalizetoolbarconfig.js
+++ b/packages/ckeditor5-ui/tests/toolbar/normalizetoolbarconfig.js
@@ -7,11 +7,16 @@ import normalizeToolbarConfig from '../../src/toolbar/normalizetoolbarconfig';
describe( 'normalizeToolbarConfig()', () => {
it( 'normalizes the config specified as an Array', () => {
- const cfg = [ 'foo', 'bar' ];
- const normalized = normalizeToolbarConfig( cfg );
+ const items = [ 'foo', 'bar' ];
+ const normalized = normalizeToolbarConfig( items );
expect( normalized ).to.be.an( 'object' );
- expect( normalized.items ).to.deep.equal( cfg );
+ expect( normalized ).to.deep.equal(
+ {
+ items,
+ removeItems: []
+ }
+ );
} );
it( 'passes through an already normalized config', () => {
@@ -21,7 +26,9 @@ describe( 'normalizeToolbarConfig()', () => {
};
const normalized = normalizeToolbarConfig( cfg );
- expect( normalized ).to.deep.equal( cfg );
+ expect( normalized ).to.deep.equal(
+ Object.assign( { removeItems: [] }, cfg )
+ );
} );
it( 'adds missing items property', () => {
@@ -33,6 +40,7 @@ describe( 'normalizeToolbarConfig()', () => {
expect( normalized ).to.deep.equal( {
items: [],
+ removeItems: [],
foo: 'bar'
} );
expect( normalized ).to.not.equal( cfg ); // Make sure we don't modify an existing obj.
@@ -43,5 +51,6 @@ describe( 'normalizeToolbarConfig()', () => {
expect( normalized ).to.be.an( 'object' );
expect( normalized.items ).to.be.an( 'array' ).of.length( 0 );
+ expect( normalized.removeItems ).to.be.an( 'array' ).of.length( 0 );
} );
} );
diff --git a/packages/ckeditor5-ui/tests/toolbar/toolbarview.js b/packages/ckeditor5-ui/tests/toolbar/toolbarview.js
index f0c192b6a60..62e43aef06d 100644
--- a/packages/ckeditor5-ui/tests/toolbar/toolbarview.js
+++ b/packages/ckeditor5-ui/tests/toolbar/toolbarview.js
@@ -438,7 +438,6 @@ describe( 'ToolbarView', () => {
view.fillFromConfig( [ 'foo', '-', 'bar', '|', 'foo' ], factory );
const items = view.items;
-
expect( items ).to.have.length( 5 );
expect( items.get( 0 ).name ).to.equal( 'foo' );
expect( items.get( 1 ) ).to.be.instanceOf( ToolbarLineBreakView );
@@ -447,6 +446,96 @@ describe( 'ToolbarView', () => {
expect( items.get( 4 ).name ).to.equal( 'foo' );
} );
+ it( 'accepts configuration object', () => {
+ view.fillFromConfig( { items: [ 'foo', 'bar', 'foo' ] }, factory );
+
+ const items = view.items;
+ expect( items ).to.have.length( 3 );
+ expect( items.get( 0 ).name ).to.equal( 'foo' );
+ expect( items.get( 1 ).name ).to.equal( 'bar' );
+ expect( items.get( 2 ).name ).to.equal( 'foo' );
+ } );
+
+ it( 'removes items listed in `removeItems`', () => {
+ view.fillFromConfig(
+ {
+ items: [ 'foo', 'bar', 'foo' ],
+ removeItems: [ 'foo' ]
+ },
+ factory
+ );
+
+ const items = view.items;
+ expect( items ).to.have.length( 1 );
+ expect( items.get( 0 ).name ).to.equal( 'bar' );
+ } );
+
+ it( 'deduplicates consecutive separators after removing items listed in `removeItems` - the vertical separator case (`|`)', () => {
+ view.fillFromConfig(
+ {
+ items: [ '|', '|', 'foo', '|', 'bar', '|', 'foo' ],
+ removeItems: [ 'bar' ]
+ },
+ factory
+ );
+
+ const items = view.items;
+
+ expect( items ).to.have.length( 3 );
+ expect( items.get( 0 ).name ).to.equal( 'foo' );
+ expect( items.get( 1 ) ).to.be.instanceOf( ToolbarSeparatorView );
+ expect( items.get( 2 ).name ).to.equal( 'foo' );
+ } );
+
+ it( 'deduplicates consecutive separators after removing items listed in `removeItems` - the line break case (`-`)', () => {
+ view.fillFromConfig(
+ {
+ items: [ '-', '-', 'foo', '-', 'bar', '-', 'foo' ],
+ removeItems: [ 'bar' ]
+ },
+ factory
+ );
+
+ const items = view.items;
+
+ expect( items ).to.have.length( 3 );
+ expect( items.get( 0 ).name ).to.equal( 'foo' );
+ expect( items.get( 1 ) ).to.be.instanceOf( ToolbarLineBreakView );
+ expect( items.get( 2 ).name ).to.equal( 'foo' );
+ } );
+
+ it( 'removes trailing and leading separators from the item list - the vertical separator case (`|`)', () => {
+ view.fillFromConfig(
+ {
+ items: [ '|', '|', 'foo', '|', 'bar', '|' ]
+ },
+ factory
+ );
+
+ const items = view.items;
+
+ expect( items ).to.have.length( 3 );
+ expect( items.get( 0 ).name ).to.equal( 'foo' );
+ expect( items.get( 1 ) ).to.be.instanceOf( ToolbarSeparatorView );
+ expect( items.get( 2 ).name ).to.equal( 'bar' );
+ } );
+
+ it( 'removes trailing and leading separators from the item list - the line break case (`-`)', () => {
+ view.fillFromConfig(
+ {
+ items: [ '-', '-', 'foo', '-', 'bar', '-' ]
+ },
+ factory
+ );
+
+ const items = view.items;
+
+ expect( items ).to.have.length( 3 );
+ expect( items.get( 0 ).name ).to.equal( 'foo' );
+ expect( items.get( 1 ) ).to.be.instanceOf( ToolbarLineBreakView );
+ expect( items.get( 2 ).name ).to.equal( 'bar' );
+ } );
+
it( 'warns if there is no such component in the factory', () => {
const items = view.items;
const consoleWarnStub = sinon.stub( console, 'warn' );
@@ -478,6 +567,22 @@ describe( 'ToolbarView', () => {
sinon.match.string // Link to the documentation.
);
} );
+
+ // https://github.com/ckeditor/ckeditor5/issues/8582
+ it( 'does not render line separator when the button grouping option is enabled', () => {
+ // Catch warn to stop tests from failing in production mode.
+ sinon.stub( console, 'warn' );
+
+ view.options.shouldGroupWhenFull = true;
+
+ view.fillFromConfig( [ 'foo', '-', 'bar' ], factory );
+
+ const items = view.items;
+
+ expect( items ).to.have.length( 2 );
+ expect( items.get( 0 ).name ).to.equal( 'foo' );
+ expect( items.get( 1 ).name ).to.equal( 'bar' );
+ } );
} );
describe( 'toolbar with static items', () => {
@@ -982,6 +1087,26 @@ describe( 'ToolbarView', () => {
expect( groupedItemsDropdown.buttonView.label ).to.equal( 'Show more items' );
} );
+ it( 'tooltip has the proper position depending on the UI language direction (LTR UI)', () => {
+ const locale = new Locale( { uiLanguage: 'en' } );
+ const view = new ToolbarView( locale, { shouldGroupWhenFull: true } );
+ view.render();
+
+ expect( view._behavior.groupedItemsDropdown.buttonView.tooltipPosition ).to.equal( 'sw' );
+
+ view.destroy();
+ } );
+
+ it( 'tooltip has the proper position depending on the UI language direction (RTL UI)', () => {
+ const locale = new Locale( { uiLanguage: 'ar' } );
+ const view = new ToolbarView( locale, { shouldGroupWhenFull: true } );
+ view.render();
+
+ expect( view._behavior.groupedItemsDropdown.buttonView.tooltipPosition ).to.equal( 'se' );
+
+ view.destroy();
+ } );
+
it( 'shares its toolbarView#items with grouped items', () => {
view.items.add( focusable() );
view.items.add( focusable() );
diff --git a/scripts/docs/snippetadapter.js b/scripts/docs/snippetadapter.js
index 390170ccfe8..0a511825488 100644
--- a/scripts/docs/snippetadapter.js
+++ b/scripts/docs/snippetadapter.js
@@ -188,8 +188,18 @@ module.exports = function snippetAdapter( snippets, options, umbertoHelpers ) {
jsFiles.push( path.join( snippetData.basePath, 'assets', 'snippet.js' ) );
jsFiles.push( path.join( snippetData.relativeOutputPath, snippetData.snippetName, 'snippet.js' ) );
+ jsFiles.push( path.join( snippetData.relativeOutputPath,
+ '../../../../../', 'node_modules', '@popperjs', 'core', 'dist', 'umd', 'popper.min.js' ) );
+ jsFiles.push( path.join( snippetData.relativeOutputPath,
+ '../../../../../', 'node_modules', 'tippy.js', 'dist', 'tippy-bundle.umd.min.js' ) );
+ jsFiles.push( path.join( snippetData.basePath, 'assets', 'tour-balloon.js' ) );
cssFiles.push( path.join( snippetData.basePath, 'assets', 'snippet-styles.css' ) );
+ cssFiles.push( path.join( snippetData.relativeOutputPath,
+ '../../../../../', 'node_modules', 'tippy.js', 'dist', 'tippy.css' ) );
+ cssFiles.push( path.join( snippetData.relativeOutputPath,
+ '../../../../../', 'node_modules', 'tippy.js', 'themes', 'light-border.css' ) );
+ cssFiles.push( path.join( snippetData.basePath, 'assets', 'tour-balloon.css' ) );
if ( wasCSSGenerated ) {
cssFiles.unshift( path.join( snippetData.relativeOutputPath, snippetData.snippetName, 'snippet.css' ) );
From ca58a3bc8bd2981c69b934f5e500eac2bd9b501e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Pawe=C5=82=20Kwa=C5=9Bnik?=
Date: Thu, 7 Jan 2021 13:38:18 +0100
Subject: [PATCH 30/40] Fixing button indicator for block quote.
---
.../docs/_snippets/features/block-quote.js | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/packages/ckeditor5-block-quote/docs/_snippets/features/block-quote.js b/packages/ckeditor5-block-quote/docs/_snippets/features/block-quote.js
index 0303623d7f8..cd799c9a076 100644
--- a/packages/ckeditor5-block-quote/docs/_snippets/features/block-quote.js
+++ b/packages/ckeditor5-block-quote/docs/_snippets/features/block-quote.js
@@ -32,6 +32,12 @@ ClassicEditor
} )
.then( editor => {
window.editor = editor;
+
+ // eslint-disable-next-line no-undef
+ setTimeout( () => window.attachTourBalloon( {
+ target: window.findToolbarItem( editor.ui.view.toolbar, item => item.label && item.label === 'Block quote' ),
+ text: 'Click to insert a block quote.'
+ } ) );
} )
.catch( err => {
console.error( err );
From 9e06f0420f2e12f37b00d71849475eac164e5952 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Maksymilian=20Barna=C5=9B?=
Date: Thu, 7 Jan 2021 15:32:24 +0100
Subject: [PATCH 31/40] Add tests.
---
.../tableproperties/tablepropertiesediting.js | 73 ++++++++++++++++---
1 file changed, 61 insertions(+), 12 deletions(-)
diff --git a/packages/ckeditor5-table/tests/tableproperties/tablepropertiesediting.js b/packages/ckeditor5-table/tests/tableproperties/tablepropertiesediting.js
index 09ca00b858d..b22a4983480 100644
--- a/packages/ckeditor5-table/tests/tableproperties/tablepropertiesediting.js
+++ b/packages/ckeditor5-table/tests/tableproperties/tablepropertiesediting.js
@@ -227,19 +227,9 @@ describe( 'table properties', () => {
assertTRBLAttribute( table, 'borderColor', 'red' );
assertTRBLAttribute( table, 'borderStyle', 'solid' );
assertTRBLAttribute( table, 'borderWidth', '1px' );
- } );
-
- // https://github.com/ckeditor/ckeditor5/issues/8393.
- it( 'should not upcast contents of nested table', () => {
- editor.setData( '' +
- '' +
- 'parent:00 ' +
- '' +
- '' +
- ' ' +
- ' ' +
- '
' );
+ // Also check the entire structure of the model.
+ // Previously the test was too loose in that regard.
expect( getModelData( editor.model ) ).to.equal(
'[]'
);
} );
+
+ // https://github.com/ckeditor/ckeditor5/issues/8393.
+ it( 'should not throw error when loading nested table with border styles', () => {
+ expect( () => {
+ editor.setData(
+ '' +
+ '' +
+ '' +
+ ' ' +
+ '' +
+ '' +
+ '' +
+ ' ' +
+ ' ' +
+ ' ' +
+ '
' +
+ ' ' +
+ ' ' +
+ ' ' +
+ '
' );
+ } ).not.to.throw();
+
+ expect( () => {
+ editor.setData(
+ '' +
+ '' +
+ '' +
+ ' ' +
+ '' +
+ ' ' +
+ ' ' +
+ ' ' +
+ '
' );
+ } ).not.to.throw();
+
+ // Conversion will create a merged text node out of all the text contents,
+ // including the one in elements not allowed by schema in this scope.
+ // Let's make sure that upcasting will not try to use model that got processed this way.
+ expect( () => {
+ editor.setData(
+ '' +
+ ' ' +
+ '' +
+ '' +
+ '' +
+ 'parent:00 ' +
+ '' +
+ '' +
+ '' +
+ 'child:00 ' +
+ ' ' +
+ '
' +
+ ' ' +
+ ' ' +
+ '
' +
+ ' ' +
+ ' ' );
+ } ).not.to.throw();
+ } );
} );
describe( 'downcast conversion', () => {
From 47fc1510981ed59f6362f50a5458277b6992b0fe Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Magdalena=20Chrze=C5=9Bcian?=
Date: Fri, 8 Jan 2021 10:42:48 +0100
Subject: [PATCH 32/40] Fixed the RegExp for counting properly the words with
spacial characters inside.
---
.../ckeditor5-word-count/src/wordcount.js | 9 +-
.../ckeditor5-word-count/tests/wordcount.js | 195 ++++++++++++++----
2 files changed, 153 insertions(+), 51 deletions(-)
diff --git a/packages/ckeditor5-word-count/src/wordcount.js b/packages/ckeditor5-word-count/src/wordcount.js
index 7c5aef3c65f..3f7371f0a0e 100644
--- a/packages/ckeditor5-word-count/src/wordcount.js
+++ b/packages/ckeditor5-word-count/src/wordcount.js
@@ -37,7 +37,7 @@ import env from '@ckeditor/ckeditor5-utils/src/env';
* // Words: 0, Characters: 5
*
* foo(bar)
- * //Words: 2, Characters: 8
+ * //Words: 1, Characters: 8
*
* 12345
* // Words: 1, Characters: 5
@@ -134,11 +134,8 @@ export default class WordCount extends Plugin {
// Groups:
// {L} - Any kind of letter from any language.
// {N} - Any kind of numeric character in any script.
- // {M} - A character intended to be combined with another character (e.g. accents, umlauts, enclosing boxes, etc.).
- // {Pd} - Any kind of hyphen or dash.
- // {Pc} - A punctuation character such as an underscore that connects words.
- new RegExp( '[\\p{L}\\p{N}\\p{M}\\p{Pd}\\p{Pc}]+', 'gu' ) :
- /[_\-a-zA-Z0-9À-ž]+/gu;
+ new RegExp( '([\\p{L}\\p{N}]+\\S?)+', 'gu' ) :
+ /([a-zA-Z0-9À-ž]+\S?)+/gu;
}
/**
diff --git a/packages/ckeditor5-word-count/tests/wordcount.js b/packages/ckeditor5-word-count/tests/wordcount.js
index ef548963ebe..f59edfeacc3 100644
--- a/packages/ckeditor5-word-count/tests/wordcount.js
+++ b/packages/ckeditor5-word-count/tests/wordcount.js
@@ -16,6 +16,10 @@ import Position from '@ckeditor/ckeditor5-engine/src/model/position';
import ShiftEnter from '@ckeditor/ckeditor5-enter/src/shiftenter';
import TableEditing from '@ckeditor/ckeditor5-table/src/tableediting';
import env from '@ckeditor/ckeditor5-utils/src/env';
+import ListEditing from '@ckeditor/ckeditor5-list/src/listediting';
+import LinkEditing from '@ckeditor/ckeditor5-link/src/linkediting';
+import ImageCaptionEditing from '@ckeditor/ckeditor5-image/src/imagecaption/imagecaptionediting';
+import ImageEditing from '@ckeditor/ckeditor5-image/src/image/imageediting';
// Delay related to word-count throttling.
const DELAY = 255;
@@ -27,7 +31,7 @@ describe( 'WordCount', () => {
beforeEach( () => {
return VirtualTestEditor.create( {
- plugins: [ WordCount, Paragraph, ShiftEnter, TableEditing ]
+ plugins: [ WordCount, Paragraph, ShiftEnter, TableEditing, ListEditing, LinkEditing, ImageEditing, ImageCaptionEditing ]
} )
.then( _editor => {
editor = _editor;
@@ -119,17 +123,152 @@ describe( 'WordCount', () => {
} );
describe( 'functionality', () => {
- it( 'counts words', () => {
- expect( wordCountPlugin.words ).to.equal( 0 );
+ describe( 'counting words', () => {
+ beforeEach( () => {
+ expect( wordCountPlugin.words ).to.equal( 0 );
+ } );
- setModelData( model, 'Foo(bar)baz ' +
- '<$text foo="true">Hello$text> world. ' +
- '1234 ' +
- '(@#$%^*()) ' );
+ it( 'should count a number as a word', () => {
+ setModelData( model, '1 12 3,5 3/4 1.2 0 ' );
+ wordCountPlugin._refreshStats();
+ expect( wordCountPlugin.words ).to.equal( 6 );
+ } );
- wordCountPlugin._refreshStats();
+ it( 'should count a single letter as a word', () => {
+ setModelData( model, 'a ' );
+ wordCountPlugin._refreshStats();
+ expect( wordCountPlugin.words ).to.equal( 1 );
+ } );
+
+ it( 'should count an e-mail as a single word', () => {
+ setModelData( model, 'j.doe@cksource.com ' );
+ wordCountPlugin._refreshStats();
+ expect( wordCountPlugin.words ).to.equal( 1 );
+ } );
+
+ it( 'should ignore apostrophes in words', () => {
+ setModelData( model, 'Foo\'bar ' );
+ wordCountPlugin._refreshStats();
+ expect( wordCountPlugin.words ).to.equal( 1 );
+ } );
+
+ it( 'should ignore dots in words', () => {
+ setModelData( model, 'Foo.bar ' );
+ wordCountPlugin._refreshStats();
+ expect( wordCountPlugin.words ).to.equal( 1 );
+ } );
+
+ it( 'should count words in links', () => {
+ setModelData( model, '<$text linkHref="http://www.cksource.com">CK Source$text> ' );
+ wordCountPlugin._refreshStats();
+ expect( wordCountPlugin.words ).to.equal( 2 );
+ } );
+
+ it( 'should not count the string with no letters or numbers as a word', () => {
+ setModelData( model, '(@#$%^*()) . ??? @ --- ... ' );
+ wordCountPlugin._refreshStats();
+ expect( wordCountPlugin.words ).to.equal( 0 );
+ } );
+
+ it( 'should not count the list item number/bullet as a word', () => {
+ setModelData( model, 'Foo ' +
+ 'bar ' );
+
+ wordCountPlugin._refreshStats();
+ expect( wordCountPlugin.words ).to.equal( 2 );
+ } );
+
+ it( 'should count words in the image caption', () => {
+ setModelData( model,
+ '' +
+ 'Foo Bar ' +
+ ' '
+ );
+
+ wordCountPlugin._refreshStats();
+ expect( wordCountPlugin.words ).to.equal( 2 );
+ } );
+
+ it( 'should count words in the table', () => {
+ setModelData( model,
+ '' +
+ '' +
+ 'Foo ' +
+ 'Foo ' +
+ 'Foo ' +
+ ' ' +
+ '' +
+ 'Foo ' +
+ 'Foo ' +
+ 'Foo ' +
+ ' ' +
+ '
'
+ );
+
+ wordCountPlugin._refreshStats();
+ expect( wordCountPlugin.words ).to.equal( 6 );
+ } );
+
+ it( 'should separate words with the end of the paragraph', () => {
+ setModelData( model, 'Foo ' +
+ 'Bar ' );
+
+ wordCountPlugin._refreshStats();
+ expect( wordCountPlugin.words ).to.equal( 2 );
+ } );
+
+ it( 'should separate words with the new line character', () => {
+ setModelData( model, 'Foo\nBar ' );
- expect( wordCountPlugin.words ).to.equal( 6 );
+ wordCountPlugin._refreshStats();
+ expect( wordCountPlugin.words ).to.equal( 2 );
+ } );
+
+ it( 'should separate words with the soft break', () => {
+ setModelData( model, 'Foo Bar ' );
+
+ wordCountPlugin._refreshStats();
+ expect( wordCountPlugin.words ).to.equal( 2 );
+ } );
+
+ it( 'should not separate words with the special characters', () => {
+ setModelData( model, 'F!o@o-B#a$r%F^o*B(a)r_F-o+o=B£a§r`F~o,B,a.F/o?o;B:a\'r"F\\o|oB{ar} ' );
+
+ wordCountPlugin._refreshStats();
+ expect( wordCountPlugin.words ).to.equal( 1 );
+ } );
+
+ it( 'should count international words', function() {
+ if ( !env.features.isRegExpUnicodePropertySupported ) {
+ this.skip();
+ }
+
+ setModelData( model, 'שמש 太陽 ดวงอาทิตย์ شمس ਸੂਰਜ słońce ' );
+ wordCountPlugin._refreshStats();
+
+ expect( wordCountPlugin.words ).to.equal( 6 );
+ } );
+
+ describe( 'ES2018 RegExp Unicode property fallback', () => {
+ const originalPropertiesSupport = env.features.isRegExpUnicodePropertySupported;
+
+ before( () => {
+ env.features.isRegExpUnicodePropertySupported = false;
+ } );
+
+ after( () => {
+ env.features.isRegExpUnicodePropertySupported = originalPropertiesSupport;
+ } );
+
+ it( 'should use different regexp when unicode properties are not supported', () => {
+ expect( wordCountPlugin.words ).to.equal( 0 );
+
+ setModelData( model, 'hello world. ' );
+ wordCountPlugin._refreshStats();
+
+ expect( wordCountPlugin.words ).to.equal( 2 );
+ } );
+ } );
} );
it( 'counts characters', () => {
@@ -162,40 +301,6 @@ describe( 'WordCount', () => {
expect( wordCountPlugin.characters ).to.equal( 9 );
} );
- it( 'should count international words', function() {
- if ( !env.features.isRegExpUnicodePropertySupported ) {
- this.skip();
- }
-
- expect( wordCountPlugin.words ).to.equal( 0 );
-
- setModelData( model, 'שמש 太陽 ดวงอาทิตย์ شمس ਸੂਰਜ słońce ' );
- wordCountPlugin._refreshStats();
-
- expect( wordCountPlugin.words ).to.equal( 6 );
- } );
-
- describe( 'ES2018 RegExp Unicode property fallback', () => {
- const originalPropertiesSupport = env.features.isRegExpUnicodePropertySupported;
-
- before( () => {
- env.features.isRegExpUnicodePropertySupported = false;
- } );
-
- after( () => {
- env.features.isRegExpUnicodePropertySupported = originalPropertiesSupport;
- } );
-
- it( 'should use different regexp when unicode properties are not supported', () => {
- expect( wordCountPlugin.words ).to.equal( 0 );
-
- setModelData( model, 'hello world. ' );
- wordCountPlugin._refreshStats();
-
- expect( wordCountPlugin.words ).to.equal( 2 );
- } );
- } );
-
describe( '#update event', () => {
it( 'fires with the actual number of characters and words', () => {
const fake = sinon.fake();
@@ -256,12 +361,12 @@ describe( 'WordCount', () => {
it( 'updates container content', () => {
expect( container.innerText ).to.equal( 'Words: 0Characters: 0' );
- setModelData( model, 'Foo(bar)baz ' +
+ setModelData( model, 'Foo bar ' +
'<$text foo="true">Hello$text> world. ' );
wordCountPlugin._refreshStats();
- expect( container.innerText ).to.equal( 'Words: 5Characters: 23' );
+ expect( container.innerText ).to.equal( 'Words: 4Characters: 19' );
} );
it( 'subsequent calls provides the same element', () => {
From 95a6cd6a9833de39944cfe0a543f8b25a5a6855b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Maksymilian=20Barna=C5=9B?=
Date: Fri, 8 Jan 2021 11:34:09 +0100
Subject: [PATCH 33/40] Update tests.
---
.../tableproperties/tablepropertiesediting.js | 103 ++++++++++++------
1 file changed, 72 insertions(+), 31 deletions(-)
diff --git a/packages/ckeditor5-table/tests/tableproperties/tablepropertiesediting.js b/packages/ckeditor5-table/tests/tableproperties/tablepropertiesediting.js
index b22a4983480..6bc8cf886a1 100644
--- a/packages/ckeditor5-table/tests/tableproperties/tablepropertiesediting.js
+++ b/packages/ckeditor5-table/tests/tableproperties/tablepropertiesediting.js
@@ -211,9 +211,10 @@ describe( 'table properties', () => {
assertTRBLAttribute( table, 'borderWidth', null, null, null, '1px' );
} );
- // https://github.com/ckeditor/ckeditor5/issues/6177.
- it( 'should upcast tables with nested tables in their cells', () => {
- editor.setData( '' +
+ describe( 'nested tables', () => {
+ // https://github.com/ckeditor/ckeditor5/issues/6177.
+ it( 'should upcast tables with nested tables in their cells', () => {
+ editor.setData( '' +
'' +
'parent:00 ' +
'' +
@@ -222,16 +223,16 @@ describe( 'table properties', () => {
' ' +
'
' );
- const table = model.document.getRoot().getNodeByPath( [ 0 ] );
+ const table = model.document.getRoot().getNodeByPath( [ 0 ] );
- assertTRBLAttribute( table, 'borderColor', 'red' );
- assertTRBLAttribute( table, 'borderStyle', 'solid' );
- assertTRBLAttribute( table, 'borderWidth', '1px' );
+ assertTRBLAttribute( table, 'borderColor', 'red' );
+ assertTRBLAttribute( table, 'borderStyle', 'solid' );
+ assertTRBLAttribute( table, 'borderWidth', '1px' );
- // Also check the entire structure of the model.
- // Previously the test was too loose in that regard.
- expect( getModelData( editor.model ) ).to.equal(
- '[' +
@@ -248,14 +249,14 @@ describe( 'table properties', () => {
'' +
'' +
'
]'
- );
- } );
+ );
+ } );
- // https://github.com/ckeditor/ckeditor5/issues/8393.
- it( 'should not throw error when loading nested table with border styles', () => {
- expect( () => {
- editor.setData(
- '' +
+ // https://github.com/ckeditor/ckeditor5/issues/8393.
+ it( 'should not throw error - inner cell with border style', () => {
+ expect( () => {
+ editor.setData(
+ '' +
'' +
'' +
' ' +
@@ -270,11 +271,24 @@ describe( 'table properties', () => {
' ' +
' ' +
'
' );
- } ).not.to.throw();
+ } ).not.to.throw();
+
+ expect( getModelData( editor.model ) ).to.equal(
+ '[' +
+ '' +
+ '' +
+ ' ' +
+ ' ' +
+ ' ' +
+ '
]'
+ );
+ } );
- expect( () => {
- editor.setData(
- '' +
+ // https://github.com/ckeditor/ckeditor5/issues/8393.
+ it( 'should not throw error - inner empty table with border style', () => {
+ expect( () => {
+ editor.setData(
+ '' +
'' +
'' +
' ' +
@@ -283,14 +297,27 @@ describe( 'table properties', () => {
' ' +
' ' +
'
' );
- } ).not.to.throw();
-
- // Conversion will create a merged text node out of all the text contents,
- // including the one in elements not allowed by schema in this scope.
- // Let's make sure that upcasting will not try to use model that got processed this way.
- expect( () => {
- editor.setData(
- '' +
+ } ).not.to.throw();
+
+ expect( getModelData( editor.model ) ).to.equal(
+ '[' +
+ '' +
+ '' +
+ ' ' +
+ ' ' +
+ ' ' +
+ '
]'
+ );
+ } );
+
+ // https://github.com/ckeditor/ckeditor5/issues/8393.
+ it( 'should not throw error - no tables allowed in an element', () => {
+ // Conversion will create a merged text node out of all the text contents,
+ // including the one in elements not allowed by schema in this scope.
+ // Let's make sure that upcasting will not try to use model that got processed this way.
+ expect( () => {
+ editor.setData(
+ '' +
' ' +
'' +
'' +
@@ -307,7 +334,21 @@ describe( 'table properties', () => {
'
' +
' ' +
' ' );
- } ).not.to.throw();
+ } ).not.to.throw();
+
+ expect( getModelData( editor.model ) ).to.equal(
+ '[' +
+ '' +
+ '' +
+ 'parent:00 ' +
+ ' ' +
+ '' +
+ 'child:00 ' +
+ ' ' +
+ ' ' +
+ '
]'
+ );
+ } );
} );
} );
From c478e3b3a9fb655f50ee82fa3daf628b419a002f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Magdalena=20Chrze=C5=9Bcian?=
Date: Fri, 8 Jan 2021 11:34:24 +0100
Subject: [PATCH 34/40] Dev dependency added to the plugin package.
---
packages/ckeditor5-word-count/package.json | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/packages/ckeditor5-word-count/package.json b/packages/ckeditor5-word-count/package.json
index 2f1c0ed982d..ba9f06630a0 100644
--- a/packages/ckeditor5-word-count/package.json
+++ b/packages/ckeditor5-word-count/package.json
@@ -24,7 +24,8 @@
"@ckeditor/ckeditor5-list": "^24.0.0",
"@ckeditor/ckeditor5-paragraph": "^24.0.0",
"@ckeditor/ckeditor5-table": "^24.0.0",
- "@ckeditor/ckeditor5-utils": "^24.0.0"
+ "@ckeditor/ckeditor5-utils": "^24.0.0",
+ "@ckeditor/ckeditor5-image": "^24.0.0"
},
"engines": {
"node": ">=12.0.0",
From 0d785a00106ed5b82e13c4f02afe28da5bd8a49c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Maksymilian=20Barna=C5=9B?=
Date: Fri, 8 Jan 2021 11:36:20 +0100
Subject: [PATCH 35/40] Fix indentation in tests.
---
.../tableproperties/tablepropertiesediting.js | 129 +++++++++---------
1 file changed, 67 insertions(+), 62 deletions(-)
diff --git a/packages/ckeditor5-table/tests/tableproperties/tablepropertiesediting.js b/packages/ckeditor5-table/tests/tableproperties/tablepropertiesediting.js
index 6bc8cf886a1..58a7e623239 100644
--- a/packages/ckeditor5-table/tests/tableproperties/tablepropertiesediting.js
+++ b/packages/ckeditor5-table/tests/tableproperties/tablepropertiesediting.js
@@ -214,14 +214,16 @@ describe( 'table properties', () => {
describe( 'nested tables', () => {
// https://github.com/ckeditor/ckeditor5/issues/6177.
it( 'should upcast tables with nested tables in their cells', () => {
- editor.setData( '' +
- '' +
- 'parent:00 ' +
- '' +
- '' +
- ' ' +
- ' ' +
- '
' );
+ editor.setData(
+ '' +
+ '' +
+ 'parent:00 ' +
+ '' +
+ '' +
+ ' ' +
+ ' ' +
+ '
'
+ );
const table = model.document.getRoot().getNodeByPath( [ 0 ] );
@@ -233,22 +235,22 @@ describe( 'table properties', () => {
// Previously the test was too loose in that regard.
expect( getModelData( editor.model ) ).to.equal(
'[' +
- '' +
- '' +
- '' +
- 'parent:00' +
- ' ' +
- ' ' +
- '' +
- '' +
- 'child:00' +
- ' ' +
- ' ' +
- ' ' +
- '
]'
+ 'borderColor="{"top":"red","bottom":"red","right":"red","left":"red"}" ' +
+ 'borderStyle="{"top":"solid","bottom":"solid","right":"solid","left":"solid"}" ' +
+ 'borderWidth="{"top":"1px","bottom":"1px","right":"1px","left":"1px"}">' +
+ '' +
+ '' +
+ '' +
+ 'parent:00' +
+ ' ' +
+ ' ' +
+ '' +
+ '' +
+ 'child:00' +
+ ' ' +
+ ' ' +
+ ' ' +
+ '
]'
);
} );
@@ -257,20 +259,21 @@ describe( 'table properties', () => {
expect( () => {
editor.setData(
'' +
- '' +
- '' +
- ' ' +
- '' +
- '' +
- '' +
- ' ' +
- ' ' +
- ' ' +
- '
' +
- ' ' +
- ' ' +
- ' ' +
- '
' );
+ '' +
+ '' +
+ ' ' +
+ '' +
+ '' +
+ '' +
+ ' ' +
+ ' ' +
+ ' ' +
+ '
' +
+ ' ' +
+ ' ' +
+ ' ' +
+ '
'
+ );
} ).not.to.throw();
expect( getModelData( editor.model ) ).to.equal(
@@ -289,14 +292,15 @@ describe( 'table properties', () => {
expect( () => {
editor.setData(
'' +
- '' +
- '' +
- ' ' +
- '' +
- ' ' +
- ' ' +
- ' ' +
- '
' );
+ '' +
+ '' +
+ ' ' +
+ '' +
+ ' ' +
+ ' ' +
+ ' ' +
+ '
'
+ );
} ).not.to.throw();
expect( getModelData( editor.model ) ).to.equal(
@@ -318,22 +322,23 @@ describe( 'table properties', () => {
expect( () => {
editor.setData(
'' +
- ' ' +
- '' +
- '' +
- '' +
- 'parent:00 ' +
- '' +
- '' +
- '' +
- 'child:00 ' +
- ' ' +
- '
' +
- ' ' +
- ' ' +
- '
' +
- ' ' +
- ' ' );
+ ' ' +
+ '' +
+ '' +
+ '' +
+ 'parent:00 ' +
+ '' +
+ '' +
+ '' +
+ 'child:00 ' +
+ ' ' +
+ '
' +
+ ' ' +
+ ' ' +
+ '
' +
+ ' ' +
+ ''
+ );
} ).not.to.throw();
expect( getModelData( editor.model ) ).to.equal(
From 38300bd3a18eb893ffbe2400cac1506bcb9f1424 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Magdalena=20Chrze=C5=9Bcian?=
Date: Fri, 8 Jan 2021 11:45:53 +0100
Subject: [PATCH 36/40] Updated the manual test.
---
packages/ckeditor5-word-count/tests/manual/wordcount.md | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/packages/ckeditor5-word-count/tests/manual/wordcount.md b/packages/ckeditor5-word-count/tests/manual/wordcount.md
index 7ca1ab0fdb5..3a56b0776cd 100644
--- a/packages/ckeditor5-word-count/tests/manual/wordcount.md
+++ b/packages/ckeditor5-word-count/tests/manual/wordcount.md
@@ -1,7 +1,7 @@
1. Try to type in the editor. The container below should be automatically updated with the current amount of words and characters.
-2. Special characters are treated as separators for words. For example
+2. Special characters are not treated as separators for words. For example
* `Hello world` - 2 words
- * `Hello(World)` - 2 words
+ * `Hello(World)` - 1 word
* `Hello\nWorld` - 2 words
3. Numbers are treated as words.
4. There are logged values of `WordCount:event-update` in the console. Values should change in same way as container in html.
From 4dcd89be34b5e45828eb87e782137372f5ccfdf6 Mon Sep 17 00:00:00 2001
From: Kamil Piechaczek
Date: Mon, 11 Jan 2021 13:20:41 +0100
Subject: [PATCH 37/40] Internal: Updated versions of dev tools.
---
package.json | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/package.json b/package.json
index e970fd715fb..d90cb7def3e 100644
--- a/package.json
+++ b/package.json
@@ -78,11 +78,11 @@
},
"devDependencies": {
"@ckeditor/ckeditor5-comments": ">=24.0.0",
- "@ckeditor/ckeditor5-dev-docs": "^23.2.0",
- "@ckeditor/ckeditor5-dev-env": "^23.2.0",
+ "@ckeditor/ckeditor5-dev-docs": "^24.0.0",
+ "@ckeditor/ckeditor5-dev-env": "^24.0.0",
"@ckeditor/ckeditor5-dev-tests": "^23.3.0",
- "@ckeditor/ckeditor5-dev-utils": "^23.2.0",
- "@ckeditor/ckeditor5-dev-webpack-plugin": "^23.2.0",
+ "@ckeditor/ckeditor5-dev-utils": "^24.0.0",
+ "@ckeditor/ckeditor5-dev-webpack-plugin": "^24.0.0",
"@ckeditor/ckeditor5-export-pdf": ">=1.0.0",
"@ckeditor/ckeditor5-export-word": ">=1.0.0",
"@ckeditor/ckeditor5-inspector": "^2.2.1",
From f3b7489b1122eee1dc54780ed49e00f4575d10ca Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Magdalena=20Chrze=C5=9Bcian?=
Date: Mon, 11 Jan 2021 15:34:38 +0100
Subject: [PATCH 38/40] Widget resize visual styles moved to the theme package.
---
.../theme/ckeditor5-widget/widgetresize.css | 36 +++++++++++++++++++
.../ckeditor5-widget/theme/widgetresize.css | 26 ++------------
2 files changed, 38 insertions(+), 24 deletions(-)
create mode 100644 packages/ckeditor5-theme-lark/theme/ckeditor5-widget/widgetresize.css
diff --git a/packages/ckeditor5-theme-lark/theme/ckeditor5-widget/widgetresize.css b/packages/ckeditor5-theme-lark/theme/ckeditor5-widget/widgetresize.css
new file mode 100644
index 00000000000..ee16fb28748
--- /dev/null
+++ b/packages/ckeditor5-theme-lark/theme/ckeditor5-widget/widgetresize.css
@@ -0,0 +1,36 @@
+/*
+ * Copyright (c) 2003-2020, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
+ */
+
+.ck .ck-widget__resizer {
+ outline: 1px solid var(--ck-color-resizer);
+}
+
+.ck .ck-widget__resizer__handle {
+ width: var(--ck-resizer-size);
+ height: var(--ck-resizer-size);
+ background: var(--ck-color-focus-border);
+ border: var(--ck-resizer-border-width) solid hsl(0, 0%, 100%);
+ border-radius: var(--ck-resizer-border-radius);
+
+ &.ck-widget__resizer__handle-top-left {
+ top: var( --ck-resizer-offset );
+ left: var( --ck-resizer-offset );
+ }
+
+ &.ck-widget__resizer__handle-top-right {
+ top: var( --ck-resizer-offset );
+ right: var( --ck-resizer-offset );
+ }
+
+ &.ck-widget__resizer__handle-bottom-right {
+ bottom: var( --ck-resizer-offset );
+ right: var( --ck-resizer-offset );
+ }
+
+ &.ck-widget__resizer__handle-bottom-left {
+ bottom: var( --ck-resizer-offset );
+ left: var( --ck-resizer-offset );
+ }
+}
diff --git a/packages/ckeditor5-widget/theme/widgetresize.css b/packages/ckeditor5-widget/theme/widgetresize.css
index 1439c1ea241..8ac149e4576 100644
--- a/packages/ckeditor5-widget/theme/widgetresize.css
+++ b/packages/ckeditor5-widget/theme/widgetresize.css
@@ -17,8 +17,6 @@
left: 0;
top: 0;
-
- outline: 1px solid var(--ck-color-resizer);
}
.ck-focused .ck-widget_with-resizer.ck-widget_selected {
@@ -33,33 +31,13 @@
/* Resizers are the only UI elements that should interfere with a pointer device. */
pointer-events: all;
- width: var(--ck-resizer-size);
- height: var(--ck-resizer-size);
- background: var(--ck-color-focus-border);
- border: var(--ck-resizer-border-width) solid hsl(0, 0%, 100%);
- border-radius: var(--ck-resizer-border-radius);
-
- &.ck-widget__resizer__handle-top-left {
- top: var( --ck-resizer-offset );
- left: var( --ck-resizer-offset );
- cursor: nwse-resize;
- }
-
- &.ck-widget__resizer__handle-top-right {
- top: var( --ck-resizer-offset );
- right: var( --ck-resizer-offset );
- cursor: nesw-resize;
- }
-
+ &.ck-widget__resizer__handle-top-left,
&.ck-widget__resizer__handle-bottom-right {
- bottom: var( --ck-resizer-offset );
- right: var( --ck-resizer-offset );
cursor: nwse-resize;
}
+ &.ck-widget__resizer__handle-top-right,
&.ck-widget__resizer__handle-bottom-left {
- bottom: var( --ck-resizer-offset );
- left: var( --ck-resizer-offset );
cursor: nesw-resize;
}
}
From 47be46e1f403a45bbecab2ec9bf4db97b6f3a326 Mon Sep 17 00:00:00 2001
From: Aleksander Nowodzinski
Date: Tue, 12 Jan 2021 10:43:11 +0100
Subject: [PATCH 39/40] Code refactoring.
---
.../theme/ckeditor5-widget/widgetresize.css | 16 ++++++++--------
1 file changed, 8 insertions(+), 8 deletions(-)
diff --git a/packages/ckeditor5-theme-lark/theme/ckeditor5-widget/widgetresize.css b/packages/ckeditor5-theme-lark/theme/ckeditor5-widget/widgetresize.css
index ee16fb28748..91f25632e3f 100644
--- a/packages/ckeditor5-theme-lark/theme/ckeditor5-widget/widgetresize.css
+++ b/packages/ckeditor5-theme-lark/theme/ckeditor5-widget/widgetresize.css
@@ -15,22 +15,22 @@
border-radius: var(--ck-resizer-border-radius);
&.ck-widget__resizer__handle-top-left {
- top: var( --ck-resizer-offset );
- left: var( --ck-resizer-offset );
+ top: var(--ck-resizer-offset);
+ left: var(--ck-resizer-offset);
}
&.ck-widget__resizer__handle-top-right {
- top: var( --ck-resizer-offset );
- right: var( --ck-resizer-offset );
+ top: var(--ck-resizer-offset);
+ right: var(--ck-resizer-offset);
}
&.ck-widget__resizer__handle-bottom-right {
- bottom: var( --ck-resizer-offset );
- right: var( --ck-resizer-offset );
+ bottom: var(--ck-resizer-offset);
+ right: var(--ck-resizer-offset);
}
&.ck-widget__resizer__handle-bottom-left {
- bottom: var( --ck-resizer-offset );
- left: var( --ck-resizer-offset );
+ bottom: var(--ck-resizer-offset);
+ left: var(--ck-resizer-offset);
}
}
From 3a65937b8bd24b329305d60ca0f86d70fac518b0 Mon Sep 17 00:00:00 2001
From: Aleksander Nowodzinski
Date: Tue, 12 Jan 2021 10:50:52 +0100
Subject: [PATCH 40/40] Code refactoring.
---
.../theme/ckeditor5-widget/widgetresize.css | 8 ++++++
packages/ckeditor5-widget/theme/widget.css | 27 ++++++++-----------
2 files changed, 19 insertions(+), 16 deletions(-)
diff --git a/packages/ckeditor5-theme-lark/theme/ckeditor5-widget/widgetresize.css b/packages/ckeditor5-theme-lark/theme/ckeditor5-widget/widgetresize.css
index 91f25632e3f..c8914a72ff3 100644
--- a/packages/ckeditor5-theme-lark/theme/ckeditor5-widget/widgetresize.css
+++ b/packages/ckeditor5-theme-lark/theme/ckeditor5-widget/widgetresize.css
@@ -3,6 +3,14 @@
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
+:root {
+ --ck-resizer-size: 10px;
+
+ /* Set the resizer with a 50% offset. */
+ --ck-resizer-offset: calc( ( var(--ck-resizer-size) / -2 ) - 2px);
+ --ck-resizer-border-width: 1px;
+}
+
.ck .ck-widget__resizer {
outline: 1px solid var(--ck-color-resizer);
}
diff --git a/packages/ckeditor5-widget/theme/widget.css b/packages/ckeditor5-widget/theme/widget.css
index d79e092a6ab..1ea8c98088d 100644
--- a/packages/ckeditor5-widget/theme/widget.css
+++ b/packages/ckeditor5-widget/theme/widget.css
@@ -5,16 +5,11 @@
:root {
--ck-color-resizer: var(--ck-color-focus-border);
- --ck-resizer-size: 10px;
- --ck-resizer-border-width: 1px;
- --ck-resizer-border-radius: 2px;
-
- /* Set the resizer with a 50% offset. */
- --ck-resizer-offset: calc( ( var(--ck-resizer-size) / -2 ) - 2px);
-
- --ck-resizer-tooltip-offset: 10px;
--ck-color-resizer-tooltip-background: hsl(0, 0%, 15%);
--ck-color-resizer-tooltip-text: hsl(0, 0%, 95%);
+
+ --ck-resizer-border-radius: var(--ck-border-radius);
+ --ck-resizer-tooltip-offset: 10px;
}
.ck .ck-widget {
@@ -66,22 +61,22 @@
}
&.ck-orientation-top-left {
- top: var( --ck-resizer-tooltip-offset );
- left: var( --ck-resizer-tooltip-offset );
+ top: var(--ck-resizer-tooltip-offset);
+ left: var(--ck-resizer-tooltip-offset);
}
&.ck-orientation-top-right {
- top: var( --ck-resizer-tooltip-offset );
- right: var( --ck-resizer-tooltip-offset );
+ top: var(--ck-resizer-tooltip-offset);
+ right: var(--ck-resizer-tooltip-offset);
}
&.ck-orientation-bottom-right {
- bottom: var( --ck-resizer-tooltip-offset );
- right: var( --ck-resizer-tooltip-offset );
+ bottom: var(--ck-resizer-tooltip-offset);
+ right: var(--ck-resizer-tooltip-offset);
}
&.ck-orientation-bottom-left {
- bottom: var( --ck-resizer-tooltip-offset );
- left: var( --ck-resizer-tooltip-offset );
+ bottom: var(--ck-resizer-tooltip-offset);
+ left: var(--ck-resizer-tooltip-offset);
}
}