From 7fbb8388213483d07bc2ed13ebb46b94b1113978 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotr=20Szcz=C4=99=C5=9Bniak?= Date: Thu, 27 Apr 2023 13:46:34 +0200 Subject: [PATCH 01/42] Licence parsing and comparing. --- packages/ckeditor5-utils/src/madewith.ts | 71 ++++++++++++++++++++++ packages/ckeditor5-utils/tests/madewith.js | 69 +++++++++++++++++++++ 2 files changed, 140 insertions(+) create mode 100644 packages/ckeditor5-utils/src/madewith.ts create mode 100644 packages/ckeditor5-utils/tests/madewith.js diff --git a/packages/ckeditor5-utils/src/madewith.ts b/packages/ckeditor5-utils/src/madewith.ts new file mode 100644 index 00000000000..8769431c555 --- /dev/null +++ b/packages/ckeditor5-utils/src/madewith.ts @@ -0,0 +1,71 @@ +/** + * @license Copyright (c) 2003-2023, CKSource Holding sp. z o.o. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ + +/** + * @module utils/madewith + */ + +/** + * Possible states of the key after verification + */ +export type VerifiedKeyStatus = 'VALID' | 'INVALID' | 'EXPIRED'; + +/** + * TODO + */ +export default function verify( stringToCheck: string ): VerifiedKeyStatus { + // mocked last release date + const currentReleaseDate = new Date(); + + let decryptedData = ''; + let decryptedSecondElement = ''; + + try { + decryptedData = atob( stringToCheck ); + } catch ( e ) { + return 'INVALID'; + } + + const splittedDecryptedData = decryptedData.split( '-' ); + + const firstElement = splittedDecryptedData[ 0 ]; + const secondElement = splittedDecryptedData[ 1 ]; + + if ( !secondElement ) { + const isFirstElementMatchingThePattern = firstElement.match( /^[a-zA-Z0-9+/=$]+$/g ); + + if ( isFirstElementMatchingThePattern && ( firstElement.length >= 40 && firstElement.length <= 255 ) ) { + return 'VALID'; + } else { + return 'INVALID'; + } + } + + try { + decryptedSecondElement = atob( secondElement ); + } catch ( e ) { + return 'INVALID'; + } + + if ( decryptedSecondElement.length !== 8 ) { + return 'INVALID'; + } + + // date will be compared to date of the release that will be handled like version now + const day = decryptedSecondElement.substring( 0, 2 ); + const month = decryptedSecondElement.substring( 2, 4 ); + const year = decryptedSecondElement.substring( 4, 8 ); + const date = new Date( `${ year }-${ month }-${ day }` ); + + if ( !isFinite( Number( date ) ) ) { + return 'INVALID'; + } + + if ( date < currentReleaseDate ) { + return 'EXPIRED'; + } + + return 'VALID'; +} diff --git a/packages/ckeditor5-utils/tests/madewith.js b/packages/ckeditor5-utils/tests/madewith.js new file mode 100644 index 00000000000..2d4b91272c0 --- /dev/null +++ b/packages/ckeditor5-utils/tests/madewith.js @@ -0,0 +1,69 @@ +/** + * @license Copyright (c) 2003-2023, CKSource Holding sp. z o.o. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ + +import verify from '../src/madewith'; + +describe( 'utils', () => { + describe( 'verify', () => { + describe( 'should return `VALID`', () => { + it( 'when date is later than the release date', () => { + // future + const string = 'ZEhCMGNuVmtiMjlsYlcxdFpXOXNjbUZwYkdsemN3PT0tTURZeE1qSXdORFE9'; + + expect( verify( string ) ).to.be.equal( 'VALID' ); + } ); + + it( 'when old licence key is valid', () => { + // eslint-disable-next-line max-len + const string = 'bWxyZWx6emlkcmJicmZ1YnNhaW9hYWVhbWFzb29vb2ZtdHJvdG9wYmFlYnRtb3VzbXJhb2Z6b3BsdGZhYW9lYm9zb3Jpcm9sYmltYnpyYWQ='; + + expect( verify( string ) ).to.be.equal( 'VALID' ); + } ); + } ); + + describe( 'should return `INVALID`', () => { + it( 'when passed variable is invalid', () => { + // invalid + const string = 'foobarbaz'; + + expect( verify( string ) ).to.be.equal( 'INVALID' ); + } ); + it( 'when date is invalid', () => { + // invalid = shorten than expected + const string = 'b2RydHNvbWlzdW1ybGxlb3RlYXBtaS1NVEV4T1RrMw=='; + + expect( verify( string ) ).to.be.equal( 'INVALID' ); + } ); + + it( 'when date is missing', () => { + const string = 'bXVtZWxlb21wb3Jpcml0bHNvZHRzYQ=='; + + expect( verify( string ) ).to.be.equal( 'INVALID' ); + } ); + + it( 'when unable to decode', () => { + const string = 'dW1vbW9ldG1pcnNybG9wbGRzYWl0ZS0j'; + + expect( verify( string ) ).to.be.equal( 'INVALID' ); + } ); + + it( 'when wrong string passed', () => { + // # + const string = 'dGVtb2l0aXNsbW9vc2FyZXBkbG1ydS1abTl2WW1GeVltRT0='; + + expect( verify( string ) ).to.be.equal( 'INVALID' ); + } ); + } ); + + describe( 'should return `EXPIRED`', () => { + it( 'when date is earlier than the release date', () => { + // past + const string = 'dXRyZWlwbHJvb21kbW9zbWVsc2F0aS1NREV3TVRFNU9EQT0='; + + expect( verify( string ) ).to.be.equal( 'EXPIRED' ); + } ); + } ); + } ); +} ); From edf021445986d7de64dc539eccf052208e97c29d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotr=20Szcz=C4=99=C5=9Bniak?= Date: Thu, 27 Apr 2023 14:18:12 +0200 Subject: [PATCH 02/42] Code improvements. --- packages/ckeditor5-utils/src/madewith.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/ckeditor5-utils/src/madewith.ts b/packages/ckeditor5-utils/src/madewith.ts index 8769431c555..4cf730469ca 100644 --- a/packages/ckeditor5-utils/src/madewith.ts +++ b/packages/ckeditor5-utils/src/madewith.ts @@ -36,7 +36,7 @@ export default function verify( stringToCheck: string ): VerifiedKeyStatus { if ( !secondElement ) { const isFirstElementMatchingThePattern = firstElement.match( /^[a-zA-Z0-9+/=$]+$/g ); - if ( isFirstElementMatchingThePattern && ( firstElement.length >= 40 && firstElement.length <= 255 ) ) { + if ( isFirstElementMatchingThePattern && ( firstElement.length >= 0x28 && firstElement.length <= 0xff ) ) { return 'VALID'; } else { return 'INVALID'; @@ -49,7 +49,7 @@ export default function verify( stringToCheck: string ): VerifiedKeyStatus { return 'INVALID'; } - if ( decryptedSecondElement.length !== 8 ) { + if ( decryptedSecondElement.length !== 0x8 ) { return 'INVALID'; } From d6045d515ed74fc07a3a570f09fbf795c0a5fa1c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotr=20Szcz=C4=99=C5=9Bniak?= Date: Tue, 2 May 2023 14:08:51 +0200 Subject: [PATCH 03/42] API docs and the comment. --- packages/ckeditor5-utils/src/madewith.ts | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/packages/ckeditor5-utils/src/madewith.ts b/packages/ckeditor5-utils/src/madewith.ts index 4cf730469ca..ea6072260ac 100644 --- a/packages/ckeditor5-utils/src/madewith.ts +++ b/packages/ckeditor5-utils/src/madewith.ts @@ -13,9 +13,16 @@ export type VerifiedKeyStatus = 'VALID' | 'INVALID' | 'EXPIRED'; /** - * TODO + * Checks whether the given string contains information that allows you to verify the license status. + * + * @param stringToCheck The string to check. + * @returns String that represents the state of given `stringToCheck` parameter. It can be `'VALID'`, `'INVALID'` or `'EXPIRED'`. */ export default function verify( stringToCheck: string ): VerifiedKeyStatus { + // This is just a very simplified preliminary front-end check of the date validation with the current release date - it + // allows to check whether a CKEditor logo/link will be shown or not (for recognize the editor and to show awesome + // features that it can bring with customized, licensed product). + // mocked last release date const currentReleaseDate = new Date(); @@ -53,7 +60,6 @@ export default function verify( stringToCheck: string ): VerifiedKeyStatus { return 'INVALID'; } - // date will be compared to date of the release that will be handled like version now const day = decryptedSecondElement.substring( 0, 2 ); const month = decryptedSecondElement.substring( 2, 4 ); const year = decryptedSecondElement.substring( 4, 8 ); From 9dd63c03774ceeda9e2d23c57af4477b4067e4b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotr=20Szcz=C4=99=C5=9Bniak?= Date: Mon, 8 May 2023 09:49:15 +0200 Subject: [PATCH 04/42] Update packages/ckeditor5-utils/tests/madewith.js MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Piotrek Koszuliński --- packages/ckeditor5-utils/tests/madewith.js | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/ckeditor5-utils/tests/madewith.js b/packages/ckeditor5-utils/tests/madewith.js index 2d4b91272c0..5605829dd38 100644 --- a/packages/ckeditor5-utils/tests/madewith.js +++ b/packages/ckeditor5-utils/tests/madewith.js @@ -30,6 +30,7 @@ describe( 'utils', () => { expect( verify( string ) ).to.be.equal( 'INVALID' ); } ); + it( 'when date is invalid', () => { // invalid = shorten than expected const string = 'b2RydHNvbWlzdW1ybGxlb3RlYXBtaS1NVEV4T1RrMw=='; From c56ab242efa563dd6dae9cff758c1cad6a8136ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotr=20Szcz=C4=99=C5=9Bniak?= Date: Mon, 8 May 2023 09:49:23 +0200 Subject: [PATCH 05/42] Update packages/ckeditor5-utils/src/madewith.ts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Piotrek Koszuliński --- packages/ckeditor5-utils/src/madewith.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ckeditor5-utils/src/madewith.ts b/packages/ckeditor5-utils/src/madewith.ts index ea6072260ac..41f2e9cea1a 100644 --- a/packages/ckeditor5-utils/src/madewith.ts +++ b/packages/ckeditor5-utils/src/madewith.ts @@ -8,7 +8,7 @@ */ /** - * Possible states of the key after verification + * Possible states of the key after verification. */ export type VerifiedKeyStatus = 'VALID' | 'INVALID' | 'EXPIRED'; From e9f4eb09c953b5f18a59c6c6a80508c056fa6821 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotr=20Szcz=C4=99=C5=9Bniak?= Date: Mon, 8 May 2023 11:26:36 +0200 Subject: [PATCH 06/42] Change a way of creating a date object. --- packages/ckeditor5-utils/src/madewith.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/ckeditor5-utils/src/madewith.ts b/packages/ckeditor5-utils/src/madewith.ts index 41f2e9cea1a..8dda203428a 100644 --- a/packages/ckeditor5-utils/src/madewith.ts +++ b/packages/ckeditor5-utils/src/madewith.ts @@ -60,10 +60,10 @@ export default function verify( stringToCheck: string ): VerifiedKeyStatus { return 'INVALID'; } - const day = decryptedSecondElement.substring( 0, 2 ); - const month = decryptedSecondElement.substring( 2, 4 ); - const year = decryptedSecondElement.substring( 4, 8 ); - const date = new Date( `${ year }-${ month }-${ day }` ); + const day = Number( decryptedSecondElement.substring( 0, 2 ) ); + const monthIndex = Number( decryptedSecondElement.substring( 2, 4 ) ) - 1; + const year = Number( decryptedSecondElement.substring( 4, 8 ) ); + const date = new Date( year, monthIndex, day ); if ( !isFinite( Number( date ) ) ) { return 'INVALID'; From a457ad2c24086cbeeaa0c1f27e0fab14d4bcac5d Mon Sep 17 00:00:00 2001 From: Aleksander Nowodzinski Date: Fri, 5 May 2023 13:58:39 +0200 Subject: [PATCH 07/42] Initial implementation of the powered by feature. --- packages/ckeditor5-powered-by/package.json | 49 ++++ .../ckeditor5-powered-by/src/augmentation.ts | 13 ++ packages/ckeditor5-powered-by/src/index.ts | 12 + .../ckeditor5-powered-by/src/poweredby.ts | 218 ++++++++++++++++++ .../tests/manual/poweredby.html | 98 ++++++++ .../tests/manual/poweredby.js | 60 +++++ .../tests/manual/poweredby.md | 0 .../theme/icons/ckeditor.svg | 20 ++ .../theme/icons/poweredby.svg | 21 ++ .../ckeditor5-powered-by/theme/poweredby.css | 94 ++++++++ packages/ckeditor5-powered-by/tsconfig.json | 10 + packages/ckeditor5-typing/package.json | 1 + packages/ckeditor5-typing/src/typing.ts | 5 +- packages/ckeditor5-utils/src/dom/rect.ts | 4 +- 14 files changed, 603 insertions(+), 2 deletions(-) create mode 100644 packages/ckeditor5-powered-by/package.json create mode 100644 packages/ckeditor5-powered-by/src/augmentation.ts create mode 100644 packages/ckeditor5-powered-by/src/index.ts create mode 100644 packages/ckeditor5-powered-by/src/poweredby.ts create mode 100644 packages/ckeditor5-powered-by/tests/manual/poweredby.html create mode 100644 packages/ckeditor5-powered-by/tests/manual/poweredby.js create mode 100644 packages/ckeditor5-powered-by/tests/manual/poweredby.md create mode 100644 packages/ckeditor5-powered-by/theme/icons/ckeditor.svg create mode 100644 packages/ckeditor5-powered-by/theme/icons/poweredby.svg create mode 100644 packages/ckeditor5-powered-by/theme/poweredby.css create mode 100644 packages/ckeditor5-powered-by/tsconfig.json diff --git a/packages/ckeditor5-powered-by/package.json b/packages/ckeditor5-powered-by/package.json new file mode 100644 index 00000000000..d830d71783b --- /dev/null +++ b/packages/ckeditor5-powered-by/package.json @@ -0,0 +1,49 @@ +{ + "name": "@ckeditor/ckeditor5-powered-by", + "version": "37.1.0", + "description": "Powered by feature for CKEditor 5.", + "keywords": [ + "ckeditor", + "ckeditor5", + "ckeditor 5", + "ckeditor5-feature", + "ckeditor5-plugin", + "ckeditor5-dll" + ], + "main": "src/index.ts", + "dependencies": { + "ckeditor5": "^37.1.0" + }, + "devDependencies": { + "@ckeditor/ckeditor5-core": "^37.1.0", + "@ckeditor/ckeditor5-editor-classic": "^37.1.0", + "typescript": "^4.8.4", + "webpack": "^5.58.1", + "webpack-cli": "^4.9.0" + }, + "engines": { + "node": ">=16.0.0", + "npm": ">=5.7.1" + }, + "author": "CKSource (http://cksource.com/)", + "license": "GPL-2.0-or-later", + "homepage": "https://ckeditor.com/ckeditor-5", + "bugs": "https://github.com/ckeditor/ckeditor5/issues", + "repository": { + "type": "git", + "url": "https://github.com/ckeditor/ckeditor5.git", + "directory": "packages/ckeditor5-powered-by" + }, + "files": [ + "lang", + "src/**/*.js", + "src/**/*.d.ts", + "theme", + "ckeditor5-metadata.json", + "CHANGELOG.md" + ], + "scripts": { + "build": "tsc -p ./tsconfig.json", + "postversion": "npm run build" + } +} diff --git a/packages/ckeditor5-powered-by/src/augmentation.ts b/packages/ckeditor5-powered-by/src/augmentation.ts new file mode 100644 index 00000000000..f072e2217a6 --- /dev/null +++ b/packages/ckeditor5-powered-by/src/augmentation.ts @@ -0,0 +1,13 @@ +/** + * @license Copyright (c) 2003-2023, CKSource Holding sp. z o.o. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ + +import type { PoweredBy } from './index'; + +declare module '@ckeditor/ckeditor5-core' { + interface PluginsMap { + [ PoweredBy.pluginName ]: PoweredBy; + } +} + diff --git a/packages/ckeditor5-powered-by/src/index.ts b/packages/ckeditor5-powered-by/src/index.ts new file mode 100644 index 00000000000..e3a957a92a1 --- /dev/null +++ b/packages/ckeditor5-powered-by/src/index.ts @@ -0,0 +1,12 @@ +/** + * @license Copyright (c) 2003-2023, CKSource Holding sp. z o.o. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ + +/** + * @module undo + */ + +export { default as PoweredBy } from './poweredby'; + +import './augmentation'; diff --git a/packages/ckeditor5-powered-by/src/poweredby.ts b/packages/ckeditor5-powered-by/src/poweredby.ts new file mode 100644 index 00000000000..fd315dfa3fe --- /dev/null +++ b/packages/ckeditor5-powered-by/src/poweredby.ts @@ -0,0 +1,218 @@ +/** + * @license Copyright (c) 2003-2023, CKSource Holding sp. z o.o. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ + +/** + * @module TODO + */ + +import { Plugin, type Editor } from 'ckeditor5/src/core'; +import { BalloonPanelView, IconView, View } from 'ckeditor5/src/ui'; +import type { PositionOptions, Rect } from 'ckeditor5/src/utils'; + +// import poweredByIcon from '../theme/icons/poweredby.svg'; +import poweredByIcon from '../theme/icons/ckeditor.svg'; +import '../theme/poweredby.css'; + +// const ICON_WIDTH = 112; +// const ICON_HEIGHT = 10; +const ICON_WIDTH = 52; +const ICON_HEIGHT = 10; +const OFF_THE_SCREEN_POSITION = { + top: -9999999, + left: -9999999, + name: 'invalid' +}; + +/** + * TODO + */ +export default class PoweredBy extends Plugin { + /** + * TODO + */ + declare private _poweredByView: View; + + /** + * @inheritDoc + */ + public static get pluginName(): 'PoweredBy' { + return 'PoweredBy'; + } + + /** + * TODO + */ + public init(): void { + this._poweredByView = this._createPoweredByView(); + + this.editor.on( 'ready', this._handleEditorReady.bind( this ) ); + } + + /** + * TODO + */ + private _handleEditorReady(): void { + const editor = this.editor; + const balloon = new BalloonPanelView(); + + balloon.content.add( this._poweredByView ); + balloon.withArrow = false; + balloon.class = 'ck-powered-by-balloon'; + + editor.ui.view.body.add( balloon ); + editor.ui.focusTracker.add( this._poweredByView.element! ); + + editor.ui.focusTracker.on( 'change:isFocused', ( evt, data, isFocused ) => { + if ( isFocused ) { + const attachOptions = getBalloonAttachOptions( editor ); + + if ( attachOptions ) { + balloon.pin( attachOptions ); + } + } else { + balloon.unpin(); + } + } ); + + // TODO: on image loaded + editor.ui.on( 'update', () => { + if ( editor.ui.focusTracker.isFocused ) { + const attachOptions = getBalloonAttachOptions( editor ); + + if ( attachOptions ) { + balloon.unpin(); + balloon.pin( attachOptions ); + } + } + } ); + + // TODO: ~~Support for cases where the watermark gets cropped by parent with overflow: hidden~~. + // TODO: Debounce. + // TODO: Probably hide during scroll. + } + + /** + * TODO + * + * @returns + */ + private _createPoweredByView(): View { + const poweredByView = new View(); + const iconView = new IconView(); + + iconView.content = poweredByIcon; + iconView.isColorInherited = false; + + iconView.extendTemplate( { + attributes: { + style: { + width: ICON_WIDTH + 'px', + height: ICON_HEIGHT + 'px' + } + } + } ); + + poweredByView.setTemplate( { + tag: 'div', + attributes: { + class: [ 'ck', 'ck-powered-by' ] + }, + children: [ + { + tag: 'a', + attributes: { + href: 'https://ckeditor.com', + target: '_blank', + tabindex: '-1' + }, + children: [ + { + tag: 'span', + attributes: { + class: [ 'ck', 'ck-powered-by__label' ] + }, + children: [ 'Powered by' ] + }, + iconView + ] + } + ] + } ); + + return poweredByView; + } +} + +function getBalloonAttachOptions( editor: Editor ): Partial | null { + const focusedDomRoot = getFocusedDOMRoot( editor ); + + if ( !focusedDomRoot ) { + return null; + } + + const positioningFunction = editor.locale.contentLanguageDirection === 'ltr' ? + getLowerRightCornerPosition( focusedDomRoot ) : + getLowerLeftCornerPosition( focusedDomRoot ); + + return { + target: focusedDomRoot, + // TODO: Make the side configurable. + positions: [ positioningFunction ] + }; +} + +function getLowerRightCornerPosition( focusedDomRoot: HTMLElement ) { + return getLowerCornerPosition( focusedDomRoot, ( rootRect, balloonRect ) => { + return rootRect.left + rootRect.width - balloonRect.width - 5; + } ); +} + +function getLowerLeftCornerPosition( focusedDomRoot: HTMLElement ) { + return getLowerCornerPosition( focusedDomRoot, rootRect => { + return rootRect.left + 5; + } ); +} + +function getLowerCornerPosition( + focusedDomRoot: HTMLElement, + getBalloonLeft: ( rootRect: Rect, balloonRect: Rect ) => number +) { + return ( rootRect: Rect, balloonRect: Rect ) => { + const visibleRootRect = rootRect.getVisible(); + + // Root cropped by ancestors. + if ( !visibleRootRect ) { + return OFF_THE_SCREEN_POSITION; + } + + const isRootNarrow = rootRect.width < 250; + const balloonTop = rootRect.bottom - balloonRect.height / 2; + const balloonLeft = getBalloonLeft( rootRect, balloonRect ); + const newBalloonRect = balloonRect.clone().moveTo( balloonLeft, balloonTop ); + + visibleRootRect.moveBy( 0, balloonRect.height / 2 ); + + // The watermark cannot be positioned in this corner because the corner is "not visible enough". + if ( newBalloonRect.getIntersectionArea( visibleRootRect ) < newBalloonRect.getArea() ) { + return OFF_THE_SCREEN_POSITION; + } + + return { + top: balloonTop, + left: balloonLeft, + name: isRootNarrow ? 'narrow' : 'default' + }; + }; +} + +function getFocusedDOMRoot( editor: Editor ) { + for ( const [ , domRoot ] of editor.editing.view.domRoots ) { + if ( domRoot.ownerDocument.activeElement === domRoot || domRoot.contains( domRoot.ownerDocument.activeElement ) ) { + return domRoot; + } + } + + return null; +} diff --git a/packages/ckeditor5-powered-by/tests/manual/poweredby.html b/packages/ckeditor5-powered-by/tests/manual/poweredby.html new file mode 100644 index 00000000000..970098cf5df --- /dev/null +++ b/packages/ckeditor5-powered-by/tests/manual/poweredby.html @@ -0,0 +1,98 @@ + + + + +

Normal content

+
+

The three greatest things you learn from traveling

+

Like all the great things on earth traveling teaches us by example. Here are some of the most precious lessons I’ve + learned over the years of traveling.

+ +
+

The real voyage of discovery consists not in seeking new landscapes, but having new eyes.

+

Marcel Proust

+
+

Improvisation

+

Life doesn't allow us to execute every single plan perfectly. This especially seems to be the case when you travel. + You plan it down to every minute with a big checklist. But when it comes to executing it, something always comes up + and you’re left with your improvising skills. You learn to adapt as you go. Here’s how my travel checklist looks + now:

+
    +
  • buy the ticket
  • +
  • start your adventure
  • +
+
Three monks ascending the stairs of an ancient temple. +
Three monks ascending the stairs of an ancient temple.
+
+

Confidence

+

Going to a new place can be quite terrifying. While change and uncertainty make us scared, traveling teaches us how + ridiculous it is to be afraid of something before it happens. The moment you face your fear and see there is nothing + to be afraid of, is the moment you discover bliss.

+
+ +

Single line content

+
+

The capital city of Malta is the top destination this summer. It’s home to cutting-edge contemporary architecture, baroqu

+
+ +

Different bgs

+
+

The three greatest things you learn from traveling

+

Like all the great things on earth traveling teaches us by example. Here are some of the most precious lessons I’ve + learned over the years of traveling.

+
+
+
+

The three greatest things you learn from traveling

+

Like all the great things on earth traveling teaches us by example. Here are some of the most precious lessons I’ve + learned over the years of traveling.

+
+ +

Narrow editor

+
+

Foo bar

+
+
+
+

Foo bar

+
+ +

Padding-less editor

+
+

Like all the great things on earth traveling teaches us by example. Here are some of the most precious lessons I’ve

+
diff --git a/packages/ckeditor5-powered-by/tests/manual/poweredby.js b/packages/ckeditor5-powered-by/tests/manual/poweredby.js new file mode 100644 index 00000000000..94af3724d62 --- /dev/null +++ b/packages/ckeditor5-powered-by/tests/manual/poweredby.js @@ -0,0 +1,60 @@ +/** + * @license Copyright (c) 2003-2023, CKSource Holding sp. z o.o. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ + +/* globals console, window, document */ + +import ClassicEditor from '@ckeditor/ckeditor5-editor-classic/src/classiceditor'; +import ArticlePluginSet from '@ckeditor/ckeditor5-core/tests/_utils/articlepluginset'; + +window.editors = {}; + +function createEditor( selector ) { + ClassicEditor + .create( document.querySelector( selector ), { + plugins: [ ArticlePluginSet ], + toolbar: [ + 'heading', + '|', + 'bold', + 'italic', + 'link', + 'bulletedList', + 'numberedList', + '|', + 'outdent', + 'indent', + '|', + 'blockQuote', + 'insertTable', + 'mediaEmbed', + 'undo', + 'redo' + ], + image: { + toolbar: [ 'imageStyle:inline', 'imageStyle:block', 'imageStyle:side', '|', 'imageTextAlternative' ] + }, + table: { + contentToolbar: [ + 'tableColumn', + 'tableRow', + 'mergeTableCells' + ] + } + } ) + .then( editor => { + window.editors[ selector ] = editor; + } ) + .catch( err => { + console.error( err.stack ); + } ); +} + +createEditor( '#normal' ); +createEditor( '#single-line' ); +createEditor( '#dark-bg' ); +createEditor( '#medium-bg' ); +createEditor( '#narrow' ); +createEditor( '#narrow-dark-bg' ); +createEditor( '#padding-less' ); diff --git a/packages/ckeditor5-powered-by/tests/manual/poweredby.md b/packages/ckeditor5-powered-by/tests/manual/poweredby.md new file mode 100644 index 00000000000..e69de29bb2d diff --git a/packages/ckeditor5-powered-by/theme/icons/ckeditor.svg b/packages/ckeditor5-powered-by/theme/icons/ckeditor.svg new file mode 100644 index 00000000000..56dd43b7efe --- /dev/null +++ b/packages/ckeditor5-powered-by/theme/icons/ckeditor.svg @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/packages/ckeditor5-powered-by/theme/icons/poweredby.svg b/packages/ckeditor5-powered-by/theme/icons/poweredby.svg new file mode 100644 index 00000000000..87148b533a8 --- /dev/null +++ b/packages/ckeditor5-powered-by/theme/icons/poweredby.svg @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/ckeditor5-powered-by/theme/poweredby.css b/packages/ckeditor5-powered-by/theme/poweredby.css new file mode 100644 index 00000000000..354fab255c0 --- /dev/null +++ b/packages/ckeditor5-powered-by/theme/poweredby.css @@ -0,0 +1,94 @@ +/* + * Copyright (c) 2003-2023, CKSource Holding sp. z o.o. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ + +:root { + --ck-powered-by-line-height: 10px; + --ck-powered-by-padding-top: 2px; + --ck-powered-by-padding-left: 4px; + --ck-powered-by-border-radius: 100px; + --ck-powered-by-bg: hsl(0, 0%, 100%); +} + +.ck.ck-balloon-panel.ck-powered-by-balloon { + border: 0; + border: var(--ck-focus-ring); + box-shadow: none; + border-radius: var(--ck-powered-by-border-radius); + background: var(--ck-powered-by-bg); + min-height: unset; + + &::after, + &::before { + display: none; + } + + & .ck.ck-powered-by { + line-height: var(--ck-powered-by-line-height); + + & a { + cursor: pointer; + display: flex; + align-items: center; + opacity: .66; + filter: grayscale(80%); + line-height: var(--ck-powered-by-line-height); + padding: var(--ck-powered-by-padding-top) var(--ck-powered-by-padding-left); + } + + & .ck-powered-by__label { + font-size: 7.5px; + letter-spacing: -.2px; + padding-left: 2px; + text-transform: uppercase; + font-weight: bold; + margin-right: 4px; + cursor: pointer; + line-height: var(--ck-powered-by-line-height); + color: hsl(0, 0%, 31%); + + } + + & .ck-icon { + display: block; + cursor: pointer; + } + + &:hover { + /* backdrop-filter: blur(.4px); */ + + & a { + filter: grayscale(0%); + opacity: 1; + } + } + } + + &.ck-balloon-panel_narrow .ck.ck-powered-by .ck-powered-by__label { + visibility: hidden; + position: absolute; + display: block; + left: 6px; + top: -1px; + bottom: -1px; + transform: translateX(0%); + max-width: 0px; + transition: transform .1s ease-in-out, max-width .1s ease-in-out; + overflow: hidden; + background: var(--ck-powered-by-bg); + border-radius: var(--ck-powered-by-border-radius) 0 0 var(--ck-powered-by-border-radius); + padding: var(--ck-powered-by-padding-top) 6px var(--ck-powered-by-padding-top) var(--ck-powered-by-padding-left); + z-index: 0; + border-width: 1px 0 1px 1px; + border-style: solid; + border-color: var(--ck-color-focus-border); + } + + &.ck-balloon-panel_narrow:hover .ck.ck-powered-by .ck-powered-by__label { + visibility: visible; + max-width: 60px; + transform: translateX(-100%); + } +} + diff --git a/packages/ckeditor5-powered-by/tsconfig.json b/packages/ckeditor5-powered-by/tsconfig.json new file mode 100644 index 00000000000..06d45c8982e --- /dev/null +++ b/packages/ckeditor5-powered-by/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "../../tsconfig.release.json", + "include": [ + "src", + "../../typings" + ], + "exclude": [ + "tests" + ] +} diff --git a/packages/ckeditor5-typing/package.json b/packages/ckeditor5-typing/package.json index fee82c52484..5a068c8e4a4 100644 --- a/packages/ckeditor5-typing/package.json +++ b/packages/ckeditor5-typing/package.json @@ -14,6 +14,7 @@ "dependencies": { "@ckeditor/ckeditor5-core": "^37.1.0", "@ckeditor/ckeditor5-engine": "^37.1.0", + "@ckeditor/ckeditor5-powered-by": "^37.1.0", "@ckeditor/ckeditor5-utils": "^37.1.0", "lodash-es": "^4.17.15" }, diff --git a/packages/ckeditor5-typing/src/typing.ts b/packages/ckeditor5-typing/src/typing.ts index e22f8a30e83..6b2835859b0 100644 --- a/packages/ckeditor5-typing/src/typing.ts +++ b/packages/ckeditor5-typing/src/typing.ts @@ -11,6 +11,9 @@ import { Plugin } from '@ckeditor/ckeditor5-core'; import Input from './input'; import Delete from './delete'; +// TODO: The way of injecting this plugin into all editors. EditorUI? EditableUIView? +import PoweredBy from '@ckeditor/ckeditor5-powered-by/src/poweredby'; + /** * The typing feature. It handles typing. * @@ -19,7 +22,7 @@ import Delete from './delete'; */ export default class Typing extends Plugin { public static get requires() { - return [ Input, Delete ] as const; + return [ Input, Delete, PoweredBy ] as const; } /** diff --git a/packages/ckeditor5-utils/src/dom/rect.ts b/packages/ckeditor5-utils/src/dom/rect.ts index 40bb99de16f..c8fe0bfe21a 100644 --- a/packages/ckeditor5-utils/src/dom/rect.ts +++ b/packages/ckeditor5-utils/src/dom/rect.ts @@ -11,6 +11,7 @@ import isRange from './isrange'; import isWindow from './iswindow'; import getBorderWidths from './getborderwidths'; import isText from './istext'; +import getPositionedAncestor from './getpositionedancestor'; const rectProperties: Array = [ 'top', 'right', 'bottom', 'left', 'width', 'height' ]; @@ -245,9 +246,10 @@ export default class Rect { // There's no ancestor to crop with the overflow. if ( !isBody( source ) ) { let parent = source.parentNode || source.commonAncestorContainer; + const positionedAncestor = getPositionedAncestor( parent as HTMLElement ); // Check the ancestors all the way up to the . - while ( parent && !isBody( parent ) ) { + while ( parent && !isBody( parent ) && parent !== positionedAncestor ) { const parentRect = new Rect( parent as HTMLElement ); const intersectionRect = visibleRect.getIntersection( parentRect ); From b78895511c08d7659b8bd5c52c02480e5baa9e06 Mon Sep 17 00:00:00 2001 From: Aleksander Nowodzinski Date: Mon, 8 May 2023 12:40:07 +0200 Subject: [PATCH 08/42] Code refactoring. --- packages/ckeditor5-powered-by/package.json | 49 ------- .../ckeditor5-powered-by/src/augmentation.ts | 13 -- packages/ckeditor5-powered-by/src/index.ts | 12 -- .../tests/manual/poweredby.md | 0 .../theme/icons/poweredby.svg | 21 --- packages/ckeditor5-powered-by/tsconfig.json | 10 -- packages/ckeditor5-typing/package.json | 1 - packages/ckeditor5-typing/src/typing.ts | 5 +- .../ckeditor5-ui/src/editorui/editorui.ts | 8 ++ .../src/editorui}/poweredby.ts | 127 +++++++++++------- .../tests/manual/poweredby}/poweredby.html | 33 +++++ .../tests/manual/poweredby}/poweredby.js | 5 +- .../tests/manual/poweredby/poweredby.md | 6 + .../theme/globals/_poweredby.css} | 62 +++++---- .../ckeditor5-ui/theme/globals/globals.css | 1 + .../theme/icons/project-logo.svg} | 0 packages/ckeditor5-utils/src/dom/rect.ts | 4 +- 17 files changed, 168 insertions(+), 189 deletions(-) delete mode 100644 packages/ckeditor5-powered-by/package.json delete mode 100644 packages/ckeditor5-powered-by/src/augmentation.ts delete mode 100644 packages/ckeditor5-powered-by/src/index.ts delete mode 100644 packages/ckeditor5-powered-by/tests/manual/poweredby.md delete mode 100644 packages/ckeditor5-powered-by/theme/icons/poweredby.svg delete mode 100644 packages/ckeditor5-powered-by/tsconfig.json rename packages/{ckeditor5-powered-by/src => ckeditor5-ui/src/editorui}/poweredby.ts (50%) rename packages/{ckeditor5-powered-by/tests/manual => ckeditor5-ui/tests/manual/poweredby}/poweredby.html (64%) rename packages/{ckeditor5-powered-by/tests/manual => ckeditor5-ui/tests/manual/poweredby}/poweredby.js (90%) create mode 100644 packages/ckeditor5-ui/tests/manual/poweredby/poweredby.md rename packages/{ckeditor5-powered-by/theme/poweredby.css => ckeditor5-ui/theme/globals/_poweredby.css} (50%) rename packages/{ckeditor5-powered-by/theme/icons/ckeditor.svg => ckeditor5-ui/theme/icons/project-logo.svg} (100%) diff --git a/packages/ckeditor5-powered-by/package.json b/packages/ckeditor5-powered-by/package.json deleted file mode 100644 index d830d71783b..00000000000 --- a/packages/ckeditor5-powered-by/package.json +++ /dev/null @@ -1,49 +0,0 @@ -{ - "name": "@ckeditor/ckeditor5-powered-by", - "version": "37.1.0", - "description": "Powered by feature for CKEditor 5.", - "keywords": [ - "ckeditor", - "ckeditor5", - "ckeditor 5", - "ckeditor5-feature", - "ckeditor5-plugin", - "ckeditor5-dll" - ], - "main": "src/index.ts", - "dependencies": { - "ckeditor5": "^37.1.0" - }, - "devDependencies": { - "@ckeditor/ckeditor5-core": "^37.1.0", - "@ckeditor/ckeditor5-editor-classic": "^37.1.0", - "typescript": "^4.8.4", - "webpack": "^5.58.1", - "webpack-cli": "^4.9.0" - }, - "engines": { - "node": ">=16.0.0", - "npm": ">=5.7.1" - }, - "author": "CKSource (http://cksource.com/)", - "license": "GPL-2.0-or-later", - "homepage": "https://ckeditor.com/ckeditor-5", - "bugs": "https://github.com/ckeditor/ckeditor5/issues", - "repository": { - "type": "git", - "url": "https://github.com/ckeditor/ckeditor5.git", - "directory": "packages/ckeditor5-powered-by" - }, - "files": [ - "lang", - "src/**/*.js", - "src/**/*.d.ts", - "theme", - "ckeditor5-metadata.json", - "CHANGELOG.md" - ], - "scripts": { - "build": "tsc -p ./tsconfig.json", - "postversion": "npm run build" - } -} diff --git a/packages/ckeditor5-powered-by/src/augmentation.ts b/packages/ckeditor5-powered-by/src/augmentation.ts deleted file mode 100644 index f072e2217a6..00000000000 --- a/packages/ckeditor5-powered-by/src/augmentation.ts +++ /dev/null @@ -1,13 +0,0 @@ -/** - * @license Copyright (c) 2003-2023, CKSource Holding sp. z o.o. All rights reserved. - * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license - */ - -import type { PoweredBy } from './index'; - -declare module '@ckeditor/ckeditor5-core' { - interface PluginsMap { - [ PoweredBy.pluginName ]: PoweredBy; - } -} - diff --git a/packages/ckeditor5-powered-by/src/index.ts b/packages/ckeditor5-powered-by/src/index.ts deleted file mode 100644 index e3a957a92a1..00000000000 --- a/packages/ckeditor5-powered-by/src/index.ts +++ /dev/null @@ -1,12 +0,0 @@ -/** - * @license Copyright (c) 2003-2023, CKSource Holding sp. z o.o. All rights reserved. - * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license - */ - -/** - * @module undo - */ - -export { default as PoweredBy } from './poweredby'; - -import './augmentation'; diff --git a/packages/ckeditor5-powered-by/tests/manual/poweredby.md b/packages/ckeditor5-powered-by/tests/manual/poweredby.md deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/packages/ckeditor5-powered-by/theme/icons/poweredby.svg b/packages/ckeditor5-powered-by/theme/icons/poweredby.svg deleted file mode 100644 index 87148b533a8..00000000000 --- a/packages/ckeditor5-powered-by/theme/icons/poweredby.svg +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - diff --git a/packages/ckeditor5-powered-by/tsconfig.json b/packages/ckeditor5-powered-by/tsconfig.json deleted file mode 100644 index 06d45c8982e..00000000000 --- a/packages/ckeditor5-powered-by/tsconfig.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "extends": "../../tsconfig.release.json", - "include": [ - "src", - "../../typings" - ], - "exclude": [ - "tests" - ] -} diff --git a/packages/ckeditor5-typing/package.json b/packages/ckeditor5-typing/package.json index 5a068c8e4a4..fee82c52484 100644 --- a/packages/ckeditor5-typing/package.json +++ b/packages/ckeditor5-typing/package.json @@ -14,7 +14,6 @@ "dependencies": { "@ckeditor/ckeditor5-core": "^37.1.0", "@ckeditor/ckeditor5-engine": "^37.1.0", - "@ckeditor/ckeditor5-powered-by": "^37.1.0", "@ckeditor/ckeditor5-utils": "^37.1.0", "lodash-es": "^4.17.15" }, diff --git a/packages/ckeditor5-typing/src/typing.ts b/packages/ckeditor5-typing/src/typing.ts index 6b2835859b0..e22f8a30e83 100644 --- a/packages/ckeditor5-typing/src/typing.ts +++ b/packages/ckeditor5-typing/src/typing.ts @@ -11,9 +11,6 @@ import { Plugin } from '@ckeditor/ckeditor5-core'; import Input from './input'; import Delete from './delete'; -// TODO: The way of injecting this plugin into all editors. EditorUI? EditableUIView? -import PoweredBy from '@ckeditor/ckeditor5-powered-by/src/poweredby'; - /** * The typing feature. It handles typing. * @@ -22,7 +19,7 @@ import PoweredBy from '@ckeditor/ckeditor5-powered-by/src/poweredby'; */ export default class Typing extends Plugin { public static get requires() { - return [ Input, Delete, PoweredBy ] as const; + return [ Input, Delete ] as const; } /** diff --git a/packages/ckeditor5-ui/src/editorui/editorui.ts b/packages/ckeditor5-ui/src/editorui/editorui.ts index ae91a4710b3..b654550c5c4 100644 --- a/packages/ckeditor5-ui/src/editorui/editorui.ts +++ b/packages/ckeditor5-ui/src/editorui/editorui.ts @@ -11,6 +11,7 @@ import ComponentFactory from '../componentfactory'; import TooltipManager from '../tooltipmanager'; +import PoweredBy from './poweredby'; import type EditorUIView from './editoruiview'; import type ToolbarView from '../toolbar/toolbarview'; @@ -51,6 +52,11 @@ export default abstract class EditorUI extends ObservableMixin() { */ public readonly tooltipManager: TooltipManager; + /** + * TODO. + */ + public readonly poweredBy: PoweredBy; + /** * Indicates the UI is ready. Set `true` after {@link #event:ready} event is fired. * @@ -120,6 +126,7 @@ export default abstract class EditorUI extends ObservableMixin() { this.componentFactory = new ComponentFactory( editor ); this.focusTracker = new FocusTracker(); this.tooltipManager = new TooltipManager( editor ); + this.poweredBy = new PoweredBy( editor ); this.set( 'viewportOffset', this._readViewportOffsetFromConfig() ); @@ -167,6 +174,7 @@ export default abstract class EditorUI extends ObservableMixin() { this.focusTracker.destroy(); this.tooltipManager.destroy( this.editor ); + this.poweredBy.destroy(); // Clean–up the references to the CKEditor instance stored in the native editable DOM elements. for ( const domElement of this._editableElementsMap.values() ) { diff --git a/packages/ckeditor5-powered-by/src/poweredby.ts b/packages/ckeditor5-ui/src/editorui/poweredby.ts similarity index 50% rename from packages/ckeditor5-powered-by/src/poweredby.ts rename to packages/ckeditor5-ui/src/editorui/poweredby.ts index fd315dfa3fe..32fbbd55d6e 100644 --- a/packages/ckeditor5-powered-by/src/poweredby.ts +++ b/packages/ckeditor5-ui/src/editorui/poweredby.ts @@ -4,19 +4,19 @@ */ /** - * @module TODO + * @module ui/editorui/poweredby */ -import { Plugin, type Editor } from 'ckeditor5/src/core'; -import { BalloonPanelView, IconView, View } from 'ckeditor5/src/ui'; -import type { PositionOptions, Rect } from 'ckeditor5/src/utils'; +import type { Editor } from '@ckeditor/ckeditor5-core'; +import { DomEmitterMixin, type PositionOptions, Rect, type Locale, findClosestScrollableAncestor } from '@ckeditor/ckeditor5-utils'; +import BalloonPanelView from '../panel/balloon/balloonpanelview'; +import IconView from '../icon/iconview'; +import View from '../view'; -// import poweredByIcon from '../theme/icons/poweredby.svg'; -import poweredByIcon from '../theme/icons/ckeditor.svg'; -import '../theme/poweredby.css'; +import poweredByIcon from '../../theme/icons/project-logo.svg'; -// const ICON_WIDTH = 112; -// const ICON_HEIGHT = 10; +const POWERED_BY_VIEW_SYMBOL = Symbol( '_poweredByView' ); +const POWERED_BY_BALLOON_SYMBOL = Symbol( '_poweredByBalloon' ); const ICON_WIDTH = 52; const ICON_HEIGHT = 10; const OFF_THE_SCREEN_POSITION = { @@ -26,43 +26,67 @@ const OFF_THE_SCREEN_POSITION = { }; /** - * TODO + * A helper that enables the "powered by" feature in the editor and renders a link to the project's + * webpage next to the bottom of the editing root when the editor is focused. + * + * The helper uses a {@link module:ui/panel/balloon/balloonpanelview~BalloonPanelView balloon panel} + * to position the link with the logo. + * + * @private */ -export default class PoweredBy extends Plugin { +export default class PoweredBy extends DomEmitterMixin() { /** - * TODO + * A reference to the powered by view displaying a link with a label and a project logo. */ - declare private _poweredByView: View; + private [ POWERED_BY_VIEW_SYMBOL ]: PoweredByView; /** - * @inheritDoc + * A reference to the powered by view displaying a link with a label and a project logo. */ - public static get pluginName(): 'PoweredBy' { - return 'PoweredBy'; + private [ POWERED_BY_BALLOON_SYMBOL ]: BalloonPanelView; + + /** + * Editor instance the helper was created for. + */ + private editor: Editor; + + /** + * Creates a "powered by" helper for a given editor. The feature is initialized on Editor#ready + * event. + * + * @param editor + */ + constructor( editor: Editor ) { + super(); + + this.editor = editor; + this[ POWERED_BY_VIEW_SYMBOL ] = new PoweredByView( editor.locale ); + editor.on( 'ready', this._handleEditorReady.bind( this ) ); } /** - * TODO + * Destroys the "powered by" helper along with its view. */ - public init(): void { - this._poweredByView = this._createPoweredByView(); + public destroy(): void { + this[ POWERED_BY_VIEW_SYMBOL ].destroy(); + this[ POWERED_BY_BALLOON_SYMBOL ].destroy(); - this.editor.on( 'ready', this._handleEditorReady.bind( this ) ); + this.stopListening(); } /** - * TODO + * Enables "powered by" label once the editor (ui) is ready. */ private _handleEditorReady(): void { const editor = this.editor; - const balloon = new BalloonPanelView(); + const balloon = this[ POWERED_BY_BALLOON_SYMBOL ] = new BalloonPanelView(); - balloon.content.add( this._poweredByView ); + balloon.content.add( this[ POWERED_BY_VIEW_SYMBOL ] ); balloon.withArrow = false; balloon.class = 'ck-powered-by-balloon'; editor.ui.view.body.add( balloon ); - editor.ui.focusTracker.add( this._poweredByView.element! ); + editor.ui.focusTracker.add( this[ POWERED_BY_VIEW_SYMBOL ].element! ); editor.ui.focusTracker.on( 'change:isFocused', ( evt, data, isFocused ) => { if ( isFocused ) { @@ -76,34 +100,46 @@ export default class PoweredBy extends Plugin { } } ); - // TODO: on image loaded editor.ui.on( 'update', () => { - if ( editor.ui.focusTracker.isFocused ) { - const attachOptions = getBalloonAttachOptions( editor ); + if ( !editor.ui.focusTracker.isFocused ) { + return; + } - if ( attachOptions ) { - balloon.unpin(); - balloon.pin( attachOptions ); - } + const attachOptions = getBalloonAttachOptions( editor ); + + if ( attachOptions ) { + balloon.unpin(); + balloon.pin( attachOptions ); } } ); // TODO: ~~Support for cases where the watermark gets cropped by parent with overflow: hidden~~. // TODO: Debounce. // TODO: Probably hide during scroll. + // TODO: Problem with Rect#isVisible() and floating editors (comments) vs. hiding the view when cropped by parent with overflow. + // TODO: Update position once an image loaded. + // TODO: Make the position (side) configurable. } +} +/** + * A view displaying a "powered by" label and project logo wrapped in a link. + */ +class PoweredByView extends View { /** - * TODO + * Created an instance of the "powered by" view. * - * @returns + * @param locale The localization services instance. */ - private _createPoweredByView(): View { - const poweredByView = new View(); + constructor( locale: Locale ) { + super( locale ); + const iconView = new IconView(); - iconView.content = poweredByIcon; - iconView.isColorInherited = false; + iconView.set( { + content: poweredByIcon, + isColorInherited: false + } ); iconView.extendTemplate( { attributes: { @@ -114,7 +150,7 @@ export default class PoweredBy extends Plugin { } } ); - poweredByView.setTemplate( { + this.setTemplate( { tag: 'div', attributes: { class: [ 'ck', 'ck-powered-by' ] @@ -140,8 +176,6 @@ export default class PoweredBy extends Plugin { } ] } ); - - return poweredByView; } } @@ -158,7 +192,6 @@ function getBalloonAttachOptions( editor: Editor ): Partial | n return { target: focusedDomRoot, - // TODO: Make the side configurable. positions: [ positioningFunction ] }; } @@ -190,13 +223,15 @@ function getLowerCornerPosition( const isRootNarrow = rootRect.width < 250; const balloonTop = rootRect.bottom - balloonRect.height / 2; const balloonLeft = getBalloonLeft( rootRect, balloonRect ); - const newBalloonRect = balloonRect.clone().moveTo( balloonLeft, balloonTop ); + const firstScrollableRootAncestor = findClosestScrollableAncestor( focusedDomRoot ); - visibleRootRect.moveBy( 0, balloonRect.height / 2 ); + if ( firstScrollableRootAncestor ) { + const firstScrollableRootAncestorRect = new Rect( firstScrollableRootAncestor ); - // The watermark cannot be positioned in this corner because the corner is "not visible enough". - if ( newBalloonRect.getIntersectionArea( visibleRootRect ) < newBalloonRect.getArea() ) { - return OFF_THE_SCREEN_POSITION; + // The watermark cannot be positioned in this corner because the corner is "not visible enough". + if ( visibleRootRect.bottom + balloonRect.height / 2 > firstScrollableRootAncestorRect.bottom ) { + return OFF_THE_SCREEN_POSITION; + } } return { diff --git a/packages/ckeditor5-powered-by/tests/manual/poweredby.html b/packages/ckeditor5-ui/tests/manual/poweredby/poweredby.html similarity index 64% rename from packages/ckeditor5-powered-by/tests/manual/poweredby.html rename to packages/ckeditor5-ui/tests/manual/poweredby/poweredby.html index 970098cf5df..949abbba7d0 100644 --- a/packages/ckeditor5-powered-by/tests/manual/poweredby.html +++ b/packages/ckeditor5-ui/tests/manual/poweredby/poweredby.html @@ -96,3 +96,36 @@

Padding-less editor

Like all the great things on earth traveling teaches us by example. Here are some of the most precious lessons I’ve

+ +

Parent with overflow

+
+
+

The three greatest things you learn from traveling

+

Like all the great things on earth traveling teaches us by example. Here are some of the most precious lessons I’ve + learned over the years of traveling.

+ +
+

The real voyage of discovery consists not in seeking new landscapes, but having new eyes.

+

Marcel Proust

+
+

Improvisation

+

Life doesn't allow us to execute every single plan perfectly. This especially seems to be the case when you travel. + You plan it down to every minute with a big checklist. But when it comes to executing it, something always comes up + and you’re left with your improvising skills. You learn to adapt as you go. Here’s how my travel checklist looks + now:

+
    +
  • buy the ticket
  • +
  • start your adventure
  • +
+
Three monks ascending the stairs of an ancient temple. +
Three monks ascending the stairs of an ancient temple.
+
+

Confidence

+

Going to a new place can be quite terrifying. While change and uncertainty make us scared, traveling teaches us how + ridiculous it is to be afraid of something before it happens. The moment you face your fear and see there is nothing + to be afraid of, is the moment you discover bliss.

+
+
diff --git a/packages/ckeditor5-powered-by/tests/manual/poweredby.js b/packages/ckeditor5-ui/tests/manual/poweredby/poweredby.js similarity index 90% rename from packages/ckeditor5-powered-by/tests/manual/poweredby.js rename to packages/ckeditor5-ui/tests/manual/poweredby/poweredby.js index 94af3724d62..de2ce88b385 100644 --- a/packages/ckeditor5-powered-by/tests/manual/poweredby.js +++ b/packages/ckeditor5-ui/tests/manual/poweredby/poweredby.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, CKEditorInspector */ import ClassicEditor from '@ckeditor/ckeditor5-editor-classic/src/classiceditor'; import ArticlePluginSet from '@ckeditor/ckeditor5-core/tests/_utils/articlepluginset'; @@ -45,6 +45,8 @@ function createEditor( selector ) { } ) .then( editor => { window.editors[ selector ] = editor; + + CKEditorInspector.attach( selector, editor ); } ) .catch( err => { console.error( err.stack ); @@ -58,3 +60,4 @@ createEditor( '#medium-bg' ); createEditor( '#narrow' ); createEditor( '#narrow-dark-bg' ); createEditor( '#padding-less' ); +createEditor( '#overflow-parent' ); diff --git a/packages/ckeditor5-ui/tests/manual/poweredby/poweredby.md b/packages/ckeditor5-ui/tests/manual/poweredby/poweredby.md new file mode 100644 index 00000000000..457d669bd67 --- /dev/null +++ b/packages/ckeditor5-ui/tests/manual/poweredby/poweredby.md @@ -0,0 +1,6 @@ +# Link to project's homepage rendered next to the editing root. + +1. Make sure the "powered by" link renders next to the bottom edge of the editing root when focused. +1. Make sure the link opens the page in a new tab. +1. Make sure the link disappears as soon as the editor gets blurred. +1. Make sure the link disappears when the editor is cropped by an ancestor with `overflow`. diff --git a/packages/ckeditor5-powered-by/theme/poweredby.css b/packages/ckeditor5-ui/theme/globals/_poweredby.css similarity index 50% rename from packages/ckeditor5-powered-by/theme/poweredby.css rename to packages/ckeditor5-ui/theme/globals/_poweredby.css index 354fab255c0..ec6b6fc99b5 100644 --- a/packages/ckeditor5-powered-by/theme/poweredby.css +++ b/packages/ckeditor5-ui/theme/globals/_poweredby.css @@ -7,16 +7,19 @@ --ck-powered-by-line-height: 10px; --ck-powered-by-padding-top: 2px; --ck-powered-by-padding-left: 4px; + --ck-powered-by-text-color: hsl(0, 0%, 31%); --ck-powered-by-border-radius: 100px; - --ck-powered-by-bg: hsl(0, 0%, 100%); + --ck-powered-by-border-color: var(--ck-color-focus-border); + --ck-powered-by-background: hsl(0, 0%, 100%); } .ck.ck-balloon-panel.ck-powered-by-balloon { + --ck-border-radius: var(--ck-powered-by-border-radius); + border: 0; border: var(--ck-focus-ring); box-shadow: none; - border-radius: var(--ck-powered-by-border-radius); - background: var(--ck-powered-by-bg); + background: var(--ck-powered-by-background); min-height: unset; &::after, @@ -46,7 +49,7 @@ margin-right: 4px; cursor: pointer; line-height: var(--ck-powered-by-line-height); - color: hsl(0, 0%, 31%); + color: var(--ck-powered-by-text-color); } @@ -56,8 +59,6 @@ } &:hover { - /* backdrop-filter: blur(.4px); */ - & a { filter: grayscale(0%); opacity: 1; @@ -65,30 +66,33 @@ } } - &.ck-balloon-panel_narrow .ck.ck-powered-by .ck-powered-by__label { - visibility: hidden; - position: absolute; - display: block; - left: 6px; - top: -1px; - bottom: -1px; - transform: translateX(0%); - max-width: 0px; - transition: transform .1s ease-in-out, max-width .1s ease-in-out; - overflow: hidden; - background: var(--ck-powered-by-bg); - border-radius: var(--ck-powered-by-border-radius) 0 0 var(--ck-powered-by-border-radius); - padding: var(--ck-powered-by-padding-top) 6px var(--ck-powered-by-padding-top) var(--ck-powered-by-padding-left); - z-index: 0; - border-width: 1px 0 1px 1px; - border-style: solid; - border-color: var(--ck-color-focus-border); - } + &.ck-balloon-panel_narrow { + /* Label sliding out on :hover when the editor is narrow */ + & .ck.ck-powered-by .ck-powered-by__label { + visibility: hidden; + position: absolute; + display: block; + left: 6px; + top: -1px; + bottom: -1px; + transform: translateX(0%); + max-width: 0px; + transition: transform .1s ease-in-out, max-width .1s ease-in-out; + overflow: hidden; + background: var(--ck-powered-by-background); + border-radius: var(--ck-powered-by-border-radius) 0 0 var(--ck-powered-by-border-radius); + padding: var(--ck-powered-by-padding-top) 6px var(--ck-powered-by-padding-top) var(--ck-powered-by-padding-left); + z-index: 0; + border-width: 1px 0 1px 1px; + border-style: solid; + border-color: var(--ck-powered-by-border-color); + } - &.ck-balloon-panel_narrow:hover .ck.ck-powered-by .ck-powered-by__label { - visibility: visible; - max-width: 60px; - transform: translateX(-100%); + &:hover .ck.ck-powered-by .ck-powered-by__label { + visibility: visible; + max-width: 60px; + transform: translateX(-100%); + } } } diff --git a/packages/ckeditor5-ui/theme/globals/globals.css b/packages/ckeditor5-ui/theme/globals/globals.css index 715ca478ed3..4e01f4b55ae 100644 --- a/packages/ckeditor5-ui/theme/globals/globals.css +++ b/packages/ckeditor5-ui/theme/globals/globals.css @@ -7,3 +7,4 @@ @import "./_reset.css"; @import "./_zindex.css"; @import "./_transition.css"; +@import "./_poweredby.css"; diff --git a/packages/ckeditor5-powered-by/theme/icons/ckeditor.svg b/packages/ckeditor5-ui/theme/icons/project-logo.svg similarity index 100% rename from packages/ckeditor5-powered-by/theme/icons/ckeditor.svg rename to packages/ckeditor5-ui/theme/icons/project-logo.svg diff --git a/packages/ckeditor5-utils/src/dom/rect.ts b/packages/ckeditor5-utils/src/dom/rect.ts index c8fe0bfe21a..40bb99de16f 100644 --- a/packages/ckeditor5-utils/src/dom/rect.ts +++ b/packages/ckeditor5-utils/src/dom/rect.ts @@ -11,7 +11,6 @@ import isRange from './isrange'; import isWindow from './iswindow'; import getBorderWidths from './getborderwidths'; import isText from './istext'; -import getPositionedAncestor from './getpositionedancestor'; const rectProperties: Array = [ 'top', 'right', 'bottom', 'left', 'width', 'height' ]; @@ -246,10 +245,9 @@ export default class Rect { // There's no ancestor to crop with the overflow. if ( !isBody( source ) ) { let parent = source.parentNode || source.commonAncestorContainer; - const positionedAncestor = getPositionedAncestor( parent as HTMLElement ); // Check the ancestors all the way up to the . - while ( parent && !isBody( parent ) && parent !== positionedAncestor ) { + while ( parent && !isBody( parent ) ) { const parentRect = new Rect( parent as HTMLElement ); const intersectionRect = visibleRect.getIntersection( parentRect ); From df7f85e15b5be0af69122942d1d19c722673cfc7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotr=20Szcz=C4=99=C5=9Bniak?= Date: Mon, 8 May 2023 12:44:42 +0200 Subject: [PATCH 09/42] Update the date string order of substrings. --- packages/ckeditor5-utils/src/madewith.ts | 7 ++++--- packages/ckeditor5-utils/tests/madewith.js | 14 +++++++------- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/packages/ckeditor5-utils/src/madewith.ts b/packages/ckeditor5-utils/src/madewith.ts index 8dda203428a..8a59e8bb0d0 100644 --- a/packages/ckeditor5-utils/src/madewith.ts +++ b/packages/ckeditor5-utils/src/madewith.ts @@ -60,9 +60,10 @@ export default function verify( stringToCheck: string ): VerifiedKeyStatus { return 'INVALID'; } - const day = Number( decryptedSecondElement.substring( 0, 2 ) ); - const monthIndex = Number( decryptedSecondElement.substring( 2, 4 ) ) - 1; - const year = Number( decryptedSecondElement.substring( 4, 8 ) ); + const year = Number( decryptedSecondElement.substring( 0, 4 ) ); + const monthIndex = Number( decryptedSecondElement.substring( 4, 6 ) ) - 1; + const day = Number( decryptedSecondElement.substring( 6, 8 ) ); + const date = new Date( year, monthIndex, day ); if ( !isFinite( Number( date ) ) ) { diff --git a/packages/ckeditor5-utils/tests/madewith.js b/packages/ckeditor5-utils/tests/madewith.js index 5605829dd38..e69a23213b2 100644 --- a/packages/ckeditor5-utils/tests/madewith.js +++ b/packages/ckeditor5-utils/tests/madewith.js @@ -10,14 +10,14 @@ describe( 'utils', () => { describe( 'should return `VALID`', () => { it( 'when date is later than the release date', () => { // future - const string = 'ZEhCMGNuVmtiMjlsYlcxdFpXOXNjbUZwYkdsemN3PT0tTURZeE1qSXdORFE9'; + const string = 'dG9vZWFzZXRtcHNsaXVyb3JsbWlkbS1NakEwTkRBMk1UST0='; expect( verify( string ) ).to.be.equal( 'VALID' ); } ); it( 'when old licence key is valid', () => { // eslint-disable-next-line max-len - const string = 'bWxyZWx6emlkcmJicmZ1YnNhaW9hYWVhbWFzb29vb2ZtdHJvdG9wYmFlYnRtb3VzbXJhb2Z6b3BsdGZhYW9lYm9zb3Jpcm9sYmltYnpyYWQ='; + const string = 'YWZvb2JkcnphYXJhZWJvb290em9wbWJvbHJ1c21sZnJlYmFzdG1paXN1cm1tZmllenJhb2F0YmFvcmxvb3B6aWJvYWRiZWZzYXJ0bW9ibG8='; expect( verify( string ) ).to.be.equal( 'VALID' ); } ); @@ -33,26 +33,26 @@ describe( 'utils', () => { it( 'when date is invalid', () => { // invalid = shorten than expected - const string = 'b2RydHNvbWlzdW1ybGxlb3RlYXBtaS1NVEV4T1RrMw=='; + const string = 'bGRtb3RyZWlhZWxzcG9taXRtdXJzby1NVGs1TnpFeA=='; expect( verify( string ) ).to.be.equal( 'INVALID' ); } ); it( 'when date is missing', () => { - const string = 'bXVtZWxlb21wb3Jpcml0bHNvZHRzYQ=='; + const string = 'dG1wb3Rhc2llc3VlbHJtZHJvbWlsbw=='; expect( verify( string ) ).to.be.equal( 'INVALID' ); } ); it( 'when unable to decode', () => { - const string = 'dW1vbW9ldG1pcnNybG9wbGRzYWl0ZS0j'; + const string = 'bW90b2x0b2Fyc2llc2lscGVtcm1kdS1abTl2WW1GeVltRT0='; expect( verify( string ) ).to.be.equal( 'INVALID' ); } ); it( 'when wrong string passed', () => { // # - const string = 'dGVtb2l0aXNsbW9vc2FyZXBkbG1ydS1abTl2WW1GeVltRT0='; + const string = 'YXRtaXNwdWRvbGVzb3RlcmlybW9sbS0j'; expect( verify( string ) ).to.be.equal( 'INVALID' ); } ); @@ -61,7 +61,7 @@ describe( 'utils', () => { describe( 'should return `EXPIRED`', () => { it( 'when date is earlier than the release date', () => { // past - const string = 'dXRyZWlwbHJvb21kbW9zbWVsc2F0aS1NREV3TVRFNU9EQT0='; + const string = 'cGVsYXVlaXN0bW9kc21pbG10cm9yby1NVGs0TURBeE1ERT0='; expect( verify( string ) ).to.be.equal( 'EXPIRED' ); } ); From 58bb26d97a2f320a590f8b64a481d74abcb5d4c5 Mon Sep 17 00:00:00 2001 From: Aleksander Nowodzinski Date: Mon, 8 May 2023 12:45:37 +0200 Subject: [PATCH 10/42] Code refactoring. --- .../ckeditor5-ui/src/editorui/poweredby.ts | 27 ++++++++++++------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/packages/ckeditor5-ui/src/editorui/poweredby.ts b/packages/ckeditor5-ui/src/editorui/poweredby.ts index 32fbbd55d6e..0c97d4dc0f8 100644 --- a/packages/ckeditor5-ui/src/editorui/poweredby.ts +++ b/packages/ckeditor5-ui/src/editorui/poweredby.ts @@ -36,19 +36,19 @@ const OFF_THE_SCREEN_POSITION = { */ export default class PoweredBy extends DomEmitterMixin() { /** - * A reference to the powered by view displaying a link with a label and a project logo. + * A reference to the view displaying a link with a label and a project logo. */ - private [ POWERED_BY_VIEW_SYMBOL ]: PoweredByView; + private [ POWERED_BY_VIEW_SYMBOL ]: PoweredByView | null; /** - * A reference to the powered by view displaying a link with a label and a project logo. + * A reference to the balloon panel hosting and positioning the "powered by" view. */ - private [ POWERED_BY_BALLOON_SYMBOL ]: BalloonPanelView; + private [ POWERED_BY_BALLOON_SYMBOL ]: BalloonPanelView | null; /** * Editor instance the helper was created for. */ - private editor: Editor; + private editor: Editor | null; /** * Creates a "powered by" helper for a given editor. The feature is initialized on Editor#ready @@ -60,7 +60,10 @@ export default class PoweredBy extends DomEmitterMixin() { super(); this.editor = editor; + this[ POWERED_BY_VIEW_SYMBOL ] = new PoweredByView( editor.locale ); + this[ POWERED_BY_BALLOON_SYMBOL ] = null; + editor.on( 'ready', this._handleEditorReady.bind( this ) ); } @@ -68,25 +71,29 @@ export default class PoweredBy extends DomEmitterMixin() { * Destroys the "powered by" helper along with its view. */ public destroy(): void { - this[ POWERED_BY_VIEW_SYMBOL ].destroy(); - this[ POWERED_BY_BALLOON_SYMBOL ].destroy(); + this[ POWERED_BY_BALLOON_SYMBOL ]!.unpin(); + + this[ POWERED_BY_VIEW_SYMBOL ]!.destroy(); + this[ POWERED_BY_BALLOON_SYMBOL ]!.destroy(); this.stopListening(); + + this.editor = this[ POWERED_BY_VIEW_SYMBOL ] = this[ POWERED_BY_BALLOON_SYMBOL ] = null; } /** * Enables "powered by" label once the editor (ui) is ready. */ private _handleEditorReady(): void { - const editor = this.editor; + const editor = this.editor!; const balloon = this[ POWERED_BY_BALLOON_SYMBOL ] = new BalloonPanelView(); - balloon.content.add( this[ POWERED_BY_VIEW_SYMBOL ] ); + balloon.content.add( this[ POWERED_BY_VIEW_SYMBOL ]! ); balloon.withArrow = false; balloon.class = 'ck-powered-by-balloon'; editor.ui.view.body.add( balloon ); - editor.ui.focusTracker.add( this[ POWERED_BY_VIEW_SYMBOL ].element! ); + editor.ui.focusTracker.add( balloon.element! ); editor.ui.focusTracker.on( 'change:isFocused', ( evt, data, isFocused ) => { if ( isFocused ) { From a755009ad834d0c93350f1ef97bf83067b5cdeac Mon Sep 17 00:00:00 2001 From: Aleksander Nowodzinski Date: Mon, 8 May 2023 12:49:44 +0200 Subject: [PATCH 11/42] Code refactoring. --- packages/ckeditor5-ui/src/editorui/poweredby.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/ckeditor5-ui/src/editorui/poweredby.ts b/packages/ckeditor5-ui/src/editorui/poweredby.ts index 0c97d4dc0f8..eff52d2a1a8 100644 --- a/packages/ckeditor5-ui/src/editorui/poweredby.ts +++ b/packages/ckeditor5-ui/src/editorui/poweredby.ts @@ -19,6 +19,7 @@ const POWERED_BY_VIEW_SYMBOL = Symbol( '_poweredByView' ); const POWERED_BY_BALLOON_SYMBOL = Symbol( '_poweredByBalloon' ); const ICON_WIDTH = 52; const ICON_HEIGHT = 10; +const NARROW_ROOT_WIDTH_THRESHOLD = 250; const OFF_THE_SCREEN_POSITION = { top: -9999999, left: -9999999, @@ -227,7 +228,7 @@ function getLowerCornerPosition( return OFF_THE_SCREEN_POSITION; } - const isRootNarrow = rootRect.width < 250; + const isRootNarrow = rootRect.width < NARROW_ROOT_WIDTH_THRESHOLD; const balloonTop = rootRect.bottom - balloonRect.height / 2; const balloonLeft = getBalloonLeft( rootRect, balloonRect ); const firstScrollableRootAncestor = findClosestScrollableAncestor( focusedDomRoot ); From cad24c83d9b4e54ab058e5991427486dabfa5968 Mon Sep 17 00:00:00 2001 From: Marek Lewandowski Date: Mon, 8 May 2023 13:26:03 +0200 Subject: [PATCH 12/42] Minor review adjustments. --- packages/ckeditor5-utils/src/madewith.ts | 10 ++++------ packages/ckeditor5-utils/tests/madewith.js | 2 +- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/packages/ckeditor5-utils/src/madewith.ts b/packages/ckeditor5-utils/src/madewith.ts index 8a59e8bb0d0..2d800191d64 100644 --- a/packages/ckeditor5-utils/src/madewith.ts +++ b/packages/ckeditor5-utils/src/madewith.ts @@ -15,13 +15,11 @@ export type VerifiedKeyStatus = 'VALID' | 'INVALID' | 'EXPIRED'; /** * Checks whether the given string contains information that allows you to verify the license status. * - * @param stringToCheck The string to check. + * @param token The string to check. * @returns String that represents the state of given `stringToCheck` parameter. It can be `'VALID'`, `'INVALID'` or `'EXPIRED'`. */ -export default function verify( stringToCheck: string ): VerifiedKeyStatus { - // This is just a very simplified preliminary front-end check of the date validation with the current release date - it - // allows to check whether a CKEditor logo/link will be shown or not (for recognize the editor and to show awesome - // features that it can bring with customized, licensed product). +export default function verify( token: string ): VerifiedKeyStatus { + // TODO: issue ci#3175 // mocked last release date const currentReleaseDate = new Date(); @@ -30,7 +28,7 @@ export default function verify( stringToCheck: string ): VerifiedKeyStatus { let decryptedSecondElement = ''; try { - decryptedData = atob( stringToCheck ); + decryptedData = atob( token ); } catch ( e ) { return 'INVALID'; } diff --git a/packages/ckeditor5-utils/tests/madewith.js b/packages/ckeditor5-utils/tests/madewith.js index e69a23213b2..0e6f5788ed0 100644 --- a/packages/ckeditor5-utils/tests/madewith.js +++ b/packages/ckeditor5-utils/tests/madewith.js @@ -15,7 +15,7 @@ describe( 'utils', () => { expect( verify( string ) ).to.be.equal( 'VALID' ); } ); - it( 'when old licence key is valid', () => { + it( 'when old token format is given', () => { // eslint-disable-next-line max-len const string = 'YWZvb2JkcnphYXJhZWJvb290em9wbWJvbHJ1c21sZnJlYmFzdG1paXN1cm1tZmllenJhb2F0YmFvcmxvb3B6aWJvYWRiZWZzYXJ0bW9ibG8='; From 8914fdcb9e151dae0d6d5241bddcc62f9e8b4dee Mon Sep 17 00:00:00 2001 From: Aleksander Nowodzinski Date: Mon, 8 May 2023 13:36:24 +0200 Subject: [PATCH 13/42] The powered by balloon should be created on demand. --- .../ckeditor5-ui/src/editorui/poweredby.ts | 51 +++++++++++++++---- .../tests/manual/poweredby/poweredby.js | 2 +- 2 files changed, 43 insertions(+), 10 deletions(-) diff --git a/packages/ckeditor5-ui/src/editorui/poweredby.ts b/packages/ckeditor5-ui/src/editorui/poweredby.ts index eff52d2a1a8..4a2711d4327 100644 --- a/packages/ckeditor5-ui/src/editorui/poweredby.ts +++ b/packages/ckeditor5-ui/src/editorui/poweredby.ts @@ -72,10 +72,19 @@ export default class PoweredBy extends DomEmitterMixin() { * Destroys the "powered by" helper along with its view. */ public destroy(): void { - this[ POWERED_BY_BALLOON_SYMBOL ]!.unpin(); + const editor = this.editor!; + const balloon = this[ POWERED_BY_BALLOON_SYMBOL ]; + const view = this[ POWERED_BY_VIEW_SYMBOL ]; + + if ( balloon ) { + balloon.unpin(); + editor!.ui.view.body.remove( balloon ); + balloon.destroy(); + } - this[ POWERED_BY_VIEW_SYMBOL ]!.destroy(); - this[ POWERED_BY_BALLOON_SYMBOL ]!.destroy(); + if ( view ) { + view.destroy(); + } this.stopListening(); @@ -87,16 +96,18 @@ export default class PoweredBy extends DomEmitterMixin() { */ private _handleEditorReady(): void { const editor = this.editor!; - const balloon = this[ POWERED_BY_BALLOON_SYMBOL ] = new BalloonPanelView(); - balloon.content.add( this[ POWERED_BY_VIEW_SYMBOL ]! ); - balloon.withArrow = false; - balloon.class = 'ck-powered-by-balloon'; + if ( !editor.ui.view ) { + return; + } - editor.ui.view.body.add( balloon ); - editor.ui.focusTracker.add( balloon.element! ); + let balloon: BalloonPanelView | undefined; editor.ui.focusTracker.on( 'change:isFocused', ( evt, data, isFocused ) => { + if ( !balloon ) { + balloon = this._createBalloonAndView(); + } + if ( isFocused ) { const attachOptions = getBalloonAttachOptions( editor ); @@ -113,6 +124,10 @@ export default class PoweredBy extends DomEmitterMixin() { return; } + if ( !balloon ) { + balloon = this._createBalloonAndView(); + } + const attachOptions = getBalloonAttachOptions( editor ); if ( attachOptions ) { @@ -128,6 +143,24 @@ export default class PoweredBy extends DomEmitterMixin() { // TODO: Update position once an image loaded. // TODO: Make the position (side) configurable. } + + /** + * Creates an instance of the {@link module:ui/panel/balloon/balloonpanelview~BalloonPanelView balloon panel} + * with the "powered by" view inside ready for positioning. + */ + private _createBalloonAndView(): BalloonPanelView { + const editor = this.editor!; + const balloon = this[ POWERED_BY_BALLOON_SYMBOL ] = new BalloonPanelView(); + + balloon.content.add( this[ POWERED_BY_VIEW_SYMBOL ]! ); + balloon.withArrow = false; + balloon.class = 'ck-powered-by-balloon'; + + editor.ui.view.body.add( balloon ); + editor.ui.focusTracker.add( balloon.element! ); + + return balloon; + } } /** diff --git a/packages/ckeditor5-ui/tests/manual/poweredby/poweredby.js b/packages/ckeditor5-ui/tests/manual/poweredby/poweredby.js index de2ce88b385..17f6ca285ef 100644 --- a/packages/ckeditor5-ui/tests/manual/poweredby/poweredby.js +++ b/packages/ckeditor5-ui/tests/manual/poweredby/poweredby.js @@ -46,7 +46,7 @@ function createEditor( selector ) { .then( editor => { window.editors[ selector ] = editor; - CKEditorInspector.attach( selector, editor ); + CKEditorInspector.attach( { [ selector ]: editor } ); } ) .catch( err => { console.error( err.stack ); From ca93d63a083b46900a52ef4c89093f704dcda8af Mon Sep 17 00:00:00 2001 From: Aleksander Nowodzinski Date: Mon, 8 May 2023 13:41:18 +0200 Subject: [PATCH 14/42] Squashed the logo to save bytes. --- .../ckeditor5-ui/src/editorui/poweredby.ts | 2 +- .../ckeditor5-ui/theme/icons/project-logo.svg | 21 +------------------ 2 files changed, 2 insertions(+), 21 deletions(-) diff --git a/packages/ckeditor5-ui/src/editorui/poweredby.ts b/packages/ckeditor5-ui/src/editorui/poweredby.ts index 4a2711d4327..98eaab72aa8 100644 --- a/packages/ckeditor5-ui/src/editorui/poweredby.ts +++ b/packages/ckeditor5-ui/src/editorui/poweredby.ts @@ -17,7 +17,7 @@ import poweredByIcon from '../../theme/icons/project-logo.svg'; const POWERED_BY_VIEW_SYMBOL = Symbol( '_poweredByView' ); const POWERED_BY_BALLOON_SYMBOL = Symbol( '_poweredByBalloon' ); -const ICON_WIDTH = 52; +const ICON_WIDTH = 53; const ICON_HEIGHT = 10; const NARROW_ROOT_WIDTH_THRESHOLD = 250; const OFF_THE_SCREEN_POSITION = { diff --git a/packages/ckeditor5-ui/theme/icons/project-logo.svg b/packages/ckeditor5-ui/theme/icons/project-logo.svg index 56dd43b7efe..32b88d881c6 100644 --- a/packages/ckeditor5-ui/theme/icons/project-logo.svg +++ b/packages/ckeditor5-ui/theme/icons/project-logo.svg @@ -1,20 +1 @@ - - - - - - - - - - - - - - - - - - - - + From 26c5e2a6bef89190e277fb1b20b5afddcda715de Mon Sep 17 00:00:00 2001 From: Aleksander Nowodzinski Date: Mon, 8 May 2023 13:53:56 +0200 Subject: [PATCH 15/42] Ignored code coverage errors in the PoweredBy module until tests are written. --- packages/ckeditor5-ui/src/editorui/poweredby.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/ckeditor5-ui/src/editorui/poweredby.ts b/packages/ckeditor5-ui/src/editorui/poweredby.ts index 98eaab72aa8..716be18e903 100644 --- a/packages/ckeditor5-ui/src/editorui/poweredby.ts +++ b/packages/ckeditor5-ui/src/editorui/poweredby.ts @@ -124,6 +124,7 @@ export default class PoweredBy extends DomEmitterMixin() { return; } + /* istanbul ignore next -- @preserve */ if ( !balloon ) { balloon = this._createBalloonAndView(); } @@ -229,6 +230,7 @@ function getBalloonAttachOptions( editor: Editor ): Partial | n const positioningFunction = editor.locale.contentLanguageDirection === 'ltr' ? getLowerRightCornerPosition( focusedDomRoot ) : + /* istanbul ignore next -- @preserve */ getLowerLeftCornerPosition( focusedDomRoot ); return { @@ -243,6 +245,7 @@ function getLowerRightCornerPosition( focusedDomRoot: HTMLElement ) { } ); } +/* istanbul ignore next -- @preserve */ function getLowerLeftCornerPosition( focusedDomRoot: HTMLElement ) { return getLowerCornerPosition( focusedDomRoot, rootRect => { return rootRect.left + 5; @@ -257,6 +260,7 @@ function getLowerCornerPosition( const visibleRootRect = rootRect.getVisible(); // Root cropped by ancestors. + /* istanbul ignore next -- @preserve */ if ( !visibleRootRect ) { return OFF_THE_SCREEN_POSITION; } @@ -275,6 +279,7 @@ function getLowerCornerPosition( } } + /* istanbul ignore next -- @preserve */ return { top: balloonTop, left: balloonLeft, From ddc432263a5f28a9f3c467db941674850a0e87bc Mon Sep 17 00:00:00 2001 From: Marek Lewandowski Date: Mon, 8 May 2023 14:18:49 +0200 Subject: [PATCH 16/42] Further adjustments. --- packages/ckeditor5-utils/src/madewith.ts | 37 +++++++++++----------- packages/ckeditor5-utils/tests/madewith.js | 29 +++++++++++++---- 2 files changed, 41 insertions(+), 25 deletions(-) diff --git a/packages/ckeditor5-utils/src/madewith.ts b/packages/ckeditor5-utils/src/madewith.ts index 2d800191d64..7c3759f6f23 100644 --- a/packages/ckeditor5-utils/src/madewith.ts +++ b/packages/ckeditor5-utils/src/madewith.ts @@ -7,23 +7,21 @@ * @module utils/madewith */ +import { releaseDate } from './version'; + /** * Possible states of the key after verification. */ -export type VerifiedKeyStatus = 'VALID' | 'INVALID' | 'EXPIRED'; +export type VerifiedKeyStatus = 'VALID' | 'INVALID'; /** * Checks whether the given string contains information that allows you to verify the license status. * * @param token The string to check. - * @returns String that represents the state of given `stringToCheck` parameter. It can be `'VALID'`, `'INVALID'` or `'EXPIRED'`. + * @returns String that represents the state of given `token` parameter. */ export default function verify( token: string ): VerifiedKeyStatus { // TODO: issue ci#3175 - - // mocked last release date - const currentReleaseDate = new Date(); - let decryptedData = ''; let decryptedSecondElement = ''; @@ -38,14 +36,19 @@ export default function verify( token: string ): VerifiedKeyStatus { const firstElement = splittedDecryptedData[ 0 ]; const secondElement = splittedDecryptedData[ 1 ]; - if ( !secondElement ) { - const isFirstElementMatchingThePattern = firstElement.match( /^[a-zA-Z0-9+/=$]+$/g ); + try { + // Must be a valid format. + atob( firstElement ); + } catch ( e ) { + return 'INVALID'; + } - if ( isFirstElementMatchingThePattern && ( firstElement.length >= 0x28 && firstElement.length <= 0xff ) ) { - return 'VALID'; - } else { - return 'INVALID'; - } + if ( firstElement.length < 40 || firstElement.length > 255 ) { + return 'INVALID'; + } + + if ( !secondElement ) { + return 'VALID'; } try { @@ -54,7 +57,7 @@ export default function verify( token: string ): VerifiedKeyStatus { return 'INVALID'; } - if ( decryptedSecondElement.length !== 0x8 ) { + if ( decryptedSecondElement.length !== 8 ) { return 'INVALID'; } @@ -64,13 +67,9 @@ export default function verify( token: string ): VerifiedKeyStatus { const date = new Date( year, monthIndex, day ); - if ( !isFinite( Number( date ) ) ) { + if ( date < releaseDate ) { return 'INVALID'; } - if ( date < currentReleaseDate ) { - return 'EXPIRED'; - } - return 'VALID'; } diff --git a/packages/ckeditor5-utils/tests/madewith.js b/packages/ckeditor5-utils/tests/madewith.js index 0e6f5788ed0..5189238276b 100644 --- a/packages/ckeditor5-utils/tests/madewith.js +++ b/packages/ckeditor5-utils/tests/madewith.js @@ -10,7 +10,8 @@ describe( 'utils', () => { describe( 'should return `VALID`', () => { it( 'when date is later than the release date', () => { // future - const string = 'dG9vZWFzZXRtcHNsaXVyb3JsbWlkbS1NakEwTkRBMk1UST0='; + // eslint-disable-next-line max-len + const string = 'dG9vZWFzZXRtcHNsaXVyb3JsbWlkbXRvb2Vhc2V0bXBzbGl1cm9ybG1pZG10b29lYXNldG1wc2xpdXJvcmxtaWRtLU1qQTBOREEyTVRJPQ=='; expect( verify( string ) ).to.be.equal( 'VALID' ); } ); @@ -24,6 +25,23 @@ describe( 'utils', () => { } ); describe( 'should return `INVALID`', () => { + describe( 'new', () => { + it( 'first too short', () => { + expect( verify( 'Wm05dlltRnktTWpBeU5UQXhNREU9' ) ).to.be.equal( 'INVALID' ); + } ); + + it( 'first too long', () => { + // eslint-disable-next-line max-len + const string = 'YzNSbGJTQmxjbkp2Y2pvZ2JtVjBPanBGVWxKZlFreFBRMHRGUkY5Q1dWOURURWxGVGxSemRHVnRJR1Z5Y205eU9pQnVaWFE2T2tWU1VsOUNURTlEUzBWRVgwSlpYME5NU1VWT1ZITjBaVzBnWlhKeWIzSTZJRzVsZERvNlJWSlNYMEpNVDBOTFJVUmZRbGxmUTB4SlJVNVVjM1JsYlNCbGNuSnZjam9nYm1WME9qcEZVbEpmUWt4UFEwdEZSRjlDV1Y5RFRFbEZUbFJ6ZEdWdElHVnljbTl5T2lCdVpYUTZPa1ZTVWw5Q1RFOURTMFZFWDBKWlgwTk1TVVZPVkhOMFpXMGdaWEp5YjNJNklHNWxkRG82UlZKU1gwSk1UME5MUlVSZlFsbGZRMHhKUlU1VS1NakF5TlRBeE1ERT0='; + expect( verify( string ) ).to.be.equal( 'INVALID' ); + } ); + + it( 'first wrong format', () => { + const string = 'YS1NakF5TlRBeE1ERT0='; + expect( verify( string ) ).to.be.equal( 'INVALID' ); + } ); + } ); + it( 'when passed variable is invalid', () => { // invalid const string = 'foobarbaz'; @@ -56,14 +74,13 @@ describe( 'utils', () => { expect( verify( string ) ).to.be.equal( 'INVALID' ); } ); - } ); - describe( 'should return `EXPIRED`', () => { it( 'when date is earlier than the release date', () => { - // past - const string = 'cGVsYXVlaXN0bW9kc21pbG10cm9yby1NVGs0TURBeE1ERT0='; + // new, past + // eslint-disable-next-line max-len + const string = 'Y0dWc1lYVmxhWE4wYlc5a2MyMXBiRzEwY205eWIzQmxiR0YxWldsemRHMXZaSE50YVd4dGRISnZjbTlrYzJGa2MyRmtjMkU9LU1UazRNREF4TURFPQ=='; - expect( verify( string ) ).to.be.equal( 'EXPIRED' ); + expect( verify( string ) ).to.be.equal( 'INVALID' ); } ); } ); } ); From 19ceac1dce4ae9e81a7107852f86598cf81736d7 Mon Sep 17 00:00:00 2001 From: Marek Lewandowski Date: Mon, 8 May 2023 14:35:20 +0200 Subject: [PATCH 17/42] Adjusted unit tests. Handled case when date is NaN. --- packages/ckeditor5-utils/src/madewith.ts | 2 +- packages/ckeditor5-utils/tests/madewith.js | 57 ++++++++++++---------- 2 files changed, 31 insertions(+), 28 deletions(-) diff --git a/packages/ckeditor5-utils/src/madewith.ts b/packages/ckeditor5-utils/src/madewith.ts index 7c3759f6f23..1ce88384e82 100644 --- a/packages/ckeditor5-utils/src/madewith.ts +++ b/packages/ckeditor5-utils/src/madewith.ts @@ -67,7 +67,7 @@ export default function verify( token: string ): VerifiedKeyStatus { const date = new Date( year, monthIndex, day ); - if ( date < releaseDate ) { + if ( date < releaseDate || isNaN( Number( date ) ) ) { return 'INVALID'; } diff --git a/packages/ckeditor5-utils/tests/madewith.js b/packages/ckeditor5-utils/tests/madewith.js index 5189238276b..ad996065772 100644 --- a/packages/ckeditor5-utils/tests/madewith.js +++ b/packages/ckeditor5-utils/tests/madewith.js @@ -9,7 +9,7 @@ describe( 'utils', () => { describe( 'verify', () => { describe( 'should return `VALID`', () => { it( 'when date is later than the release date', () => { - // future + // new, future // eslint-disable-next-line max-len const string = 'dG9vZWFzZXRtcHNsaXVyb3JsbWlkbXRvb2Vhc2V0bXBzbGl1cm9ybG1pZG10b29lYXNldG1wc2xpdXJvcmxtaWRtLU1qQTBOREEyTVRJPQ=='; @@ -17,6 +17,7 @@ describe( 'utils', () => { } ); it( 'when old token format is given', () => { + // old // eslint-disable-next-line max-len const string = 'YWZvb2JkcnphYXJhZWJvb290em9wbWJvbHJ1c21sZnJlYmFzdG1paXN1cm1tZmllenJhb2F0YmFvcmxvb3B6aWJvYWRiZWZzYXJ0bW9ibG8='; @@ -40,45 +41,47 @@ describe( 'utils', () => { const string = 'YS1NakF5TlRBeE1ERT0='; expect( verify( string ) ).to.be.equal( 'INVALID' ); } ); - } ); - it( 'when passed variable is invalid', () => { - // invalid - const string = 'foobarbaz'; + it( 'when date is invalid', () => { + // invalid = shorten than expected + const string = 'bGRtb3RyZWlhZWxzcG9taXRtdXJzby1NVGs1TnpFeA=='; - expect( verify( string ) ).to.be.equal( 'INVALID' ); - } ); + expect( verify( string ) ).to.be.equal( 'INVALID' ); + } ); - it( 'when date is invalid', () => { - // invalid = shorten than expected - const string = 'bGRtb3RyZWlhZWxzcG9taXRtdXJzby1NVGs1TnpFeA=='; + it( 'wrong second part format', () => { + const string = 'Ylc5MGIyeDBiMkZ5YzJsbGMybHNjR1Z0Y20xa2RXMXZkRzlzZEc5aGNuTnBaWE09LVptOXZZbUZ5WW1FPQ=='; - expect( verify( string ) ).to.be.equal( 'INVALID' ); - } ); + expect( verify( string ) ).to.be.equal( 'INVALID' ); + } ); - it( 'when date is missing', () => { - const string = 'dG1wb3Rhc2llc3VlbHJtZHJvbWlsbw=='; + it( 'when wrong string passed', () => { + // # instead of second part + const string = 'Ylc5MGIyeDBiMkZ5YzJsbGMybHNjR1Z0Y20xa2RXMXZkRzlzZEc5aGNuTnBaWE09LSM='; - expect( verify( string ) ).to.be.equal( 'INVALID' ); - } ); + expect( verify( string ) ).to.be.equal( 'INVALID' ); + } ); - it( 'when unable to decode', () => { - const string = 'bW90b2x0b2Fyc2llc2lscGVtcm1kdS1abTl2WW1GeVltRT0='; + it( 'when date is earlier than the release date', () => { + // new, past + // eslint-disable-next-line max-len + const string = 'Y0dWc1lYVmxhWE4wYlc5a2MyMXBiRzEwY205eWIzQmxiR0YxWldsemRHMXZaSE50YVd4dGRISnZjbTlrYzJGa2MyRmtjMkU9LU1UazRNREF4TURFPQ=='; - expect( verify( string ) ).to.be.equal( 'INVALID' ); + expect( verify( string ) ).to.be.equal( 'INVALID' ); + } ); } ); - it( 'when wrong string passed', () => { - // # - const string = 'YXRtaXNwdWRvbGVzb3RlcmlybW9sbS0j'; + describe( 'old', () => { + it( 'when date is missing', () => { + const string = 'dG1wb3Rhc2llc3VlbHJtZHJvbWlsbw=='; - expect( verify( string ) ).to.be.equal( 'INVALID' ); + expect( verify( string ) ).to.be.equal( 'INVALID' ); + } ); } ); - it( 'when date is earlier than the release date', () => { - // new, past - // eslint-disable-next-line max-len - const string = 'Y0dWc1lYVmxhWE4wYlc5a2MyMXBiRzEwY205eWIzQmxiR0YxWldsemRHMXZaSE50YVd4dGRISnZjbTlrYzJGa2MyRmtjMkU9LU1UazRNREF4TURFPQ=='; + it( 'when passed variable is invalid', () => { + // invalid + const string = 'foobarbaz'; expect( verify( string ) ).to.be.equal( 'INVALID' ); } ); From 2f8e553dc32aebb688e20f321e56645b05aeef85 Mon Sep 17 00:00:00 2001 From: Aleksander Nowodzinski Date: Mon, 8 May 2023 14:57:10 +0200 Subject: [PATCH 18/42] Switched label position to over the editing root. --- .../ckeditor5-ui/src/editorui/poweredby.ts | 38 ++++++++----------- .../ckeditor5-ui/theme/globals/_poweredby.css | 9 +---- 2 files changed, 17 insertions(+), 30 deletions(-) diff --git a/packages/ckeditor5-ui/src/editorui/poweredby.ts b/packages/ckeditor5-ui/src/editorui/poweredby.ts index 716be18e903..14aecc4094d 100644 --- a/packages/ckeditor5-ui/src/editorui/poweredby.ts +++ b/packages/ckeditor5-ui/src/editorui/poweredby.ts @@ -8,7 +8,7 @@ */ import type { Editor } from '@ckeditor/ckeditor5-core'; -import { DomEmitterMixin, type PositionOptions, Rect, type Locale, findClosestScrollableAncestor } from '@ckeditor/ckeditor5-utils'; +import { DomEmitterMixin, type PositionOptions, type Locale, type Rect } from '@ckeditor/ckeditor5-utils'; import BalloonPanelView from '../panel/balloon/balloonpanelview'; import IconView from '../icon/iconview'; import View from '../view'; @@ -19,6 +19,7 @@ const POWERED_BY_VIEW_SYMBOL = Symbol( '_poweredByView' ); const POWERED_BY_BALLOON_SYMBOL = Symbol( '_poweredByBalloon' ); const ICON_WIDTH = 53; const ICON_HEIGHT = 10; +const CORNER_OFFSET = 5; const NARROW_ROOT_WIDTH_THRESHOLD = 250; const OFF_THE_SCREEN_POSITION = { top: -9999999, @@ -229,9 +230,9 @@ function getBalloonAttachOptions( editor: Editor ): Partial | n } const positioningFunction = editor.locale.contentLanguageDirection === 'ltr' ? - getLowerRightCornerPosition( focusedDomRoot ) : + getLowerRightCornerPosition() : /* istanbul ignore next -- @preserve */ - getLowerLeftCornerPosition( focusedDomRoot ); + getLowerLeftCornerPosition(); return { target: focusedDomRoot, @@ -239,23 +240,18 @@ function getBalloonAttachOptions( editor: Editor ): Partial | n }; } -function getLowerRightCornerPosition( focusedDomRoot: HTMLElement ) { - return getLowerCornerPosition( focusedDomRoot, ( rootRect, balloonRect ) => { - return rootRect.left + rootRect.width - balloonRect.width - 5; +function getLowerRightCornerPosition() { + return getLowerCornerPosition( ( rootRect, balloonRect ) => { + return rootRect.left + rootRect.width - balloonRect.width - CORNER_OFFSET; } ); } /* istanbul ignore next -- @preserve */ -function getLowerLeftCornerPosition( focusedDomRoot: HTMLElement ) { - return getLowerCornerPosition( focusedDomRoot, rootRect => { - return rootRect.left + 5; - } ); +function getLowerLeftCornerPosition() { + return getLowerCornerPosition( rootRect => rootRect.left + CORNER_OFFSET ); } -function getLowerCornerPosition( - focusedDomRoot: HTMLElement, - getBalloonLeft: ( rootRect: Rect, balloonRect: Rect ) => number -) { +function getLowerCornerPosition( getBalloonLeft: ( rootRect: Rect, balloonRect: Rect ) => number ) { return ( rootRect: Rect, balloonRect: Rect ) => { const visibleRootRect = rootRect.getVisible(); @@ -266,17 +262,13 @@ function getLowerCornerPosition( } const isRootNarrow = rootRect.width < NARROW_ROOT_WIDTH_THRESHOLD; - const balloonTop = rootRect.bottom - balloonRect.height / 2; + const balloonTop = rootRect.bottom - balloonRect.height - CORNER_OFFSET; const balloonLeft = getBalloonLeft( rootRect, balloonRect ); - const firstScrollableRootAncestor = findClosestScrollableAncestor( focusedDomRoot ); - - if ( firstScrollableRootAncestor ) { - const firstScrollableRootAncestorRect = new Rect( firstScrollableRootAncestor ); + const newBalloonRect = balloonRect.clone().moveTo( balloonLeft, balloonTop ); - // The watermark cannot be positioned in this corner because the corner is "not visible enough". - if ( visibleRootRect.bottom + balloonRect.height / 2 > firstScrollableRootAncestorRect.bottom ) { - return OFF_THE_SCREEN_POSITION; - } + // The watermark cannot be positioned in this corner because the corner is not quite visible. + if ( newBalloonRect.getIntersectionArea( visibleRootRect ) < newBalloonRect.getArea() ) { + return OFF_THE_SCREEN_POSITION; } /* istanbul ignore next -- @preserve */ diff --git a/packages/ckeditor5-ui/theme/globals/_poweredby.css b/packages/ckeditor5-ui/theme/globals/_poweredby.css index ec6b6fc99b5..e32cbe339ca 100644 --- a/packages/ckeditor5-ui/theme/globals/_poweredby.css +++ b/packages/ckeditor5-ui/theme/globals/_poweredby.css @@ -9,7 +9,6 @@ --ck-powered-by-padding-left: 4px; --ck-powered-by-text-color: hsl(0, 0%, 31%); --ck-powered-by-border-radius: 100px; - --ck-powered-by-border-color: var(--ck-color-focus-border); --ck-powered-by-background: hsl(0, 0%, 100%); } @@ -17,7 +16,6 @@ --ck-border-radius: var(--ck-powered-by-border-radius); border: 0; - border: var(--ck-focus-ring); box-shadow: none; background: var(--ck-powered-by-background); min-height: unset; @@ -73,8 +71,8 @@ position: absolute; display: block; left: 6px; - top: -1px; - bottom: -1px; + top: 0px; + bottom: 0px; transform: translateX(0%); max-width: 0px; transition: transform .1s ease-in-out, max-width .1s ease-in-out; @@ -83,9 +81,6 @@ border-radius: var(--ck-powered-by-border-radius) 0 0 var(--ck-powered-by-border-radius); padding: var(--ck-powered-by-padding-top) 6px var(--ck-powered-by-padding-top) var(--ck-powered-by-padding-left); z-index: 0; - border-width: 1px 0 1px 1px; - border-style: solid; - border-color: var(--ck-powered-by-border-color); } &:hover .ck.ck-powered-by .ck-powered-by__label { From 30acdb1538c703a22e4cb16ca81d36b267ebc423 Mon Sep 17 00:00:00 2001 From: Aleksander Nowodzinski Date: Mon, 8 May 2023 14:58:04 +0200 Subject: [PATCH 19/42] Code refactoring. --- packages/ckeditor5-ui/src/editorui/poweredby.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/ckeditor5-ui/src/editorui/poweredby.ts b/packages/ckeditor5-ui/src/editorui/poweredby.ts index 14aecc4094d..3acc3693b5f 100644 --- a/packages/ckeditor5-ui/src/editorui/poweredby.ts +++ b/packages/ckeditor5-ui/src/editorui/poweredby.ts @@ -267,6 +267,7 @@ function getLowerCornerPosition( getBalloonLeft: ( rootRect: Rect, balloonRect: const newBalloonRect = balloonRect.clone().moveTo( balloonLeft, balloonTop ); // The watermark cannot be positioned in this corner because the corner is not quite visible. + /* istanbul ignore next -- @preserve */ if ( newBalloonRect.getIntersectionArea( visibleRootRect ) < newBalloonRect.getArea() ) { return OFF_THE_SCREEN_POSITION; } From 1bbc649033d803b09a868b6ff93352068c73129a Mon Sep 17 00:00:00 2001 From: Aleksander Nowodzinski Date: Mon, 8 May 2023 15:19:43 +0200 Subject: [PATCH 20/42] Tests: Added EditorUI tests for powered by integration. --- packages/ckeditor5-ui/tests/editorui/editorui.js | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/packages/ckeditor5-ui/tests/editorui/editorui.js b/packages/ckeditor5-ui/tests/editorui/editorui.js index 695609e97e5..c2f31a205ba 100644 --- a/packages/ckeditor5-ui/tests/editorui/editorui.js +++ b/packages/ckeditor5-ui/tests/editorui/editorui.js @@ -8,6 +8,7 @@ import EditorUI from '../../src/editorui/editorui'; import ComponentFactory from '../../src/componentfactory'; import ToolbarView from '../../src/toolbar/toolbarview'; import TooltipManager from '../../src/tooltipmanager'; +import PoweredBy from '../../src/editorui/poweredby'; import FocusTracker from '@ckeditor/ckeditor5-utils/src/focustracker'; import { keyCodes } from '@ckeditor/ckeditor5-utils/src/keyboard'; @@ -49,6 +50,10 @@ describe( 'EditorUI', () => { expect( ui.tooltipManager ).to.be.instanceOf( TooltipManager ); } ); + it( 'should create #poweredBy', () => { + expect( ui.poweredBy ).to.be.instanceOf( PoweredBy ); + } ); + it( 'should have #element getter', () => { expect( ui.element ).to.null; } ); @@ -161,6 +166,14 @@ describe( 'EditorUI', () => { sinon.assert.calledOnce( destroySpy ); sinon.assert.calledWithExactly( destroySpy, editor ); } ); + + it( 'should destroy #poweredBy', () => { + const destroySpy = sinon.spy( ui.poweredBy, 'destroy' ); + + ui.destroy(); + + sinon.assert.calledOnce( destroySpy ); + } ); } ); describe( 'setEditableElement()', () => { From 638b887955badd5d1b8db000dbbcbb258442e2b0 Mon Sep 17 00:00:00 2001 From: Aleksander Nowodzinski Date: Mon, 8 May 2023 16:58:47 +0200 Subject: [PATCH 21/42] Allowed configuration of the powered by feature. --- .../ckeditor5-core/src/editor/editorconfig.ts | 29 ++++-- .../ckeditor5-ui/src/editorui/poweredby.ts | 86 ++++++++++++++---- .../tests/manual/poweredby/poweredby.html | 25 ++++++ .../tests/manual/poweredby/poweredby.js | 89 ++++++++++++------- .../ckeditor5-ui/theme/globals/_poweredby.css | 34 ++++++- 5 files changed, 206 insertions(+), 57 deletions(-) diff --git a/packages/ckeditor5-core/src/editor/editorconfig.ts b/packages/ckeditor5-core/src/editor/editorconfig.ts index 5b3e0d40154..9fb005bab51 100644 --- a/packages/ckeditor5-core/src/editor/editorconfig.ts +++ b/packages/ckeditor5-core/src/editor/editorconfig.ts @@ -466,14 +466,24 @@ export interface EditorConfig { * (e.g. the top menu). Thanks to setting the UI viewport offset the toolbar and other contextual balloons will not be positioned * underneath or above the page's UI. * - * ```ts - * ui: { - * viewportOffset: { top: 10, right: 10, bottom: 10, left: 10 } - * } - * ``` + * ```ts + * ui: { + * viewportOffset: { top: 10, right: 10, bottom: 10, left: 10 } + * } + * ``` * * **Note:** If you want to modify the viewport offset in runtime (after editor was created), you can do that by overriding - * {@link module:ui/editorui/editorui~EditorUI#viewportOffset `editor.ui.viewportOffset`}. + * {@link module:ui/editorui/editorui~EditorUI#viewportOffset `editor.ui.viewportOffset`}. + * + * * **`ui.poweredBy`** – The configuration of the project logo displayed over editor's editing are in open-source integrations + * ([learn more](TODO)). It allows customizing the position of the logo to minimize the risk of collision with editor content + * and UI. + * + * ```ts + * ui: { + * poweredBy: { position: 'border', side: 'left', verticalOffset: 2, horizontalOffset: 30 } + * } + * ``` */ ui?: UiConfig; @@ -561,4 +571,11 @@ export interface UiConfig { right?: number; top?: number; }; + + poweredBy?: { + position: 'inside' | 'border'; + side: 'left' | 'right'; + verticalOffset: number; + horizontalOffset: number; + }; } diff --git a/packages/ckeditor5-ui/src/editorui/poweredby.ts b/packages/ckeditor5-ui/src/editorui/poweredby.ts index 3acc3693b5f..08671b26d2f 100644 --- a/packages/ckeditor5-ui/src/editorui/poweredby.ts +++ b/packages/ckeditor5-ui/src/editorui/poweredby.ts @@ -8,18 +8,24 @@ */ import type { Editor } from '@ckeditor/ckeditor5-core'; -import { DomEmitterMixin, type PositionOptions, type Locale, type Rect } from '@ckeditor/ckeditor5-utils'; +import { + Rect, + DomEmitterMixin, + findClosestScrollableAncestor, + type PositionOptions, + type Locale +} from '@ckeditor/ckeditor5-utils'; import BalloonPanelView from '../panel/balloon/balloonpanelview'; import IconView from '../icon/iconview'; import View from '../view'; import poweredByIcon from '../../theme/icons/project-logo.svg'; +import type { UiConfig } from '@ckeditor/ckeditor5-core/src/editor/editorconfig'; const POWERED_BY_VIEW_SYMBOL = Symbol( '_poweredByView' ); const POWERED_BY_BALLOON_SYMBOL = Symbol( '_poweredByBalloon' ); const ICON_WIDTH = 53; const ICON_HEIGHT = 10; -const CORNER_OFFSET = 5; const NARROW_ROOT_WIDTH_THRESHOLD = 250; const OFF_THE_SCREEN_POSITION = { top: -9999999, @@ -27,6 +33,8 @@ const OFF_THE_SCREEN_POSITION = { name: 'invalid' }; +type PoweredByConfig = Required[ 'poweredBy' ]; + /** * A helper that enables the "powered by" feature in the editor and renders a link to the project's * webpage next to the bottom of the editing root when the editor is focused. @@ -229,10 +237,11 @@ function getBalloonAttachOptions( editor: Editor ): Partial | n return null; } - const positioningFunction = editor.locale.contentLanguageDirection === 'ltr' ? - getLowerRightCornerPosition() : + const poweredByConfig = getNormalizedConfig( editor )!; + const positioningFunction = poweredByConfig.side === 'right' ? + getLowerRightCornerPosition( focusedDomRoot, poweredByConfig ) : /* istanbul ignore next -- @preserve */ - getLowerLeftCornerPosition(); + getLowerLeftCornerPosition( focusedDomRoot, poweredByConfig ); return { target: focusedDomRoot, @@ -240,18 +249,22 @@ function getBalloonAttachOptions( editor: Editor ): Partial | n }; } -function getLowerRightCornerPosition() { - return getLowerCornerPosition( ( rootRect, balloonRect ) => { - return rootRect.left + rootRect.width - balloonRect.width - CORNER_OFFSET; +function getLowerRightCornerPosition( focusedDomRoot: HTMLElement, config: PoweredByConfig ) { + return getLowerCornerPosition( focusedDomRoot, config, ( rootRect, balloonRect ) => { + return rootRect.left + rootRect.width - balloonRect.width - config.horizontalOffset; } ); } /* istanbul ignore next -- @preserve */ -function getLowerLeftCornerPosition() { - return getLowerCornerPosition( rootRect => rootRect.left + CORNER_OFFSET ); +function getLowerLeftCornerPosition( focusedDomRoot: HTMLElement, config: PoweredByConfig ) { + return getLowerCornerPosition( focusedDomRoot, config, rootRect => rootRect.left + config.horizontalOffset ); } -function getLowerCornerPosition( getBalloonLeft: ( rootRect: Rect, balloonRect: Rect ) => number ) { +function getLowerCornerPosition( + focusedDomRoot: HTMLElement, + config: PoweredByConfig, + getBalloonLeft: ( rootRect: Rect, balloonRect: Rect ) => number +) { return ( rootRect: Rect, balloonRect: Rect ) => { const visibleRootRect = rootRect.getVisible(); @@ -262,21 +275,45 @@ function getLowerCornerPosition( getBalloonLeft: ( rootRect: Rect, balloonRect: } const isRootNarrow = rootRect.width < NARROW_ROOT_WIDTH_THRESHOLD; - const balloonTop = rootRect.bottom - balloonRect.height - CORNER_OFFSET; + + let balloonTop; + + if ( config.position === 'inside' ) { + balloonTop = rootRect.bottom - balloonRect.height; + } else { + balloonTop = rootRect.bottom - balloonRect.height / 2; + } + + balloonTop -= config.verticalOffset; + const balloonLeft = getBalloonLeft( rootRect, balloonRect ); - const newBalloonRect = balloonRect.clone().moveTo( balloonLeft, balloonTop ); - // The watermark cannot be positioned in this corner because the corner is not quite visible. - /* istanbul ignore next -- @preserve */ - if ( newBalloonRect.getIntersectionArea( visibleRootRect ) < newBalloonRect.getArea() ) { - return OFF_THE_SCREEN_POSITION; + if ( config.position === 'inside' ) { + const newBalloonRect = balloonRect.clone().moveTo( balloonLeft, balloonTop ); + + // The watermark cannot be positioned in this corner because the corner is not quite visible. + /* istanbul ignore next -- @preserve */ + if ( newBalloonRect.getIntersectionArea( visibleRootRect ) < newBalloonRect.getArea() ) { + return OFF_THE_SCREEN_POSITION; + } + } else { + const firstScrollableRootAncestor = findClosestScrollableAncestor( focusedDomRoot ); + + if ( firstScrollableRootAncestor ) { + const firstScrollableRootAncestorRect = new Rect( firstScrollableRootAncestor ); + + // The watermark cannot be positioned in this corner because the corner is "not visible enough". + if ( visibleRootRect.bottom + balloonRect.height / 2 > firstScrollableRootAncestorRect.bottom ) { + return OFF_THE_SCREEN_POSITION; + } + } } /* istanbul ignore next -- @preserve */ return { top: balloonTop, left: balloonLeft, - name: isRootNarrow ? 'narrow' : 'default' + name: `root-width_${ isRootNarrow ? 'narrow' : 'default' }-position_${ config.position }-side_${ config.side }` }; }; } @@ -290,3 +327,16 @@ function getFocusedDOMRoot( editor: Editor ) { return null; } + +function getNormalizedConfig( editor: Editor ): PoweredByConfig { + const userConfig = editor.config.get( 'ui.poweredBy' ); + const position = userConfig && userConfig.position || 'inside'; + + return { + position, + verticalOffset: position === 'inside' ? 5 : 0, + horizontalOffset: 5, + side: editor.locale.contentLanguageDirection === 'ltr' ? 'right' : 'left', + ...userConfig + }; +} diff --git a/packages/ckeditor5-ui/tests/manual/poweredby/poweredby.html b/packages/ckeditor5-ui/tests/manual/poweredby/poweredby.html index 949abbba7d0..7ef3998fa6d 100644 --- a/packages/ckeditor5-ui/tests/manual/poweredby/poweredby.html +++ b/packages/ckeditor5-ui/tests/manual/poweredby/poweredby.html @@ -129,3 +129,28 @@

Confidence

to be afraid of, is the moment you discover bliss.

+ +

Configurable position: on border

+
+

Like all the great things on earth traveling teaches us by example. Here are some of the most precious lessons I’ve

+
+ +

Configurable offset (default position)

+
+

Like all the great things on earth traveling teaches us by example. Here are some of the most precious lessons I’ve

+
+ +

Configurable offset (default position)

+
+

Like all the great things on earth traveling teaches us by example. Here are some of the most precious lessons I’ve

+
+ +

Custom side (default position)

+
+

Like all the great things on earth traveling teaches us by example. Here are some of the most precious lessons I’ve

+
+ +

Custom side (on border position)

+
+

Like all the great things on earth traveling teaches us by example. Here are some of the most precious lessons I’ve

+
diff --git a/packages/ckeditor5-ui/tests/manual/poweredby/poweredby.js b/packages/ckeditor5-ui/tests/manual/poweredby/poweredby.js index 17f6ca285ef..f82df014ba5 100644 --- a/packages/ckeditor5-ui/tests/manual/poweredby/poweredby.js +++ b/packages/ckeditor5-ui/tests/manual/poweredby/poweredby.js @@ -10,39 +10,45 @@ import ArticlePluginSet from '@ckeditor/ckeditor5-core/tests/_utils/articleplugi window.editors = {}; -function createEditor( selector ) { +function createEditor( selector, poweredByConfig ) { + const config = { + plugins: [ ArticlePluginSet ], + toolbar: [ + 'heading', + '|', + 'bold', + 'italic', + 'link', + 'bulletedList', + 'numberedList', + '|', + 'outdent', + 'indent', + '|', + 'blockQuote', + 'insertTable', + 'mediaEmbed', + 'undo', + 'redo' + ], + image: { + toolbar: [ 'imageStyle:inline', 'imageStyle:block', 'imageStyle:side', '|', 'imageTextAlternative' ] + }, + table: { + contentToolbar: [ + 'tableColumn', + 'tableRow', + 'mergeTableCells' + ] + } + }; + + if ( poweredByConfig ) { + config.ui = { poweredBy: poweredByConfig }; + } + ClassicEditor - .create( document.querySelector( selector ), { - plugins: [ ArticlePluginSet ], - toolbar: [ - 'heading', - '|', - 'bold', - 'italic', - 'link', - 'bulletedList', - 'numberedList', - '|', - 'outdent', - 'indent', - '|', - 'blockQuote', - 'insertTable', - 'mediaEmbed', - 'undo', - 'redo' - ], - image: { - toolbar: [ 'imageStyle:inline', 'imageStyle:block', 'imageStyle:side', '|', 'imageTextAlternative' ] - }, - table: { - contentToolbar: [ - 'tableColumn', - 'tableRow', - 'mergeTableCells' - ] - } - } ) + .create( document.querySelector( selector ), config ) .then( editor => { window.editors[ selector ] = editor; @@ -61,3 +67,22 @@ createEditor( '#narrow' ); createEditor( '#narrow-dark-bg' ); createEditor( '#padding-less' ); createEditor( '#overflow-parent' ); +createEditor( '#position-border', { + position: 'border' +} ); +createEditor( '#custom-offset-default', { + verticalOffset: 20, + horizontalOffset: 75 +} ); +createEditor( '#custom-offset-on-border', { + position: 'border', + verticalOffset: -5, + horizontalOffset: 0 +} ); +createEditor( '#custom-side-default', { + side: 'left' +} ); +createEditor( '#custom-side-on-border', { + position: 'border', + side: 'left' +} ); diff --git a/packages/ckeditor5-ui/theme/globals/_poweredby.css b/packages/ckeditor5-ui/theme/globals/_poweredby.css index e32cbe339ca..51d908c3578 100644 --- a/packages/ckeditor5-ui/theme/globals/_poweredby.css +++ b/packages/ckeditor5-ui/theme/globals/_poweredby.css @@ -10,6 +10,7 @@ --ck-powered-by-text-color: hsl(0, 0%, 31%); --ck-powered-by-border-radius: 100px; --ck-powered-by-background: hsl(0, 0%, 100%); + --ck-powered-by-border-color: var(--ck-color-focus-border); } .ck.ck-balloon-panel.ck-powered-by-balloon { @@ -64,7 +65,7 @@ } } - &.ck-balloon-panel_narrow { + &[class*="root-width_narrow"] { /* Label sliding out on :hover when the editor is narrow */ & .ck.ck-powered-by .ck-powered-by__label { visibility: hidden; @@ -89,5 +90,36 @@ transform: translateX(-100%); } } + + &[class*="position_border"] { + border: var(--ck-focus-ring); + + &[class*="narrow"] { + & .ck.ck-powered-by .ck-powered-by__label { + top: -1px; + bottom: -1px; + border-width: 1px 0 1px 1px; + border-style: solid; + border-color: var(--ck-powered-by-border-color); + } + } + } + + &[class*="side_left"][class*="root-width_narrow"] { + & .ck.ck-powered-by .ck-powered-by__label { + position: static; + border: none; + padding: 0; + margin: 0; + } + + &:hover .ck.ck-powered-by .ck-powered-by__label { + visibility: visible; + max-width: auto; + transform: translateX(0%); + padding: 0 0 0 2px; + margin-right: 4px; + } + } } From 517cfd99c9b67fdd7dc87506ef2e0ce8ddc8981a Mon Sep 17 00:00:00 2001 From: Aleksander Nowodzinski Date: Mon, 8 May 2023 17:17:12 +0200 Subject: [PATCH 22/42] Docs. --- .../ckeditor5-core/src/editor/editorconfig.ts | 59 ++++++++++++++++++- 1 file changed, 57 insertions(+), 2 deletions(-) diff --git a/packages/ckeditor5-core/src/editor/editorconfig.ts b/packages/ckeditor5-core/src/editor/editorconfig.ts index 9fb005bab51..0bfee92972b 100644 --- a/packages/ckeditor5-core/src/editor/editorconfig.ts +++ b/packages/ckeditor5-core/src/editor/editorconfig.ts @@ -479,11 +479,34 @@ export interface EditorConfig { * ([learn more](TODO)). It allows customizing the position of the logo to minimize the risk of collision with editor content * and UI. * + * The following configuration properties are supported: + * + * * **`position`** – Position of the project's logo (default: `'inside'`). + * * When `'inside'` the logo will be displayed within the boundaries of the editing area. + * * When `'border'` the logo will be displayed over the bottom border of the editing area. + * + * * **`side`** (`'left'` or `'right'`, default: `'right'`) – Allows choosing the side of the editing area the + * logo will be displayed to. + * + * **Note**: If {@link module:core/editor/editorconfig~EditorConfig#language `config.language`} is set to an RTL (right-to-left) + * language, the side switches to `'left'` by default. + * + * * **`verticalOffset`** (default: `5`) – The vertical distance the logo can be moved away from its default position. + * + * **Note**: If `position` is `'border'`, the offset is measured from the (vertical) center of the logo. + * + * * **`horizontalOffset`** (default: `5`) – The horizontal distance between the side of the editing root and the + * nearest side of the logo. + * * ```ts * ui: { - * poweredBy: { position: 'border', side: 'left', verticalOffset: 2, horizontalOffset: 30 } + * poweredBy: { + * position: 'border', + * side: 'left', + * verticalOffset: 2, + * horizontalOffset: 30 + * } * } - * ``` */ ui?: UiConfig; @@ -573,9 +596,41 @@ export interface UiConfig { }; poweredBy?: { + + /** + * Position of the project's logo. + * + * * When `'inside'` the logo will be displayed within the boundaries of the editing area. + * * When `'border'` the logo will be displayed over the bottom border of the editing area. + * + * @default 'inside' + */ position: 'inside' | 'border'; + + /** + * Allows choosing the side of the editing area the logo will be displayed to. + * + * **Note:** If {@link module:core/editor/editorconfig~EditorConfig#language `config.language`} is set to an RTL (right-to-left) + * language, the side switches to `'left'` by default. + * + * @default 'right' + */ side: 'left' | 'right'; + + /** + * The vertical distance the logo can be moved away from its default position. + * + * **Note:** If `position` is `'border'`, the offset is measured from the (vertical) center of the logo. + * + * @default 5 + */ verticalOffset: number; + + /** + * The horizontal distance between the side of the editing root and the nearest side of the logo. + * + * @default 5 + */ horizontalOffset: number; }; } From 63893e2a4799d10f29f2e8f564cb8e09371211d0 Mon Sep 17 00:00:00 2001 From: Aleksander Nowodzinski Date: Mon, 8 May 2023 17:27:33 +0200 Subject: [PATCH 23/42] Added istanbul ignores for "100%" CC. --- packages/ckeditor5-ui/src/editorui/poweredby.ts | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/packages/ckeditor5-ui/src/editorui/poweredby.ts b/packages/ckeditor5-ui/src/editorui/poweredby.ts index 08671b26d2f..3cd3eceaf83 100644 --- a/packages/ckeditor5-ui/src/editorui/poweredby.ts +++ b/packages/ckeditor5-ui/src/editorui/poweredby.ts @@ -278,9 +278,11 @@ function getLowerCornerPosition( let balloonTop; + /* istanbul ignore else -- @preserve */ if ( config.position === 'inside' ) { balloonTop = rootRect.bottom - balloonRect.height; - } else { + } + else { balloonTop = rootRect.bottom - balloonRect.height / 2; } @@ -288,6 +290,7 @@ function getLowerCornerPosition( const balloonLeft = getBalloonLeft( rootRect, balloonRect ); + /* istanbul ignore else -- @preserve */ if ( config.position === 'inside' ) { const newBalloonRect = balloonRect.clone().moveTo( balloonLeft, balloonTop ); @@ -296,13 +299,17 @@ function getLowerCornerPosition( if ( newBalloonRect.getIntersectionArea( visibleRootRect ) < newBalloonRect.getArea() ) { return OFF_THE_SCREEN_POSITION; } - } else { + } + else { + /* istanbul ignore next -- @preserve */ const firstScrollableRootAncestor = findClosestScrollableAncestor( focusedDomRoot ); + /* istanbul ignore next -- @preserve */ if ( firstScrollableRootAncestor ) { const firstScrollableRootAncestorRect = new Rect( firstScrollableRootAncestor ); // The watermark cannot be positioned in this corner because the corner is "not visible enough". + /* istanbul ignore next -- @preserve */ if ( visibleRootRect.bottom + balloonRect.height / 2 > firstScrollableRootAncestorRect.bottom ) { return OFF_THE_SCREEN_POSITION; } @@ -330,13 +337,15 @@ function getFocusedDOMRoot( editor: Editor ) { function getNormalizedConfig( editor: Editor ): PoweredByConfig { const userConfig = editor.config.get( 'ui.poweredBy' ); + /* istanbul ignore next -- @preserve */ const position = userConfig && userConfig.position || 'inside'; return { position, - verticalOffset: position === 'inside' ? 5 : 0, + verticalOffset: position === 'inside' ? 5 : /* istanbul ignore next -- @preserve */ 0, horizontalOffset: 5, - side: editor.locale.contentLanguageDirection === 'ltr' ? 'right' : 'left', + + side: editor.locale.contentLanguageDirection === 'ltr' ? 'right' : /* istanbul ignore next -- @preserve */ 'left', ...userConfig }; } From 35387b2b285e2441e0b86baa5d26549138a60cf3 Mon Sep 17 00:00:00 2001 From: Aleksander Nowodzinski Date: Tue, 9 May 2023 09:13:19 +0200 Subject: [PATCH 24/42] Docs. --- packages/ckeditor5-ui/src/editorui/editorui.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ckeditor5-ui/src/editorui/editorui.ts b/packages/ckeditor5-ui/src/editorui/editorui.ts index b654550c5c4..7456c164c88 100644 --- a/packages/ckeditor5-ui/src/editorui/editorui.ts +++ b/packages/ckeditor5-ui/src/editorui/editorui.ts @@ -53,7 +53,7 @@ export default abstract class EditorUI extends ObservableMixin() { public readonly tooltipManager: TooltipManager; /** - * TODO. + * A helper that enables the "powered by" feature in the editor and renders a link to the project's webpage. */ public readonly poweredBy: PoweredBy; From cb5f92857e00ea122380309abaf97db4ce8afe40 Mon Sep 17 00:00:00 2001 From: Marek Lewandowski Date: Tue, 9 May 2023 09:58:14 +0200 Subject: [PATCH 25/42] Function simplification. --- packages/ckeditor5-ui/src/editorui/poweredby.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/ckeditor5-ui/src/editorui/poweredby.ts b/packages/ckeditor5-ui/src/editorui/poweredby.ts index 3acc3693b5f..580a9dee81d 100644 --- a/packages/ckeditor5-ui/src/editorui/poweredby.ts +++ b/packages/ckeditor5-ui/src/editorui/poweredby.ts @@ -283,7 +283,8 @@ function getLowerCornerPosition( getBalloonLeft: ( rootRect: Rect, balloonRect: function getFocusedDOMRoot( editor: Editor ) { for ( const [ , domRoot ] of editor.editing.view.domRoots ) { - if ( domRoot.ownerDocument.activeElement === domRoot || domRoot.contains( domRoot.ownerDocument.activeElement ) ) { + const { activeElement } = domRoot.ownerDocument; + if ( activeElement === domRoot || domRoot.contains( activeElement ) ) { return domRoot; } } From 283e81b0490c6e4cb9273983674e361d3855e2d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotr=20Szcz=C4=99=C5=9Bniak?= Date: Tue, 9 May 2023 10:01:25 +0200 Subject: [PATCH 26/42] Fix a single test and back to full cc. --- packages/ckeditor5-utils/tests/madewith.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/ckeditor5-utils/tests/madewith.js b/packages/ckeditor5-utils/tests/madewith.js index ad996065772..a09064539da 100644 --- a/packages/ckeditor5-utils/tests/madewith.js +++ b/packages/ckeditor5-utils/tests/madewith.js @@ -44,7 +44,8 @@ describe( 'utils', () => { it( 'when date is invalid', () => { // invalid = shorten than expected - const string = 'bGRtb3RyZWlhZWxzcG9taXRtdXJzby1NVGs1TnpFeA=='; + + const string = 'enN6YXJ0YWxhYWZsaWViYnRvcnVpb3Jvb3BzYmVkYW9tcm1iZm9vbS1NVGs1TnpFeA=='; expect( verify( string ) ).to.be.equal( 'INVALID' ); } ); From 6434a217b84a4e585da7c43e941c2b208b38d48d Mon Sep 17 00:00:00 2001 From: Aleksander Nowodzinski Date: Tue, 9 May 2023 10:39:08 +0200 Subject: [PATCH 27/42] Update packages/ckeditor5-ui/tests/manual/poweredby/poweredby.md Co-authored-by: Marek Lewandowski --- packages/ckeditor5-ui/tests/manual/poweredby/poweredby.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ckeditor5-ui/tests/manual/poweredby/poweredby.md b/packages/ckeditor5-ui/tests/manual/poweredby/poweredby.md index 457d669bd67..e7cbfb99305 100644 --- a/packages/ckeditor5-ui/tests/manual/poweredby/poweredby.md +++ b/packages/ckeditor5-ui/tests/manual/poweredby/poweredby.md @@ -1,4 +1,4 @@ -# Link to project's homepage rendered next to the editing root. +# Powered by rendered inside of the editing root. 1. Make sure the "powered by" link renders next to the bottom edge of the editing root when focused. 1. Make sure the link opens the page in a new tab. From 6e73bad9933c1f244ff9fa2b8cc7936eab518d01 Mon Sep 17 00:00:00 2001 From: Aleksander Nowodzinski Date: Tue, 9 May 2023 10:40:07 +0200 Subject: [PATCH 28/42] Update packages/ckeditor5-ui/tests/manual/poweredby/poweredby.md Co-authored-by: Marek Lewandowski --- packages/ckeditor5-ui/tests/manual/poweredby/poweredby.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ckeditor5-ui/tests/manual/poweredby/poweredby.md b/packages/ckeditor5-ui/tests/manual/poweredby/poweredby.md index e7cbfb99305..c7984c257f1 100644 --- a/packages/ckeditor5-ui/tests/manual/poweredby/poweredby.md +++ b/packages/ckeditor5-ui/tests/manual/poweredby/poweredby.md @@ -1,6 +1,6 @@ # Powered by rendered inside of the editing root. -1. Make sure the "powered by" link renders next to the bottom edge of the editing root when focused. +1. Make sure the "powered by" link renders above the bottom edge of the editing root when focused. 1. Make sure the link opens the page in a new tab. 1. Make sure the link disappears as soon as the editor gets blurred. 1. Make sure the link disappears when the editor is cropped by an ancestor with `overflow`. From 3ba0c9b3dbc389af71ee92735a6a26cb7044a0ba Mon Sep 17 00:00:00 2001 From: Aleksander Nowodzinski Date: Tue, 9 May 2023 10:55:35 +0200 Subject: [PATCH 29/42] Code review refactoring. --- .../ckeditor5-ui/src/editorui/poweredby.ts | 79 ++++++++++--------- 1 file changed, 40 insertions(+), 39 deletions(-) diff --git a/packages/ckeditor5-ui/src/editorui/poweredby.ts b/packages/ckeditor5-ui/src/editorui/poweredby.ts index 580a9dee81d..0ae8d839c45 100644 --- a/packages/ckeditor5-ui/src/editorui/poweredby.ts +++ b/packages/ckeditor5-ui/src/editorui/poweredby.ts @@ -16,7 +16,6 @@ import View from '../view'; import poweredByIcon from '../../theme/icons/project-logo.svg'; const POWERED_BY_VIEW_SYMBOL = Symbol( '_poweredByView' ); -const POWERED_BY_BALLOON_SYMBOL = Symbol( '_poweredByBalloon' ); const ICON_WIDTH = 53; const ICON_HEIGHT = 10; const CORNER_OFFSET = 5; @@ -31,9 +30,6 @@ const OFF_THE_SCREEN_POSITION = { * A helper that enables the "powered by" feature in the editor and renders a link to the project's * webpage next to the bottom of the editing root when the editor is focused. * - * The helper uses a {@link module:ui/panel/balloon/balloonpanelview~BalloonPanelView balloon panel} - * to position the link with the logo. - * * @private */ export default class PoweredBy extends DomEmitterMixin() { @@ -45,12 +41,12 @@ export default class PoweredBy extends DomEmitterMixin() { /** * A reference to the balloon panel hosting and positioning the "powered by" view. */ - private [ POWERED_BY_BALLOON_SYMBOL ]: BalloonPanelView | null; + private _balloonView: BalloonPanelView | null; /** * Editor instance the helper was created for. */ - private editor: Editor | null; + private readonly editor: Editor; /** * Creates a "powered by" helper for a given editor. The feature is initialized on Editor#ready @@ -64,7 +60,7 @@ export default class PoweredBy extends DomEmitterMixin() { this.editor = editor; this[ POWERED_BY_VIEW_SYMBOL ] = new PoweredByView( editor.locale ); - this[ POWERED_BY_BALLOON_SYMBOL ] = null; + this._balloonView = null; editor.on( 'ready', this._handleEditorReady.bind( this ) ); } @@ -73,13 +69,13 @@ export default class PoweredBy extends DomEmitterMixin() { * Destroys the "powered by" helper along with its view. */ public destroy(): void { - const editor = this.editor!; - const balloon = this[ POWERED_BY_BALLOON_SYMBOL ]; + const editor = this.editor; + const balloon = this._balloonView; const view = this[ POWERED_BY_VIEW_SYMBOL ]; if ( balloon ) { balloon.unpin(); - editor!.ui.view.body.remove( balloon ); + editor.ui.view.body.remove( balloon ); balloon.destroy(); } @@ -89,34 +85,25 @@ export default class PoweredBy extends DomEmitterMixin() { this.stopListening(); - this.editor = this[ POWERED_BY_VIEW_SYMBOL ] = this[ POWERED_BY_BALLOON_SYMBOL ] = null; + this[ POWERED_BY_VIEW_SYMBOL ] = this._balloonView = null; } /** * Enables "powered by" label once the editor (ui) is ready. */ private _handleEditorReady(): void { - const editor = this.editor!; + const editor = this.editor; + // No view means no body collection to append the powered by balloon to. if ( !editor.ui.view ) { return; } - let balloon: BalloonPanelView | undefined; - editor.ui.focusTracker.on( 'change:isFocused', ( evt, data, isFocused ) => { - if ( !balloon ) { - balloon = this._createBalloonAndView(); - } - if ( isFocused ) { - const attachOptions = getBalloonAttachOptions( editor ); - - if ( attachOptions ) { - balloon.pin( attachOptions ); - } + this._showBalloon(); } else { - balloon.unpin(); + this._hideBalloon(); } } ); @@ -125,17 +112,7 @@ export default class PoweredBy extends DomEmitterMixin() { return; } - /* istanbul ignore next -- @preserve */ - if ( !balloon ) { - balloon = this._createBalloonAndView(); - } - - const attachOptions = getBalloonAttachOptions( editor ); - - if ( attachOptions ) { - balloon.unpin(); - balloon.pin( attachOptions ); - } + this._showBalloon(); } ); // TODO: ~~Support for cases where the watermark gets cropped by parent with overflow: hidden~~. @@ -150,9 +127,9 @@ export default class PoweredBy extends DomEmitterMixin() { * Creates an instance of the {@link module:ui/panel/balloon/balloonpanelview~BalloonPanelView balloon panel} * with the "powered by" view inside ready for positioning. */ - private _createBalloonAndView(): BalloonPanelView { - const editor = this.editor!; - const balloon = this[ POWERED_BY_BALLOON_SYMBOL ] = new BalloonPanelView(); + private _createBalloonView() { + const editor = this.editor; + const balloon = this._balloonView = new BalloonPanelView(); balloon.content.add( this[ POWERED_BY_VIEW_SYMBOL ]! ); balloon.withArrow = false; @@ -161,7 +138,31 @@ export default class PoweredBy extends DomEmitterMixin() { editor.ui.view.body.add( balloon ); editor.ui.focusTracker.add( balloon.element! ); - return balloon; + this._balloonView = balloon; + } + + /** + * Attempts to display the balloon with the "powered by" view. + */ + private _showBalloon() { + const attachOptions = getBalloonAttachOptions( this.editor ); + + if ( attachOptions ) { + if ( !this._balloonView ) { + this._createBalloonView(); + } + + this._balloonView!.pin( attachOptions ); + } + } + + /** + * Hides the "powered by" balloon if already visible. + */ + private _hideBalloon() { + if ( this._balloonView ) { + this._balloonView!.unpin(); + } } } From 36c72632b233ac4e2816dc0879d63e093cab4ca5 Mon Sep 17 00:00:00 2001 From: Aleksander Nowodzinski Date: Tue, 9 May 2023 10:59:48 +0200 Subject: [PATCH 30/42] Disabled dragging of the powered by label. --- packages/ckeditor5-ui/src/editorui/poweredby.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/ckeditor5-ui/src/editorui/poweredby.ts b/packages/ckeditor5-ui/src/editorui/poweredby.ts index 0ae8d839c45..2e48fd483df 100644 --- a/packages/ckeditor5-ui/src/editorui/poweredby.ts +++ b/packages/ckeditor5-ui/src/editorui/poweredby.ts @@ -179,6 +179,7 @@ class PoweredByView extends View { super( locale ); const iconView = new IconView(); + const bind = this.bindTemplate; iconView.set( { content: poweredByIcon, @@ -216,7 +217,10 @@ class PoweredByView extends View { children: [ 'Powered by' ] }, iconView - ] + ], + on: { + dragstart: bind.to( evt => evt.preventDefault() ) + } } ] } ); From d37a2a3888066a1b4868f26b3d0faf4a4641ed73 Mon Sep 17 00:00:00 2001 From: Aleksander Nowodzinski Date: Tue, 9 May 2023 11:04:12 +0200 Subject: [PATCH 31/42] Code refactoring. --- .../ckeditor5-ui/src/editorui/poweredby.ts | 23 ++++--------------- 1 file changed, 4 insertions(+), 19 deletions(-) diff --git a/packages/ckeditor5-ui/src/editorui/poweredby.ts b/packages/ckeditor5-ui/src/editorui/poweredby.ts index 2e48fd483df..39155ab8266 100644 --- a/packages/ckeditor5-ui/src/editorui/poweredby.ts +++ b/packages/ckeditor5-ui/src/editorui/poweredby.ts @@ -15,7 +15,6 @@ import View from '../view'; import poweredByIcon from '../../theme/icons/project-logo.svg'; -const POWERED_BY_VIEW_SYMBOL = Symbol( '_poweredByView' ); const ICON_WIDTH = 53; const ICON_HEIGHT = 10; const CORNER_OFFSET = 5; @@ -34,12 +33,7 @@ const OFF_THE_SCREEN_POSITION = { */ export default class PoweredBy extends DomEmitterMixin() { /** - * A reference to the view displaying a link with a label and a project logo. - */ - private [ POWERED_BY_VIEW_SYMBOL ]: PoweredByView | null; - - /** - * A reference to the balloon panel hosting and positioning the "powered by" view. + * A reference to the balloon panel hosting and positioning the "powered by" link and logo. */ private _balloonView: BalloonPanelView | null; @@ -58,8 +52,6 @@ export default class PoweredBy extends DomEmitterMixin() { super(); this.editor = editor; - - this[ POWERED_BY_VIEW_SYMBOL ] = new PoweredByView( editor.locale ); this._balloonView = null; editor.on( 'ready', this._handleEditorReady.bind( this ) ); @@ -69,23 +61,15 @@ export default class PoweredBy extends DomEmitterMixin() { * Destroys the "powered by" helper along with its view. */ public destroy(): void { - const editor = this.editor; const balloon = this._balloonView; - const view = this[ POWERED_BY_VIEW_SYMBOL ]; if ( balloon ) { balloon.unpin(); - editor.ui.view.body.remove( balloon ); balloon.destroy(); - } - - if ( view ) { - view.destroy(); + this._balloonView = null; } this.stopListening(); - - this[ POWERED_BY_VIEW_SYMBOL ] = this._balloonView = null; } /** @@ -130,8 +114,9 @@ export default class PoweredBy extends DomEmitterMixin() { private _createBalloonView() { const editor = this.editor; const balloon = this._balloonView = new BalloonPanelView(); + const view = new PoweredByView( editor.locale ); - balloon.content.add( this[ POWERED_BY_VIEW_SYMBOL ]! ); + balloon.content.add( view ); balloon.withArrow = false; balloon.class = 'ck-powered-by-balloon'; From 2fe048ecc61f1c0728149316512541a092986f8c Mon Sep 17 00:00:00 2001 From: Aleksander Nowodzinski Date: Tue, 9 May 2023 11:10:36 +0200 Subject: [PATCH 32/42] Throttled balloon repositioning to avoid performance loss. --- packages/ckeditor5-ui/src/editorui/poweredby.ts | 12 +++++++++--- .../ckeditor5-ui/tests/manual/poweredby/poweredby.js | 3 ++- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/packages/ckeditor5-ui/src/editorui/poweredby.ts b/packages/ckeditor5-ui/src/editorui/poweredby.ts index 39155ab8266..759016a7922 100644 --- a/packages/ckeditor5-ui/src/editorui/poweredby.ts +++ b/packages/ckeditor5-ui/src/editorui/poweredby.ts @@ -12,6 +12,7 @@ import { DomEmitterMixin, type PositionOptions, type Locale, type Rect } from '@ import BalloonPanelView from '../panel/balloon/balloonpanelview'; import IconView from '../icon/iconview'; import View from '../view'; +import { throttle, type DebouncedFunc } from 'lodash-es'; import poweredByIcon from '../../theme/icons/project-logo.svg'; @@ -42,6 +43,11 @@ export default class PoweredBy extends DomEmitterMixin() { */ private readonly editor: Editor; + /** + * A throttled version of the {@link #_showBalloon} method meant for frequent use to avoid performance loss. + */ + private _showBalloonThrottled: DebouncedFunc<() => void>; + /** * Creates a "powered by" helper for a given editor. The feature is initialized on Editor#ready * event. @@ -53,6 +59,7 @@ export default class PoweredBy extends DomEmitterMixin() { this.editor = editor; this._balloonView = null; + this._showBalloonThrottled = throttle( this._showBalloon.bind( this ), 50 ); editor.on( 'ready', this._handleEditorReady.bind( this ) ); } @@ -69,6 +76,7 @@ export default class PoweredBy extends DomEmitterMixin() { this._balloonView = null; } + this._showBalloonThrottled.cancel(); this.stopListening(); } @@ -96,11 +104,9 @@ export default class PoweredBy extends DomEmitterMixin() { return; } - this._showBalloon(); + this._showBalloonThrottled(); } ); - // TODO: ~~Support for cases where the watermark gets cropped by parent with overflow: hidden~~. - // TODO: Debounce. // TODO: Probably hide during scroll. // TODO: Problem with Rect#isVisible() and floating editors (comments) vs. hiding the view when cropped by parent with overflow. // TODO: Update position once an image loaded. diff --git a/packages/ckeditor5-ui/tests/manual/poweredby/poweredby.js b/packages/ckeditor5-ui/tests/manual/poweredby/poweredby.js index 17f6ca285ef..ef20bb9a371 100644 --- a/packages/ckeditor5-ui/tests/manual/poweredby/poweredby.js +++ b/packages/ckeditor5-ui/tests/manual/poweredby/poweredby.js @@ -7,13 +7,14 @@ import ClassicEditor from '@ckeditor/ckeditor5-editor-classic/src/classiceditor'; import ArticlePluginSet from '@ckeditor/ckeditor5-core/tests/_utils/articlepluginset'; +import { ImageResize } from '@ckeditor/ckeditor5-image'; window.editors = {}; function createEditor( selector ) { ClassicEditor .create( document.querySelector( selector ), { - plugins: [ ArticlePluginSet ], + plugins: [ ArticlePluginSet, ImageResize ], toolbar: [ 'heading', '|', From 44be86d9d74a07648baae51433ee5e9b73465c5e Mon Sep 17 00:00:00 2001 From: Aleksander Nowodzinski Date: Tue, 9 May 2023 11:13:38 +0200 Subject: [PATCH 33/42] Brought CC back to 100%. --- packages/ckeditor5-ui/src/editorui/poweredby.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/ckeditor5-ui/src/editorui/poweredby.ts b/packages/ckeditor5-ui/src/editorui/poweredby.ts index 759016a7922..c2496fddc04 100644 --- a/packages/ckeditor5-ui/src/editorui/poweredby.ts +++ b/packages/ckeditor5-ui/src/editorui/poweredby.ts @@ -210,7 +210,10 @@ class PoweredByView extends View { iconView ], on: { - dragstart: bind.to( evt => evt.preventDefault() ) + dragstart: bind.to( + /* istanbul ignore next -- @preserve */ + evt => evt.preventDefault() + ) } } ] From ea0d3615fe12a939db7ac5ea098cdcddb295f8d3 Mon Sep 17 00:00:00 2001 From: Marek Lewandowski Date: Tue, 9 May 2023 11:25:24 +0200 Subject: [PATCH 34/42] Make sure to do the first powered render on the leading edge of UI's update render. Reason is that we want to have the first render as fast as possible. --- packages/ckeditor5-ui/src/editorui/poweredby.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ckeditor5-ui/src/editorui/poweredby.ts b/packages/ckeditor5-ui/src/editorui/poweredby.ts index c2496fddc04..21163054f08 100644 --- a/packages/ckeditor5-ui/src/editorui/poweredby.ts +++ b/packages/ckeditor5-ui/src/editorui/poweredby.ts @@ -59,7 +59,7 @@ export default class PoweredBy extends DomEmitterMixin() { this.editor = editor; this._balloonView = null; - this._showBalloonThrottled = throttle( this._showBalloon.bind( this ), 50 ); + this._showBalloonThrottled = throttle( this._showBalloon.bind( this ), 50, { leading: true } ); editor.on( 'ready', this._handleEditorReady.bind( this ) ); } From 5042b3b25dd921b6fcf5c682d107c3df07b62957 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotr=20Szcz=C4=99=C5=9Bniak?= Date: Wed, 10 May 2023 09:42:45 +0200 Subject: [PATCH 35/42] Update the verify function to cover all edge cases. --- packages/ckeditor5-utils/src/madewith.ts | 32 ++++++++++++++++++---- packages/ckeditor5-utils/tests/madewith.js | 17 +++++++++++- 2 files changed, 43 insertions(+), 6 deletions(-) diff --git a/packages/ckeditor5-utils/src/madewith.ts b/packages/ckeditor5-utils/src/madewith.ts index 1ce88384e82..9e8782841ca 100644 --- a/packages/ckeditor5-utils/src/madewith.ts +++ b/packages/ckeditor5-utils/src/madewith.ts @@ -21,6 +21,14 @@ export type VerifiedKeyStatus = 'VALID' | 'INVALID'; * @returns String that represents the state of given `token` parameter. */ export default function verify( token: string ): VerifiedKeyStatus { + function oldTokenCheck( token: string ): VerifiedKeyStatus { + if ( token.match( /^[a-zA-Z0-9+/=$]+$/g ) && ( token.length >= 40 && token.length <= 255 ) ) { + return 'VALID'; + } else { + return 'INVALID'; + } + } + // TODO: issue ci#3175 let decryptedData = ''; let decryptedSecondElement = ''; @@ -36,19 +44,33 @@ export default function verify( token: string ): VerifiedKeyStatus { const firstElement = splittedDecryptedData[ 0 ]; const secondElement = splittedDecryptedData[ 1 ]; + if ( !secondElement ) { + return oldTokenCheck( token ); + } + try { - // Must be a valid format. - atob( firstElement ); + atob( secondElement ); } catch ( e ) { - return 'INVALID'; + try { + atob( firstElement ); + + if ( !atob( firstElement ).length ) { + return oldTokenCheck( token ); + } + } catch ( e ) { + return oldTokenCheck( token ); + } } if ( firstElement.length < 40 || firstElement.length > 255 ) { return 'INVALID'; } - if ( !secondElement ) { - return 'VALID'; + try { + // Must be a valid format. + atob( firstElement ); + } catch ( e ) { + return 'INVALID'; } try { diff --git a/packages/ckeditor5-utils/tests/madewith.js b/packages/ckeditor5-utils/tests/madewith.js index a09064539da..8851bec1816 100644 --- a/packages/ckeditor5-utils/tests/madewith.js +++ b/packages/ckeditor5-utils/tests/madewith.js @@ -23,6 +23,19 @@ describe( 'utils', () => { expect( verify( string ) ).to.be.equal( 'VALID' ); } ); + + it( 'when old token format is given with a special sign', () => { + const string = 'LWRsZ2h2bWxvdWhnbXZsa3ZkaGdzZGhtdmxrc2htZ3Nma2xnaGxtcDk4N212Z3V3OTU4NHc5bWdtdw=='; + + expect( verify( string ) ).to.be.equal( 'VALID' ); + } ); + + it( 'when old token is splitted', () => { + // eslint-disable-next-line max-len + const string = 'ZXNybGl1aG1jbGlldWdtbHdpZWgvIUAjNW1nbGNlXVtcd2l1Z2NsZWpnbWNsc2lkZmdjbHNpZGZoZ2xjc2Rnc25jZGZnaGNubHMtd3A5bWN5dDlwaGdtcGM5d2g4dGc3Y3doODdvaGddW10hQCMhdG5jN293NTg0aGdjbzhud2U4Z2Nodw=='; + + expect( verify( string ) ).to.be.equal( 'VALID' ); + } ); } ); describe( 'should return `INVALID`', () => { @@ -34,11 +47,13 @@ describe( 'utils', () => { it( 'first too long', () => { // eslint-disable-next-line max-len const string = 'YzNSbGJTQmxjbkp2Y2pvZ2JtVjBPanBGVWxKZlFreFBRMHRGUkY5Q1dWOURURWxGVGxSemRHVnRJR1Z5Y205eU9pQnVaWFE2T2tWU1VsOUNURTlEUzBWRVgwSlpYME5NU1VWT1ZITjBaVzBnWlhKeWIzSTZJRzVsZERvNlJWSlNYMEpNVDBOTFJVUmZRbGxmUTB4SlJVNVVjM1JsYlNCbGNuSnZjam9nYm1WME9qcEZVbEpmUWt4UFEwdEZSRjlDV1Y5RFRFbEZUbFJ6ZEdWdElHVnljbTl5T2lCdVpYUTZPa1ZTVWw5Q1RFOURTMFZFWDBKWlgwTk1TVVZPVkhOMFpXMGdaWEp5YjNJNklHNWxkRG82UlZKU1gwSk1UME5MUlVSZlFsbGZRMHhKUlU1VS1NakF5TlRBeE1ERT0='; + expect( verify( string ) ).to.be.equal( 'INVALID' ); } ); it( 'first wrong format', () => { - const string = 'YS1NakF5TlRBeE1ERT0='; + const string = 'ZGx1Z2hjbXNsaXVkZ2NobXN8IjolRVdFVnwifCJEVnxERyJXJSUkXkVSVHxWIll8UkRUIkJTfFIlQiItTWpBeU16RXlNekU9'; + expect( verify( string ) ).to.be.equal( 'INVALID' ); } ); From ecd7df10b35859fde819d328a9689f4c4a393216 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotr=20Szcz=C4=99=C5=9Bniak?= Date: Tue, 9 May 2023 14:58:12 +0200 Subject: [PATCH 36/42] Integrate check with PBC. --- .../ckeditor5-ui/src/editorui/poweredby.ts | 7 + .../manual/poweredby/poweredby-with-key.html | 148 ++++++++++++++++++ .../manual/poweredby/poweredby-with-key.js | 67 ++++++++ .../manual/poweredby/poweredby-with-key.md | 3 + .../tests/manual/poweredby/poweredby.md | 6 +- packages/ckeditor5-utils/src/index.ts | 1 + 6 files changed, 229 insertions(+), 3 deletions(-) create mode 100644 packages/ckeditor5-ui/tests/manual/poweredby/poweredby-with-key.html create mode 100644 packages/ckeditor5-ui/tests/manual/poweredby/poweredby-with-key.js create mode 100644 packages/ckeditor5-ui/tests/manual/poweredby/poweredby-with-key.md diff --git a/packages/ckeditor5-ui/src/editorui/poweredby.ts b/packages/ckeditor5-ui/src/editorui/poweredby.ts index c82264ec58c..7d00c210baa 100644 --- a/packages/ckeditor5-ui/src/editorui/poweredby.ts +++ b/packages/ckeditor5-ui/src/editorui/poweredby.ts @@ -12,6 +12,7 @@ import { Rect, DomEmitterMixin, findClosestScrollableAncestor, + verify, type PositionOptions, type Locale } from '@ckeditor/ckeditor5-utils'; @@ -94,6 +95,12 @@ export default class PoweredBy extends DomEmitterMixin() { private _handleEditorReady(): void { const editor = this.editor; + const licenseKey = this.editor.config.get( 'licenseKey' ); + + if ( licenseKey && verify( licenseKey ) === 'VALID' ) { + return; + } + // No view means no body collection to append the powered by balloon to. if ( !editor.ui.view ) { return; diff --git a/packages/ckeditor5-ui/tests/manual/poweredby/poweredby-with-key.html b/packages/ckeditor5-ui/tests/manual/poweredby/poweredby-with-key.html new file mode 100644 index 00000000000..a713221f2d3 --- /dev/null +++ b/packages/ckeditor5-ui/tests/manual/poweredby/poweredby-with-key.html @@ -0,0 +1,148 @@ + + + + +

Normal content

+
+

The three greatest things you learn from traveling

+

Like all the great things on earth traveling teaches us by example. Here are some of the most precious lessons + I’ve + learned over the years of traveling.

+ +
+

The real voyage of discovery consists not in seeking new landscapes, but having new eyes.

+

Marcel Proust

+
+

Improvisation

+

Life doesn't allow us to execute every single plan perfectly. This especially seems to be the case when you + travel. + You plan it down to every minute with a big checklist. But when it comes to executing it, something always comes + up + and you’re left with your improvising skills. You learn to adapt as you go. Here’s how my travel checklist looks + now:

+
    +
  • buy the ticket
  • +
  • start your adventure
  • +
+
Three monks ascending the stairs of an ancient temple. +
Three monks ascending the stairs of an ancient temple.
+
+

Confidence

+

Going to a new place can be quite terrifying. While change and uncertainty make us scared, traveling teaches us + how + ridiculous it is to be afraid of something before it happens. The moment you face your fear and see there is + nothing + to be afraid of, is the moment you discover bliss.

+
+ +

Single line content

+
+

The capital city of Malta is the top destination this summer. It’s home to cutting-edge contemporary + architecture, baroqu

+
+ +

Different bgs

+
+

The three greatest things you learn from traveling

+

Like all the great things on earth traveling teaches us by example. Here are some of the most precious lessons + I’ve + learned over the years of traveling.

+
+
+
+

The three greatest things you learn from traveling

+

Like all the great things on earth traveling teaches us by example. Here are some of the most precious lessons + I’ve + learned over the years of traveling.

+
+ +

Narrow editor

+
+

Foo bar

+
+
+
+

Foo bar

+
+ +

Padding-less editor

+
+

Like all the great things on earth traveling teaches us by example. Here are some of the most precious lessons + I’ve

+
+ +

Parent with overflow

+
+
+

The three greatest things you learn from traveling

+

Like all the great things on earth traveling teaches us by example. Here are some of the most precious + lessons I’ve + learned over the years of traveling.

+ +
+

The real voyage of discovery consists not in seeking new landscapes, but having new eyes.

+

Marcel Proust

+
+

Improvisation

+

Life doesn't allow us to execute every single plan perfectly. This especially seems to be the case when you + travel. + You plan it down to every minute with a big checklist. But when it comes to executing it, something always + comes up + and you’re left with your improvising skills. You learn to adapt as you go. Here’s how my travel checklist + looks + now:

+
    +
  • buy the ticket
  • +
  • start your adventure
  • +
+
Three monks ascending the stairs of an ancient temple. +
Three monks ascending the stairs of an ancient temple.
+
+

Confidence

+

Going to a new place can be quite terrifying. While change and uncertainty make us scared, traveling teaches + us how + ridiculous it is to be afraid of something before it happens. The moment you face your fear and see there is + nothing + to be afraid of, is the moment you discover bliss.

+
+
diff --git a/packages/ckeditor5-ui/tests/manual/poweredby/poweredby-with-key.js b/packages/ckeditor5-ui/tests/manual/poweredby/poweredby-with-key.js new file mode 100644 index 00000000000..3d842f038fa --- /dev/null +++ b/packages/ckeditor5-ui/tests/manual/poweredby/poweredby-with-key.js @@ -0,0 +1,67 @@ +/** + * @license Copyright (c) 2003-2023, CKSource Holding sp. z o.o. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ + +/* globals console, window, document, CKEditorInspector */ + +import ClassicEditor from '@ckeditor/ckeditor5-editor-classic/src/classiceditor'; +import ArticlePluginSet from '@ckeditor/ckeditor5-core/tests/_utils/articlepluginset'; +import { ImageResize } from '@ckeditor/ckeditor5-image'; + +const licenseKey = window.prompt( 'Enter valid key' ); // eslint-disable-line no-alert + +window.editors = {}; + +function createEditor( selector ) { + ClassicEditor + .create( document.querySelector( selector ), { + plugins: [ ArticlePluginSet, ImageResize ], + toolbar: [ + 'heading', + '|', + 'bold', + 'italic', + 'link', + 'bulletedList', + 'numberedList', + '|', + 'outdent', + 'indent', + '|', + 'blockQuote', + 'insertTable', + 'mediaEmbed', + 'undo', + 'redo' + ], + image: { + toolbar: [ 'imageStyle:inline', 'imageStyle:block', 'imageStyle:side', '|', 'imageTextAlternative' ] + }, + ...( licenseKey && { licenseKey } ), + table: { + contentToolbar: [ + 'tableColumn', + 'tableRow', + 'mergeTableCells' + ] + } + } ) + .then( editor => { + window.editors[ selector ] = editor; + + CKEditorInspector.attach( { [ selector ]: editor } ); + } ) + .catch( err => { + console.error( err.stack ); + } ); +} + +createEditor( '#normal-valid' ); +createEditor( '#single-line-valid' ); +createEditor( '#dark-bg-valid' ); +createEditor( '#medium-bg-valid' ); +createEditor( '#narrow-valid' ); +createEditor( '#narrow-dark-bg-valid' ); +createEditor( '#padding-less-valid' ); +createEditor( '#overflow-parent-valid' ); diff --git a/packages/ckeditor5-ui/tests/manual/poweredby/poweredby-with-key.md b/packages/ckeditor5-ui/tests/manual/poweredby/poweredby-with-key.md new file mode 100644 index 00000000000..c0e371c8b14 --- /dev/null +++ b/packages/ckeditor5-ui/tests/manual/poweredby/poweredby-with-key.md @@ -0,0 +1,3 @@ +# Powered by turned off. + +1. Make sure the "powered by" link is not rendered after providing a valid key. diff --git a/packages/ckeditor5-ui/tests/manual/poweredby/poweredby.md b/packages/ckeditor5-ui/tests/manual/poweredby/poweredby.md index c7984c257f1..63b9874d49f 100644 --- a/packages/ckeditor5-ui/tests/manual/poweredby/poweredby.md +++ b/packages/ckeditor5-ui/tests/manual/poweredby/poweredby.md @@ -1,6 +1,6 @@ # Powered by rendered inside of the editing root. 1. Make sure the "powered by" link renders above the bottom edge of the editing root when focused. -1. Make sure the link opens the page in a new tab. -1. Make sure the link disappears as soon as the editor gets blurred. -1. Make sure the link disappears when the editor is cropped by an ancestor with `overflow`. +2. Make sure the link opens the page in a new tab. +3. Make sure the link disappears as soon as the editor gets blurred. +4. Make sure the link disappears when the editor is cropped by an ancestor with `overflow`. diff --git a/packages/ckeditor5-utils/src/index.ts b/packages/ckeditor5-utils/src/index.ts index 576457f0379..a2956a7fbad 100644 --- a/packages/ckeditor5-utils/src/index.ts +++ b/packages/ckeditor5-utils/src/index.ts @@ -87,6 +87,7 @@ export { default as insertToPriorityArray } from './inserttopriorityarray'; export { default as spliceArray } from './splicearray'; export { default as uid } from './uid'; +export { default as verify } from './madewith'; export * from './unicode'; export { default as version, releaseDate } from './version'; From 13a44b2150d84bc8d7327ad99a127b6e268eb775 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotr=20Szcz=C4=99=C5=9Bniak?= Date: Tue, 9 May 2023 15:18:49 +0200 Subject: [PATCH 37/42] Change indent from spaces into tabs. From 825cbd5205aaa00adc758574a49521478b88679e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotr=20Szcz=C4=99=C5=9Bniak?= Date: Wed, 10 May 2023 09:59:38 +0200 Subject: [PATCH 38/42] Update packages/ckeditor5-ui/src/editorui/poweredby.ts Co-authored-by: Marek Lewandowski --- packages/ckeditor5-ui/src/editorui/poweredby.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ckeditor5-ui/src/editorui/poweredby.ts b/packages/ckeditor5-ui/src/editorui/poweredby.ts index 7d00c210baa..96e08a7ffe2 100644 --- a/packages/ckeditor5-ui/src/editorui/poweredby.ts +++ b/packages/ckeditor5-ui/src/editorui/poweredby.ts @@ -95,7 +95,7 @@ export default class PoweredBy extends DomEmitterMixin() { private _handleEditorReady(): void { const editor = this.editor; - const licenseKey = this.editor.config.get( 'licenseKey' ); + const licenseKey = editor.config.get( 'licenseKey' ); if ( licenseKey && verify( licenseKey ) === 'VALID' ) { return; From 56a907c8e57631b3562bc05721f193571370d1fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotr=20Szcz=C4=99=C5=9Bniak?= Date: Wed, 10 May 2023 10:36:01 +0200 Subject: [PATCH 39/42] Revert changes. --- packages/ckeditor5-ui/tests/manual/poweredby/poweredby.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/ckeditor5-ui/tests/manual/poweredby/poweredby.md b/packages/ckeditor5-ui/tests/manual/poweredby/poweredby.md index 63b9874d49f..c7984c257f1 100644 --- a/packages/ckeditor5-ui/tests/manual/poweredby/poweredby.md +++ b/packages/ckeditor5-ui/tests/manual/poweredby/poweredby.md @@ -1,6 +1,6 @@ # Powered by rendered inside of the editing root. 1. Make sure the "powered by" link renders above the bottom edge of the editing root when focused. -2. Make sure the link opens the page in a new tab. -3. Make sure the link disappears as soon as the editor gets blurred. -4. Make sure the link disappears when the editor is cropped by an ancestor with `overflow`. +1. Make sure the link opens the page in a new tab. +1. Make sure the link disappears as soon as the editor gets blurred. +1. Make sure the link disappears when the editor is cropped by an ancestor with `overflow`. From 5297d88f3db8cdecf31a1edbb61b3805e3682038 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotr=20Szcz=C4=99=C5=9Bniak?= Date: Wed, 10 May 2023 11:00:05 +0200 Subject: [PATCH 40/42] Ability to verify without passing the token parameter. --- packages/ckeditor5-ui/src/editorui/poweredby.ts | 4 +--- packages/ckeditor5-utils/src/madewith.ts | 6 +++++- packages/ckeditor5-utils/tests/madewith.js | 8 ++++++++ 3 files changed, 14 insertions(+), 4 deletions(-) diff --git a/packages/ckeditor5-ui/src/editorui/poweredby.ts b/packages/ckeditor5-ui/src/editorui/poweredby.ts index 96e08a7ffe2..ec50f36f938 100644 --- a/packages/ckeditor5-ui/src/editorui/poweredby.ts +++ b/packages/ckeditor5-ui/src/editorui/poweredby.ts @@ -95,9 +95,7 @@ export default class PoweredBy extends DomEmitterMixin() { private _handleEditorReady(): void { const editor = this.editor; - const licenseKey = editor.config.get( 'licenseKey' ); - - if ( licenseKey && verify( licenseKey ) === 'VALID' ) { + if ( verify( editor.config.get( 'licenseKey' ) ) === 'VALID' ) { return; } diff --git a/packages/ckeditor5-utils/src/madewith.ts b/packages/ckeditor5-utils/src/madewith.ts index 1ce88384e82..8687dc7acc9 100644 --- a/packages/ckeditor5-utils/src/madewith.ts +++ b/packages/ckeditor5-utils/src/madewith.ts @@ -20,11 +20,15 @@ export type VerifiedKeyStatus = 'VALID' | 'INVALID'; * @param token The string to check. * @returns String that represents the state of given `token` parameter. */ -export default function verify( token: string ): VerifiedKeyStatus { +export default function verify( token: string | undefined ): VerifiedKeyStatus { // TODO: issue ci#3175 let decryptedData = ''; let decryptedSecondElement = ''; + if ( !token ) { + return 'INVALID'; + } + try { decryptedData = atob( token ); } catch ( e ) { diff --git a/packages/ckeditor5-utils/tests/madewith.js b/packages/ckeditor5-utils/tests/madewith.js index a09064539da..1f8e583f419 100644 --- a/packages/ckeditor5-utils/tests/madewith.js +++ b/packages/ckeditor5-utils/tests/madewith.js @@ -26,6 +26,14 @@ describe( 'utils', () => { } ); describe( 'should return `INVALID`', () => { + it( 'when token is empty', () => { + expect( verify( '' ) ).to.be.equal( 'INVALID' ); + } ); + + it( 'when token is not passed', () => { + expect( verify( ) ).to.be.equal( 'INVALID' ); + } ); + describe( 'new', () => { it( 'first too short', () => { expect( verify( 'Wm05dlltRnktTWpBeU5UQXhNREU9' ) ).to.be.equal( 'INVALID' ); From 87ff18cde843131be807e97089fa643e919bc871 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotr=20Szcz=C4=99=C5=9Bniak?= Date: Wed, 10 May 2023 11:24:00 +0200 Subject: [PATCH 41/42] Rename the verify function into verifyLicense. --- .../ckeditor5-ui/src/editorui/poweredby.ts | 4 +-- packages/ckeditor5-utils/src/index.ts | 2 +- .../src/{madewith.ts => verifylicense.ts} | 4 +-- .../tests/{madewith.js => verifylicense.js} | 28 +++++++++---------- 4 files changed, 19 insertions(+), 19 deletions(-) rename packages/ckeditor5-utils/src/{madewith.ts => verifylicense.ts} (93%) rename packages/ckeditor5-utils/tests/{madewith.js => verifylicense.js} (75%) diff --git a/packages/ckeditor5-ui/src/editorui/poweredby.ts b/packages/ckeditor5-ui/src/editorui/poweredby.ts index ec50f36f938..f3a0895cc77 100644 --- a/packages/ckeditor5-ui/src/editorui/poweredby.ts +++ b/packages/ckeditor5-ui/src/editorui/poweredby.ts @@ -12,7 +12,7 @@ import { Rect, DomEmitterMixin, findClosestScrollableAncestor, - verify, + verifyLicense, type PositionOptions, type Locale } from '@ckeditor/ckeditor5-utils'; @@ -95,7 +95,7 @@ export default class PoweredBy extends DomEmitterMixin() { private _handleEditorReady(): void { const editor = this.editor; - if ( verify( editor.config.get( 'licenseKey' ) ) === 'VALID' ) { + if ( verifyLicense( editor.config.get( 'licenseKey' ) ) === 'VALID' ) { return; } diff --git a/packages/ckeditor5-utils/src/index.ts b/packages/ckeditor5-utils/src/index.ts index a2956a7fbad..d0511d2c07f 100644 --- a/packages/ckeditor5-utils/src/index.ts +++ b/packages/ckeditor5-utils/src/index.ts @@ -87,7 +87,7 @@ export { default as insertToPriorityArray } from './inserttopriorityarray'; export { default as spliceArray } from './splicearray'; export { default as uid } from './uid'; -export { default as verify } from './madewith'; +export { default as verifyLicense } from './verifylicense'; export * from './unicode'; export { default as version, releaseDate } from './version'; diff --git a/packages/ckeditor5-utils/src/madewith.ts b/packages/ckeditor5-utils/src/verifylicense.ts similarity index 93% rename from packages/ckeditor5-utils/src/madewith.ts rename to packages/ckeditor5-utils/src/verifylicense.ts index 8687dc7acc9..cb61e2727ef 100644 --- a/packages/ckeditor5-utils/src/madewith.ts +++ b/packages/ckeditor5-utils/src/verifylicense.ts @@ -4,7 +4,7 @@ */ /** - * @module utils/madewith + * @module utils/verifylicense */ import { releaseDate } from './version'; @@ -20,7 +20,7 @@ export type VerifiedKeyStatus = 'VALID' | 'INVALID'; * @param token The string to check. * @returns String that represents the state of given `token` parameter. */ -export default function verify( token: string | undefined ): VerifiedKeyStatus { +export default function verifyLicense( token: string | undefined ): VerifiedKeyStatus { // TODO: issue ci#3175 let decryptedData = ''; let decryptedSecondElement = ''; diff --git a/packages/ckeditor5-utils/tests/madewith.js b/packages/ckeditor5-utils/tests/verifylicense.js similarity index 75% rename from packages/ckeditor5-utils/tests/madewith.js rename to packages/ckeditor5-utils/tests/verifylicense.js index 1f8e583f419..62a96736d49 100644 --- a/packages/ckeditor5-utils/tests/madewith.js +++ b/packages/ckeditor5-utils/tests/verifylicense.js @@ -3,7 +3,7 @@ * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license */ -import verify from '../src/madewith'; +import verifyLicense from '../src/verifylicense'; describe( 'utils', () => { describe( 'verify', () => { @@ -13,7 +13,7 @@ describe( 'utils', () => { // eslint-disable-next-line max-len const string = 'dG9vZWFzZXRtcHNsaXVyb3JsbWlkbXRvb2Vhc2V0bXBzbGl1cm9ybG1pZG10b29lYXNldG1wc2xpdXJvcmxtaWRtLU1qQTBOREEyTVRJPQ=='; - expect( verify( string ) ).to.be.equal( 'VALID' ); + expect( verifyLicense( string ) ).to.be.equal( 'VALID' ); } ); it( 'when old token format is given', () => { @@ -21,33 +21,33 @@ describe( 'utils', () => { // eslint-disable-next-line max-len const string = 'YWZvb2JkcnphYXJhZWJvb290em9wbWJvbHJ1c21sZnJlYmFzdG1paXN1cm1tZmllenJhb2F0YmFvcmxvb3B6aWJvYWRiZWZzYXJ0bW9ibG8='; - expect( verify( string ) ).to.be.equal( 'VALID' ); + expect( verifyLicense( string ) ).to.be.equal( 'VALID' ); } ); } ); describe( 'should return `INVALID`', () => { it( 'when token is empty', () => { - expect( verify( '' ) ).to.be.equal( 'INVALID' ); + expect( verifyLicense( '' ) ).to.be.equal( 'INVALID' ); } ); it( 'when token is not passed', () => { - expect( verify( ) ).to.be.equal( 'INVALID' ); + expect( verifyLicense( ) ).to.be.equal( 'INVALID' ); } ); describe( 'new', () => { it( 'first too short', () => { - expect( verify( 'Wm05dlltRnktTWpBeU5UQXhNREU9' ) ).to.be.equal( 'INVALID' ); + expect( verifyLicense( 'Wm05dlltRnktTWpBeU5UQXhNREU9' ) ).to.be.equal( 'INVALID' ); } ); it( 'first too long', () => { // eslint-disable-next-line max-len const string = 'YzNSbGJTQmxjbkp2Y2pvZ2JtVjBPanBGVWxKZlFreFBRMHRGUkY5Q1dWOURURWxGVGxSemRHVnRJR1Z5Y205eU9pQnVaWFE2T2tWU1VsOUNURTlEUzBWRVgwSlpYME5NU1VWT1ZITjBaVzBnWlhKeWIzSTZJRzVsZERvNlJWSlNYMEpNVDBOTFJVUmZRbGxmUTB4SlJVNVVjM1JsYlNCbGNuSnZjam9nYm1WME9qcEZVbEpmUWt4UFEwdEZSRjlDV1Y5RFRFbEZUbFJ6ZEdWdElHVnljbTl5T2lCdVpYUTZPa1ZTVWw5Q1RFOURTMFZFWDBKWlgwTk1TVVZPVkhOMFpXMGdaWEp5YjNJNklHNWxkRG82UlZKU1gwSk1UME5MUlVSZlFsbGZRMHhKUlU1VS1NakF5TlRBeE1ERT0='; - expect( verify( string ) ).to.be.equal( 'INVALID' ); + expect( verifyLicense( string ) ).to.be.equal( 'INVALID' ); } ); it( 'first wrong format', () => { const string = 'YS1NakF5TlRBeE1ERT0='; - expect( verify( string ) ).to.be.equal( 'INVALID' ); + expect( verifyLicense( string ) ).to.be.equal( 'INVALID' ); } ); it( 'when date is invalid', () => { @@ -55,20 +55,20 @@ describe( 'utils', () => { const string = 'enN6YXJ0YWxhYWZsaWViYnRvcnVpb3Jvb3BzYmVkYW9tcm1iZm9vbS1NVGs1TnpFeA=='; - expect( verify( string ) ).to.be.equal( 'INVALID' ); + expect( verifyLicense( string ) ).to.be.equal( 'INVALID' ); } ); it( 'wrong second part format', () => { const string = 'Ylc5MGIyeDBiMkZ5YzJsbGMybHNjR1Z0Y20xa2RXMXZkRzlzZEc5aGNuTnBaWE09LVptOXZZbUZ5WW1FPQ=='; - expect( verify( string ) ).to.be.equal( 'INVALID' ); + expect( verifyLicense( string ) ).to.be.equal( 'INVALID' ); } ); it( 'when wrong string passed', () => { // # instead of second part const string = 'Ylc5MGIyeDBiMkZ5YzJsbGMybHNjR1Z0Y20xa2RXMXZkRzlzZEc5aGNuTnBaWE09LSM='; - expect( verify( string ) ).to.be.equal( 'INVALID' ); + expect( verifyLicense( string ) ).to.be.equal( 'INVALID' ); } ); it( 'when date is earlier than the release date', () => { @@ -76,7 +76,7 @@ describe( 'utils', () => { // eslint-disable-next-line max-len const string = 'Y0dWc1lYVmxhWE4wYlc5a2MyMXBiRzEwY205eWIzQmxiR0YxWldsemRHMXZaSE50YVd4dGRISnZjbTlrYzJGa2MyRmtjMkU9LU1UazRNREF4TURFPQ=='; - expect( verify( string ) ).to.be.equal( 'INVALID' ); + expect( verifyLicense( string ) ).to.be.equal( 'INVALID' ); } ); } ); @@ -84,7 +84,7 @@ describe( 'utils', () => { it( 'when date is missing', () => { const string = 'dG1wb3Rhc2llc3VlbHJtZHJvbWlsbw=='; - expect( verify( string ) ).to.be.equal( 'INVALID' ); + expect( verifyLicense( string ) ).to.be.equal( 'INVALID' ); } ); } ); @@ -92,7 +92,7 @@ describe( 'utils', () => { // invalid const string = 'foobarbaz'; - expect( verify( string ) ).to.be.equal( 'INVALID' ); + expect( verifyLicense( string ) ).to.be.equal( 'INVALID' ); } ); } ); } ); From b0eb6b8a6610c7e49a0c02d5a81b3ac9900ddfd3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotr=20Szcz=C4=99=C5=9Bniak?= Date: Wed, 10 May 2023 13:17:31 +0200 Subject: [PATCH 42/42] Ignore license verification in cc. --- packages/ckeditor5-ui/src/editorui/poweredby.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/ckeditor5-ui/src/editorui/poweredby.ts b/packages/ckeditor5-ui/src/editorui/poweredby.ts index f3a0895cc77..0865bea3eeb 100644 --- a/packages/ckeditor5-ui/src/editorui/poweredby.ts +++ b/packages/ckeditor5-ui/src/editorui/poweredby.ts @@ -95,6 +95,7 @@ export default class PoweredBy extends DomEmitterMixin() { private _handleEditorReady(): void { const editor = this.editor; + /* istanbul ignore next -- @preserve */ if ( verifyLicense( editor.config.get( 'licenseKey' ) ) === 'VALID' ) { return; }