@ckeditor/ckeditor5-ui 43.3.1 → 44.0.0-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/README.md +13 -7
  2. package/dist/badge/badge.d.ts +133 -0
  3. package/dist/dropdown/dropdownview.d.ts +6 -0
  4. package/dist/editorui/bodycollection.d.ts +47 -12
  5. package/dist/editorui/editorui.d.ts +5 -0
  6. package/dist/editorui/evaluationbadge.d.ts +37 -0
  7. package/dist/editorui/poweredby.d.ts +12 -49
  8. package/dist/index-editor.css +52 -3
  9. package/dist/index.css +62 -3
  10. package/dist/index.css.map +1 -1
  11. package/dist/index.js +351 -144
  12. package/dist/index.js.map +1 -1
  13. package/dist/menubar/menubarmenupanelview.d.ts +1 -1
  14. package/dist/menubar/menubarmenuview.d.ts +5 -0
  15. package/dist/menubar/utils.d.ts +1 -0
  16. package/package.json +4 -4
  17. package/src/badge/badge.d.ts +129 -0
  18. package/src/badge/badge.js +218 -0
  19. package/src/dialog/dialogview.js +6 -2
  20. package/src/dropdown/dropdownview.d.ts +6 -0
  21. package/src/dropdown/dropdownview.js +9 -1
  22. package/src/editorui/bodycollection.d.ts +47 -12
  23. package/src/editorui/bodycollection.js +50 -19
  24. package/src/editorui/editorui.d.ts +5 -0
  25. package/src/editorui/editorui.js +3 -0
  26. package/src/editorui/evaluationbadge.d.ts +33 -0
  27. package/src/editorui/evaluationbadge.js +99 -0
  28. package/src/editorui/poweredby.d.ts +12 -49
  29. package/src/editorui/poweredby.js +36 -194
  30. package/src/menubar/menubarmenupanelview.d.ts +1 -1
  31. package/src/menubar/menubarmenuview.d.ts +5 -0
  32. package/src/menubar/menubarmenuview.js +23 -1
  33. package/src/menubar/utils.d.ts +1 -0
  34. package/src/menubar/utils.js +2 -0
  35. package/theme/globals/_evaluationbadge.css +54 -0
  36. package/theme/globals/_poweredby.css +15 -3
  37. package/theme/globals/globals.css +1 -0
package/dist/index.js CHANGED
@@ -2,7 +2,7 @@
2
2
  * @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.
3
3
  * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
4
4
  */
5
- import { Collection, CKEditorError, EmitterMixin, isNode, toArray, DomEmitterMixin, ObservableMixin, isIterable, uid, env, delay, getEnvKeystrokeText, isVisible, global, KeystrokeHandler, FocusTracker, Rect, toUnit, createElement, ResizeObserver, getBorderWidths, logWarning, getOptimalPosition, isText, isRange, priorities, first, verifyLicense, getAncestors } from '@ckeditor/ckeditor5-utils/dist/index.js';
5
+ import { Collection, CKEditorError, EmitterMixin, isNode, toArray, DomEmitterMixin, ObservableMixin, isIterable, uid, env, delay, getEnvKeystrokeText, isVisible, global, KeystrokeHandler, FocusTracker, Rect, toUnit, createElement, ResizeObserver, getBorderWidths, logWarning, getOptimalPosition, isText, isRange, priorities, first, parseBase64EncodedObject, getAncestors } from '@ckeditor/ckeditor5-utils/dist/index.js';
6
6
  import { cloneDeepWith, isObject, isElement, debounce, throttle, cloneDeep, extend, escapeRegExp, escape } from 'lodash-es';
7
7
  import { icons, Plugin, ContextPlugin } from '@ckeditor/ckeditor5-core/dist/index.js';
8
8
  import parse from 'color-parse';
@@ -3459,10 +3459,14 @@ const toPx$6 = /* #__PURE__ */ toUnit('px');
3459
3459
  */ render() {
3460
3460
  super.render();
3461
3461
  this.keystrokes.set('Esc', (data, cancel)=>{
3462
- this.fire('close', {
3463
- source: 'escKeyPress'
3464
- });
3465
- cancel();
3462
+ // Do not react to the Esc key if the event has already been handled and defaultPrevented
3463
+ // by some logic of the dialog guest (child) view (https://github.com/ckeditor/ckeditor5/issues/17343).
3464
+ if (!data.defaultPrevented) {
3465
+ this.fire('close', {
3466
+ source: 'escKeyPress'
3467
+ });
3468
+ cancel();
3469
+ }
3466
3470
  });
3467
3471
  // Support for dragging the modal.
3468
3472
  this.on('drag', (evt, { deltaX, deltaY })=>{
@@ -4481,28 +4485,62 @@ var accessibilityIcon = "<svg viewBox=\"0 0 20 20\" xmlns=\"http://www.w3.org/20
4481
4485
  }
4482
4486
 
4483
4487
  /**
4484
- * This is a special {@link module:ui/viewcollection~ViewCollection} dedicated to elements that are detached
4485
- * from the DOM structure of the editor, like panels, icons, etc.
4488
+ * This is a special {@link module:ui/viewcollection~ViewCollection} dedicated to elements that are detached from the DOM structure of
4489
+ * the editor, like floating panels, floating toolbars, dialogs, etc.
4486
4490
  *
4487
- * The body collection is available in the {@link module:ui/editorui/editoruiview~EditorUIView#body `editor.ui.view.body`} property.
4491
+ * The body collection is available under the {@link module:ui/editorui/editoruiview~EditorUIView#body `editor.ui.view.body`} property.
4488
4492
  * Any plugin can add a {@link module:ui/view~View view} to this collection.
4489
- * These views will render in a container placed directly in the `<body>` element.
4490
- * The editor will detach and destroy this collection when the editor will be {@link module:core/editor/editor~Editor#destroy destroyed}.
4491
4493
  *
4492
- * If you need to control the life cycle of the body collection on your own, you can create your own instance of this class.
4494
+ * All views added to a body collection render in a dedicated DOM container (`<div class="ck ck-body ...">...</div>`). All body collection
4495
+ * containers render in a common shared (`<div class="ck-body-wrapper">...</div>`) in the DOM to limit the pollution of
4496
+ * the `<body>` element. The resulting DOM structure is as follows:
4497
+ *
4498
+ * ```html
4499
+ * <body>
4500
+ * <!-- Content of the webpage... -->
4501
+ *
4502
+ * <!-- The shared wrapper for all body collection containers. -->
4503
+ * <div class="ck-body-wrapper">
4504
+ * <!-- The container of the first body collection instance. -->
4505
+ * <div class="ck ck-body ...">
4506
+ * <!-- View elements belonging to the first body collection -->
4507
+ * </div>
4508
+ *
4509
+ * <!-- The container of the second body collection instance. -->
4510
+ * <div class="ck ck-body ...">...</div>
4511
+ *
4512
+ * <!-- More body collection containers for the rest of instances... -->
4513
+ * </div>
4514
+ * </body>
4515
+ * ```
4493
4516
  *
4494
- * A body collection will render itself automatically in the DOM body element as soon as you call {@link ~BodyCollection#attachToDom}.
4495
- * If you create multiple body collections, this class will create a special wrapper element in the DOM to limit the number of
4496
- * elements created directly in the body and remove it when the last body collection will be
4497
- * {@link ~BodyCollection#detachFromDom detached}.
4517
+ * By default, the {@link module:ui/editorui/editoruiview~EditorUIView `editor.ui.view`} manages the life cycle of the
4518
+ * {@link module:ui/editorui/editoruiview~EditorUIView#body `editor.ui.view.body`} collection, attaching and detaching it
4519
+ * when the editor gets created or {@link module:core/editor/editor~Editor#destroy destroyed}.
4520
+ *
4521
+ * # Custom body collection instances
4522
+ *
4523
+ * Even though most editor instances come with a built-in body collection
4524
+ * ({@link module:ui/editorui/editoruiview~EditorUIView#body `editor.ui.view.body`}), you can create your own instance of this
4525
+ * class if you need to control their life cycle.
4526
+ *
4527
+ * The life cycle of a custom body collection must be handled manually by the developer using the dedicated API:
4528
+ * * A body collection will render itself automatically in the DOM as soon as you call {@link ~BodyCollection#attachToDom}.
4529
+ * * Calling {@link ~BodyCollection#detachFromDom} will remove the collection from the DOM.
4530
+ *
4531
+ * **Note**: The shared collection wrapper (`<div class="ck-body-wrapper">...</div>`) gets automatically removed from DOM when the
4532
+ * last body collection is {@link ~BodyCollection#detachFromDom detached} and does not require any special handling.
4498
4533
  */ class BodyCollection extends ViewCollection {
4499
4534
  /**
4500
4535
  * The {@link module:core/editor/editor~Editor#locale editor's locale} instance.
4501
4536
  * See the view {@link module:ui/view~View#locale locale} property.
4502
4537
  */ locale;
4503
4538
  /**
4504
- * The element holding elements of the body region.
4539
+ * The element holding elements of the body collection.
4505
4540
  */ _bodyCollectionContainer;
4541
+ /**
4542
+ * The wrapper element that holds all of the {@link #_bodyCollectionContainer} elements.
4543
+ */ static _bodyWrapper;
4506
4544
  /**
4507
4545
  * Creates a new instance of the {@link module:ui/editorui/bodycollection~BodyCollection}.
4508
4546
  *
@@ -4513,7 +4551,7 @@ var accessibilityIcon = "<svg viewBox=\"0 0 20 20\" xmlns=\"http://www.w3.org/20
4513
4551
  this.locale = locale;
4514
4552
  }
4515
4553
  /**
4516
- * The element holding elements of the body region.
4554
+ * The element holding elements of the body collection.
4517
4555
  */ get bodyCollectionContainer() {
4518
4556
  return this._bodyCollectionContainer;
4519
4557
  }
@@ -4535,14 +4573,14 @@ var accessibilityIcon = "<svg viewBox=\"0 0 20 20\" xmlns=\"http://www.w3.org/20
4535
4573
  },
4536
4574
  children: this
4537
4575
  }).render();
4538
- let wrapper = document.querySelector('.ck-body-wrapper');
4539
- if (!wrapper) {
4540
- wrapper = createElement(document, 'div', {
4576
+ // Create a shared wrapper if there were none or the previous one got disconnected from DOM.
4577
+ if (!BodyCollection._bodyWrapper || !BodyCollection._bodyWrapper.isConnected) {
4578
+ BodyCollection._bodyWrapper = createElement(document, 'div', {
4541
4579
  class: 'ck-body-wrapper'
4542
4580
  });
4543
- document.body.appendChild(wrapper);
4581
+ document.body.appendChild(BodyCollection._bodyWrapper);
4544
4582
  }
4545
- wrapper.appendChild(this._bodyCollectionContainer);
4583
+ BodyCollection._bodyWrapper.appendChild(this._bodyCollectionContainer);
4546
4584
  }
4547
4585
  /**
4548
4586
  * Detaches the collection from the DOM structure. Use this method when you do not need to use the body collection
@@ -4552,9 +4590,9 @@ var accessibilityIcon = "<svg viewBox=\"0 0 20 20\" xmlns=\"http://www.w3.org/20
4552
4590
  if (this._bodyCollectionContainer) {
4553
4591
  this._bodyCollectionContainer.remove();
4554
4592
  }
4555
- const wrapper = document.querySelector('.ck-body-wrapper');
4556
- if (wrapper && wrapper.childElementCount == 0) {
4557
- wrapper.remove();
4593
+ if (BodyCollection._bodyWrapper && !BodyCollection._bodyWrapper.childElementCount) {
4594
+ BodyCollection._bodyWrapper.remove();
4595
+ delete BodyCollection._bodyWrapper;
4558
4596
  }
4559
4597
  }
4560
4598
  }
@@ -5948,7 +5986,7 @@ function getTextareaElementClone(element, value) {
5948
5986
  fitInViewport: true,
5949
5987
  positions: this._panelPositions
5950
5988
  });
5951
- this.panelView.position = optimalPanelPosition ? optimalPanelPosition.name : this._panelPositions[0].name;
5989
+ this.panelView.position = optimalPanelPosition ? optimalPanelPosition.name : this._defaultPanelPositionName;
5952
5990
  } else {
5953
5991
  this.panelView.position = this.panelPosition;
5954
5992
  }
@@ -6018,6 +6056,13 @@ function getTextareaElementClone(element, value) {
6018
6056
  ];
6019
6057
  }
6020
6058
  }
6059
+ /**
6060
+ * Returns the default position of the dropdown panel based on the direction of the UI language.
6061
+ * It is used when the {@link #panelPosition} is set to `'auto'` and the panel has not found a
6062
+ * suitable position to fit into the viewport.
6063
+ */ get _defaultPanelPositionName() {
6064
+ return this.locale.uiLanguageDirection === 'rtl' ? 'sw' : 'se';
6065
+ }
6021
6066
  /**
6022
6067
  * A set of positioning functions used by the dropdown view to determine
6023
6068
  * the optimal position (i.e. fitting into the browser viewport) of its
@@ -11795,57 +11840,51 @@ function createMutationObserver(callback) {
11795
11840
  };
11796
11841
  }
11797
11842
 
11798
- var poweredByIcon = "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"53\" height=\"10\" viewBox=\"0 0 53 10\"><path fill=\"#1C2331\" d=\"M31.724 1.492a15.139 15.139 0 0 0 .045 1.16 2.434 2.434 0 0 0-.687-.34 3.68 3.68 0 0 0-1.103-.166 2.332 2.332 0 0 0-1.14.255 1.549 1.549 0 0 0-.686.87c-.15.41-.225.98-.225 1.712 0 .939.148 1.659.444 2.161.297.503.792.754 1.487.754.452.015.9-.094 1.294-.316.296-.174.557-.4.771-.669l.14.852h1.282V.007h-1.623v1.485ZM31 6.496a1.77 1.77 0 0 1-.494.061.964.964 0 0 1-.521-.127.758.758 0 0 1-.296-.466 3.984 3.984 0 0 1-.093-.992 4.208 4.208 0 0 1 .098-1.052.753.753 0 0 1 .307-.477 1.08 1.08 0 0 1 .55-.122c.233-.004.466.026.69.089l.483.144v2.553c-.11.076-.213.143-.307.2a1.73 1.73 0 0 1-.417.189ZM35.68 0l-.702.004c-.322.002-.482.168-.48.497l.004.581c.002.33.164.493.486.49l.702-.004c.322-.002.481-.167.48-.496L36.165.49c-.002-.33-.164-.493-.486-.491ZM36.145 2.313l-1.612.01.034 5.482 1.613-.01-.035-5.482ZM39.623.79 37.989.8 38 2.306l-.946.056.006 1.009.949-.006.024 2.983c.003.476.143.844.419 1.106.275.26.658.39 1.148.387.132 0 .293-.01.483-.03.19-.02.38-.046.57-.08.163-.028.324-.068.482-.119l-.183-1.095-.702.004a.664.664 0 0 1-.456-.123.553.553 0 0 1-.14-.422l-.016-2.621 1.513-.01-.006-1.064-1.514.01-.01-1.503ZM46.226 2.388c-.41-.184-.956-.274-1.636-.27-.673.004-1.215.101-1.627.29-.402.179-.72.505-.888.91-.18.419-.268.979-.264 1.68.004.688.1 1.24.285 1.655.172.404.495.724.9.894.414.18.957.268 1.63.264.68-.004 1.224-.099 1.632-.284.4-.176.714-.501.878-.905.176-.418.263-.971.258-1.658-.004-.702-.097-1.261-.28-1.677a1.696 1.696 0 0 0-.888-.9Zm-.613 3.607a.77.77 0 0 1-.337.501 1.649 1.649 0 0 1-1.317.009.776.776 0 0 1-.343-.497 4.066 4.066 0 0 1-.105-1.02 4.136 4.136 0 0 1 .092-1.03.786.786 0 0 1 .337-.507 1.59 1.59 0 0 1 1.316-.008.79.79 0 0 1 .344.502c.078.337.113.683.105 1.03.012.343-.019.685-.092 1.02ZM52.114 2.07a2.67 2.67 0 0 0-1.128.278c-.39.191-.752.437-1.072.73l-.157-.846-1.273.008.036 5.572 1.623-.01-.024-3.78c.35-.124.646-.22.887-.286.26-.075.53-.114.8-.118l.45-.003.144-1.546-.286.001ZM22.083 7.426l-1.576-2.532a2.137 2.137 0 0 0-.172-.253 1.95 1.95 0 0 0-.304-.29.138.138 0 0 1 .042-.04 1.7 1.7 0 0 0 .328-.374l1.75-2.71c.01-.015.025-.028.024-.048-.01-.01-.021-.007-.031-.007L20.49 1.17a.078.078 0 0 0-.075.045l-.868 1.384c-.23.366-.46.732-.688 1.099a.108.108 0 0 1-.112.06c-.098-.005-.196-.001-.294-.002-.018 0-.038.006-.055-.007.002-.02.002-.039.005-.058a4.6 4.6 0 0 0 .046-.701V1.203c0-.02-.009-.032-.03-.03h-.033L16.93 1.17c-.084 0-.073-.01-.073.076v6.491c-.001.018.006.028.025.027h1.494c.083 0 .072.007.072-.071v-2.19c0-.055-.003-.11-.004-.166a3.366 3.366 0 0 0-.05-.417h.06c.104 0 .209.002.313-.002a.082.082 0 0 1 .084.05c.535.913 1.07 1.824 1.607 2.736a.104.104 0 0 0 .103.062c.554-.003 1.107-.002 1.66-.002l.069-.003-.019-.032-.188-.304ZM27.112 6.555c-.005-.08-.004-.08-.082-.08h-2.414c-.053 0-.106-.003-.159-.011a.279.279 0 0 1-.246-.209.558.558 0 0 1-.022-.15c0-.382 0-.762-.002-1.143 0-.032.007-.049.042-.044h2.504c.029.003.037-.012.034-.038V3.814c0-.089.013-.078-.076-.078h-2.44c-.07 0-.062.003-.062-.06v-.837c0-.047.004-.093.013-.14a.283.283 0 0 1 .241-.246.717.717 0 0 1 .146-.011h2.484c.024.002.035-.009.036-.033l.003-.038.03-.496c.01-.183.024-.365.034-.548.005-.085.003-.087-.082-.094-.218-.018-.437-.038-.655-.05a17.845 17.845 0 0 0-.657-.026 72.994 72.994 0 0 0-1.756-.016 1.7 1.7 0 0 0-.471.064 1.286 1.286 0 0 0-.817.655c-.099.196-.149.413-.145.633v3.875c0 .072.003.144.011.216a1.27 1.27 0 0 0 .711 1.029c.228.113.48.167.734.158.757-.005 1.515.002 2.272-.042.274-.016.548-.034.82-.053.03-.002.043-.008.04-.041-.008-.104-.012-.208-.019-.312a69.964 69.964 0 0 1-.05-.768ZM16.14 7.415l-.127-1.075c-.004-.03-.014-.04-.044-.037a13.125 13.125 0 0 1-.998.073c-.336.01-.672.02-1.008.016-.116-.001-.233-.014-.347-.039a.746.746 0 0 1-.45-.262c-.075-.1-.132-.211-.167-.33a3.324 3.324 0 0 1-.126-.773 9.113 9.113 0 0 1-.015-.749c0-.285.022-.57.065-.852.023-.158.066-.312.127-.46a.728.728 0 0 1 .518-.443 1.64 1.64 0 0 1 .397-.048c.628-.001 1.255.003 1.882.05.022.001.033-.006.036-.026l.003-.031.06-.55c.019-.177.036-.355.057-.532.004-.034-.005-.046-.04-.056a5.595 5.595 0 0 0-1.213-.21 10.783 10.783 0 0 0-.708-.02c-.24-.003-.48.01-.719.041a3.477 3.477 0 0 0-.625.14 1.912 1.912 0 0 0-.807.497c-.185.2-.33.433-.424.688a4.311 4.311 0 0 0-.24 1.096c-.031.286-.045.572-.042.86-.006.43.024.86.091 1.286.04.25.104.497.193.734.098.279.26.53.473.734.214.205.473.358.756.446.344.11.702.17 1.063.177a8.505 8.505 0 0 0 1.578-.083 6.11 6.11 0 0 0 .766-.18c.03-.008.047-.023.037-.057a.157.157 0 0 1-.003-.025Z\"/><path fill=\"#AFE229\" d=\"M6.016 6.69a1.592 1.592 0 0 0-.614.21c-.23.132-.422.32-.56.546-.044.072-.287.539-.287.539l-.836 1.528.009.006c.038.025.08.046.123.063.127.046.26.07.395.073.505.023 1.011-.007 1.517-.003.29.009.58.002.869-.022a.886.886 0 0 0 .395-.116.962.962 0 0 0 .312-.286c.056-.083.114-.163.164-.249.24-.408.48-.816.718-1.226.075-.128.148-.257.222-.386l.112-.192a1.07 1.07 0 0 0 .153-.518l-1.304.023s-1.258-.005-1.388.01Z\"/><path fill=\"#771BFF\" d=\"m2.848 9.044.76-1.39.184-.352c-.124-.067-.245-.14-.367-.21-.346-.204-.706-.384-1.045-.6a.984.984 0 0 1-.244-.207c-.108-.134-.136-.294-.144-.46-.021-.409-.002-.818-.009-1.227-.003-.195 0-.39.003-.585.004-.322.153-.553.427-.713l.833-.488c.22-.13.44-.257.662-.385.05-.029.105-.052.158-.077.272-.128.519-.047.76.085l.044.028c.123.06.242.125.358.196.318.178.635.357.952.537.095.056.187.117.275.184.194.144.254.35.266.578.016.284.007.569.006.853-.001.28.004.558 0 .838.592-.003 1.259 0 1.259 0l.723-.013c-.003-.292-.007-.584-.007-.876 0-.524.015-1.048-.016-1.571-.024-.42-.135-.8-.492-1.067a5.02 5.02 0 0 0-.506-.339A400.52 400.52 0 0 0 5.94.787C5.722.664 5.513.524 5.282.423 5.255.406 5.228.388 5.2.373 4.758.126 4.305-.026 3.807.21c-.097.046-.197.087-.29.14A699.896 699.896 0 0 0 .783 1.948c-.501.294-.773.717-.778 1.31-.004.36-.009.718-.001 1.077.016.754-.017 1.508.024 2.261.016.304.07.6.269.848.127.15.279.28.448.382.622.4 1.283.734 1.92 1.11l.183.109Z\"/></svg>\n";
11799
-
11800
- const ICON_WIDTH = 53;
11801
- const ICON_HEIGHT = 10;
11802
11843
  // ⚠ Note, whenever changing the threshold, make sure to update the docs/support/managing-ckeditor-logo.md docs
11803
11844
  // as this information is also mentioned there ⚠.
11804
11845
  const NARROW_ROOT_HEIGHT_THRESHOLD = 50;
11805
11846
  const NARROW_ROOT_WIDTH_THRESHOLD = 350;
11806
- const DEFAULT_LABEL = 'Powered by';
11807
11847
  /**
11808
- * A helper that enables the "powered by" feature in the editor and renders a link to the project's
11809
- * webpage next to the bottom of the editable element (editor root, source editing area, etc.) when the editor is focused.
11848
+ * A helper that enables the badge feature in the editor and renders a custom view next to the bottom of the editable element
11849
+ * (editor root, source editing area, etc.) when the editor is focused.
11810
11850
  *
11811
11851
  * @private
11812
- */ class PoweredBy extends /* #__PURE__ */ DomEmitterMixin() {
11852
+ */ class Badge extends /* #__PURE__ */ DomEmitterMixin() {
11813
11853
  /**
11814
11854
  * Editor instance the helper was created for.
11815
11855
  */ editor;
11816
11856
  /**
11817
- * A reference to the balloon panel hosting and positioning the "powered by" link and logo.
11818
- */ _balloonView;
11857
+ * A reference to the balloon panel hosting and positioning the badge content.
11858
+ */ _balloonView = null;
11819
11859
  /**
11820
11860
  * A throttled version of the {@link #_showBalloon} method meant for frequent use to avoid performance loss.
11821
- */ _showBalloonThrottled;
11861
+ */ _showBalloonThrottled = throttle(()=>this._showBalloon(), 50, {
11862
+ leading: true
11863
+ });
11822
11864
  /**
11823
11865
  * A reference to the last editable element (root, source editing area, etc.) focused by the user.
11824
11866
  * Since the focus can move to other focusable elements in the UI, this reference allows positioning the balloon over the
11825
11867
  * right element whether the user is typing or using the UI.
11826
- */ _lastFocusedEditableElement;
11868
+ */ _lastFocusedEditableElement = null;
11869
+ /**
11870
+ * An additional CSS class added to the `BalloonView`.
11871
+ */ _balloonClass;
11827
11872
  /**
11828
- * Creates a "powered by" helper for a given editor. The feature is initialized on Editor#ready
11873
+ * Creates a badge for a given editor. The feature is initialized on Editor#ready
11829
11874
  * event.
11830
- *
11831
- * @param editor
11832
- */ constructor(editor){
11875
+ */ constructor(editor, options = {}){
11833
11876
  super();
11834
11877
  this.editor = editor;
11835
- this._balloonView = null;
11836
- this._lastFocusedEditableElement = null;
11837
- this._showBalloonThrottled = throttle(this._showBalloon.bind(this), 50, {
11838
- leading: true
11839
- });
11840
- editor.on('ready', this._handleEditorReady.bind(this));
11878
+ this._balloonClass = options.balloonClass;
11879
+ editor.on('ready', ()=>this._handleEditorReady());
11841
11880
  }
11842
11881
  /**
11843
- * Destroys the "powered by" helper along with its view.
11882
+ * Destroys the badge along with its view.
11844
11883
  */ destroy() {
11845
11884
  const balloon = this._balloonView;
11846
11885
  if (balloon) {
11847
11886
  // Balloon gets destroyed by the body collection.
11848
- // The powered by view gets destroyed by the balloon.
11887
+ // The badge view gets destroyed by the balloon.
11849
11888
  balloon.unpin();
11850
11889
  this._balloonView = null;
11851
11890
  }
@@ -11853,14 +11892,13 @@ const DEFAULT_LABEL = 'Powered by';
11853
11892
  this.stopListening();
11854
11893
  }
11855
11894
  /**
11856
- * Enables "powered by" label once the editor (ui) is ready.
11895
+ * Enables badge label once the editor (ui) is ready.
11857
11896
  */ _handleEditorReady() {
11858
11897
  const editor = this.editor;
11859
- const forceVisible = !!editor.config.get('ui.poweredBy.forceVisible');
11860
- /* istanbul ignore next -- @preserve */ if (!forceVisible && verifyLicense(editor.config.get('licenseKey')) === 'VALID') {
11898
+ if (!this._isEnabled()) {
11861
11899
  return;
11862
11900
  }
11863
- // No view means no body collection to append the powered by balloon to.
11901
+ // No view means no body collection to append the badge balloon to.
11864
11902
  if (!editor.ui.view) {
11865
11903
  return;
11866
11904
  }
@@ -11883,41 +11921,63 @@ const DEFAULT_LABEL = 'Powered by';
11883
11921
  });
11884
11922
  }
11885
11923
  /**
11886
- * Creates an instance of the {@link module:ui/panel/balloon/balloonpanelview~BalloonPanelView balloon panel}
11887
- * with the "powered by" view inside ready for positioning.
11888
- */ _createBalloonView() {
11889
- const editor = this.editor;
11890
- const balloon = this._balloonView = new BalloonPanelView();
11891
- const poweredByConfig = getNormalizedConfig(editor);
11892
- const view = new PoweredByView(editor.locale, poweredByConfig.label);
11893
- balloon.content.add(view);
11894
- balloon.set({
11895
- class: 'ck-powered-by-balloon'
11896
- });
11897
- editor.ui.view.body.add(balloon);
11898
- this._balloonView = balloon;
11924
+ * Returns normalized configuration for the badge.
11925
+ */ _getNormalizedConfig() {
11926
+ return {
11927
+ side: this.editor.locale.contentLanguageDirection === 'ltr' ? 'right' : 'left',
11928
+ position: 'border',
11929
+ verticalOffset: 0,
11930
+ horizontalOffset: 5
11931
+ };
11899
11932
  }
11900
11933
  /**
11901
- * Attempts to display the balloon with the "powered by" view.
11934
+ * Attempts to display the balloon with the badge view.
11902
11935
  */ _showBalloon() {
11903
- if (!this._lastFocusedEditableElement) {
11936
+ const attachOptions = this._getBalloonAttachOptions();
11937
+ if (!attachOptions) {
11904
11938
  return;
11905
11939
  }
11906
- const attachOptions = getBalloonAttachOptions(this.editor, this._lastFocusedEditableElement);
11907
- if (attachOptions) {
11908
- if (!this._balloonView) {
11909
- this._createBalloonView();
11910
- }
11911
- this._balloonView.pin(attachOptions);
11940
+ if (!this._balloonView) {
11941
+ this._balloonView = this._createBalloonView();
11912
11942
  }
11943
+ this._balloonView.pin(attachOptions);
11913
11944
  }
11914
11945
  /**
11915
- * Hides the "powered by" balloon if already visible.
11946
+ * Hides the badge balloon if already visible.
11916
11947
  */ _hideBalloon() {
11917
11948
  if (this._balloonView) {
11918
11949
  this._balloonView.unpin();
11919
11950
  }
11920
11951
  }
11952
+ /**
11953
+ * Creates an instance of the {@link module:ui/panel/balloon/balloonpanelview~BalloonPanelView balloon panel}
11954
+ * with the badge view inside ready for positioning.
11955
+ */ _createBalloonView() {
11956
+ const editor = this.editor;
11957
+ const balloon = new BalloonPanelView();
11958
+ const view = this._createBadgeContent();
11959
+ balloon.content.add(view);
11960
+ if (this._balloonClass) {
11961
+ balloon.class = this._balloonClass;
11962
+ }
11963
+ editor.ui.view.body.add(balloon);
11964
+ return balloon;
11965
+ }
11966
+ /**
11967
+ * Returns the options for attaching the balloon to the focused editable element.
11968
+ */ _getBalloonAttachOptions() {
11969
+ if (!this._lastFocusedEditableElement) {
11970
+ return null;
11971
+ }
11972
+ const badgeConfig = this._getNormalizedConfig();
11973
+ const positioningFunction = badgeConfig.side === 'right' ? getLowerRightCornerPosition(this._lastFocusedEditableElement, badgeConfig) : getLowerLeftCornerPosition(this._lastFocusedEditableElement, badgeConfig);
11974
+ return {
11975
+ target: this._lastFocusedEditableElement,
11976
+ positions: [
11977
+ positioningFunction
11978
+ ]
11979
+ };
11980
+ }
11921
11981
  /**
11922
11982
  * Updates the {@link #_lastFocusedEditableElement} based on the state of the global focus tracker.
11923
11983
  */ _updateLastFocusedEditableElement() {
@@ -11934,17 +11994,113 @@ const DEFAULT_LABEL = 'Powered by';
11934
11994
  if (editableEditorElements.includes(focusedElement)) {
11935
11995
  this._lastFocusedEditableElement = focusedElement;
11936
11996
  } else {
11937
- // If it's none of the editable element, then the focus is somewhere in the UI. Let's display powered by
11997
+ // If it's none of the editable element, then the focus is somewhere in the UI. Let's display the badge
11938
11998
  // over the first element then.
11939
11999
  this._lastFocusedEditableElement = editableEditorElements[0];
11940
12000
  }
11941
12001
  }
11942
12002
  }
12003
+ function getLowerRightCornerPosition(focusedEditableElement, config) {
12004
+ return getLowerCornerPosition(focusedEditableElement, config, (rootRect, balloonRect)=>{
12005
+ return rootRect.left + rootRect.width - balloonRect.width - config.horizontalOffset;
12006
+ });
12007
+ }
12008
+ function getLowerLeftCornerPosition(focusedEditableElement, config) {
12009
+ return getLowerCornerPosition(focusedEditableElement, config, (rootRect)=>rootRect.left + config.horizontalOffset);
12010
+ }
12011
+ function getLowerCornerPosition(focusedEditableElement, config, getBalloonLeft) {
12012
+ return (visibleEditableElementRect, balloonRect)=>{
12013
+ const editableElementRect = new Rect(focusedEditableElement);
12014
+ if (editableElementRect.width < NARROW_ROOT_WIDTH_THRESHOLD || editableElementRect.height < NARROW_ROOT_HEIGHT_THRESHOLD) {
12015
+ return null;
12016
+ }
12017
+ let balloonTop;
12018
+ if (config.position === 'inside') {
12019
+ balloonTop = editableElementRect.bottom - balloonRect.height;
12020
+ } else {
12021
+ balloonTop = editableElementRect.bottom - balloonRect.height / 2;
12022
+ }
12023
+ balloonTop -= config.verticalOffset;
12024
+ const balloonLeft = getBalloonLeft(editableElementRect, balloonRect);
12025
+ // Clone the editable element rect and place it where the balloon would be placed.
12026
+ // This will allow getVisible() to work from editable element's perspective (rect source).
12027
+ // and yield a result as if the balloon was on the same (scrollable) layer as the editable element.
12028
+ const newBalloonPositionRect = visibleEditableElementRect.clone().moveTo(balloonLeft, balloonTop).getIntersection(balloonRect.clone().moveTo(balloonLeft, balloonTop));
12029
+ const newBalloonPositionVisibleRect = newBalloonPositionRect.getVisible();
12030
+ if (!newBalloonPositionVisibleRect || newBalloonPositionVisibleRect.getArea() < balloonRect.getArea()) {
12031
+ return null;
12032
+ }
12033
+ return {
12034
+ top: balloonTop,
12035
+ left: balloonLeft,
12036
+ name: `position_${config.position}-side_${config.side}`,
12037
+ config: {
12038
+ withArrow: false
12039
+ }
12040
+ };
12041
+ };
12042
+ }
12043
+
12044
+ var poweredByIcon = "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"53\" height=\"10\" viewBox=\"0 0 53 10\"><path fill=\"#1C2331\" d=\"M31.724 1.492a15.139 15.139 0 0 0 .045 1.16 2.434 2.434 0 0 0-.687-.34 3.68 3.68 0 0 0-1.103-.166 2.332 2.332 0 0 0-1.14.255 1.549 1.549 0 0 0-.686.87c-.15.41-.225.98-.225 1.712 0 .939.148 1.659.444 2.161.297.503.792.754 1.487.754.452.015.9-.094 1.294-.316.296-.174.557-.4.771-.669l.14.852h1.282V.007h-1.623v1.485ZM31 6.496a1.77 1.77 0 0 1-.494.061.964.964 0 0 1-.521-.127.758.758 0 0 1-.296-.466 3.984 3.984 0 0 1-.093-.992 4.208 4.208 0 0 1 .098-1.052.753.753 0 0 1 .307-.477 1.08 1.08 0 0 1 .55-.122c.233-.004.466.026.69.089l.483.144v2.553c-.11.076-.213.143-.307.2a1.73 1.73 0 0 1-.417.189ZM35.68 0l-.702.004c-.322.002-.482.168-.48.497l.004.581c.002.33.164.493.486.49l.702-.004c.322-.002.481-.167.48-.496L36.165.49c-.002-.33-.164-.493-.486-.491ZM36.145 2.313l-1.612.01.034 5.482 1.613-.01-.035-5.482ZM39.623.79 37.989.8 38 2.306l-.946.056.006 1.009.949-.006.024 2.983c.003.476.143.844.419 1.106.275.26.658.39 1.148.387.132 0 .293-.01.483-.03.19-.02.38-.046.57-.08.163-.028.324-.068.482-.119l-.183-1.095-.702.004a.664.664 0 0 1-.456-.123.553.553 0 0 1-.14-.422l-.016-2.621 1.513-.01-.006-1.064-1.514.01-.01-1.503ZM46.226 2.388c-.41-.184-.956-.274-1.636-.27-.673.004-1.215.101-1.627.29-.402.179-.72.505-.888.91-.18.419-.268.979-.264 1.68.004.688.1 1.24.285 1.655.172.404.495.724.9.894.414.18.957.268 1.63.264.68-.004 1.224-.099 1.632-.284.4-.176.714-.501.878-.905.176-.418.263-.971.258-1.658-.004-.702-.097-1.261-.28-1.677a1.696 1.696 0 0 0-.888-.9Zm-.613 3.607a.77.77 0 0 1-.337.501 1.649 1.649 0 0 1-1.317.009.776.776 0 0 1-.343-.497 4.066 4.066 0 0 1-.105-1.02 4.136 4.136 0 0 1 .092-1.03.786.786 0 0 1 .337-.507 1.59 1.59 0 0 1 1.316-.008.79.79 0 0 1 .344.502c.078.337.113.683.105 1.03.012.343-.019.685-.092 1.02ZM52.114 2.07a2.67 2.67 0 0 0-1.128.278c-.39.191-.752.437-1.072.73l-.157-.846-1.273.008.036 5.572 1.623-.01-.024-3.78c.35-.124.646-.22.887-.286.26-.075.53-.114.8-.118l.45-.003.144-1.546-.286.001ZM22.083 7.426l-1.576-2.532a2.137 2.137 0 0 0-.172-.253 1.95 1.95 0 0 0-.304-.29.138.138 0 0 1 .042-.04 1.7 1.7 0 0 0 .328-.374l1.75-2.71c.01-.015.025-.028.024-.048-.01-.01-.021-.007-.031-.007L20.49 1.17a.078.078 0 0 0-.075.045l-.868 1.384c-.23.366-.46.732-.688 1.099a.108.108 0 0 1-.112.06c-.098-.005-.196-.001-.294-.002-.018 0-.038.006-.055-.007.002-.02.002-.039.005-.058a4.6 4.6 0 0 0 .046-.701V1.203c0-.02-.009-.032-.03-.03h-.033L16.93 1.17c-.084 0-.073-.01-.073.076v6.491c-.001.018.006.028.025.027h1.494c.083 0 .072.007.072-.071v-2.19c0-.055-.003-.11-.004-.166a3.366 3.366 0 0 0-.05-.417h.06c.104 0 .209.002.313-.002a.082.082 0 0 1 .084.05c.535.913 1.07 1.824 1.607 2.736a.104.104 0 0 0 .103.062c.554-.003 1.107-.002 1.66-.002l.069-.003-.019-.032-.188-.304ZM27.112 6.555c-.005-.08-.004-.08-.082-.08h-2.414c-.053 0-.106-.003-.159-.011a.279.279 0 0 1-.246-.209.558.558 0 0 1-.022-.15c0-.382 0-.762-.002-1.143 0-.032.007-.049.042-.044h2.504c.029.003.037-.012.034-.038V3.814c0-.089.013-.078-.076-.078h-2.44c-.07 0-.062.003-.062-.06v-.837c0-.047.004-.093.013-.14a.283.283 0 0 1 .241-.246.717.717 0 0 1 .146-.011h2.484c.024.002.035-.009.036-.033l.003-.038.03-.496c.01-.183.024-.365.034-.548.005-.085.003-.087-.082-.094-.218-.018-.437-.038-.655-.05a17.845 17.845 0 0 0-.657-.026 72.994 72.994 0 0 0-1.756-.016 1.7 1.7 0 0 0-.471.064 1.286 1.286 0 0 0-.817.655c-.099.196-.149.413-.145.633v3.875c0 .072.003.144.011.216a1.27 1.27 0 0 0 .711 1.029c.228.113.48.167.734.158.757-.005 1.515.002 2.272-.042.274-.016.548-.034.82-.053.03-.002.043-.008.04-.041-.008-.104-.012-.208-.019-.312a69.964 69.964 0 0 1-.05-.768ZM16.14 7.415l-.127-1.075c-.004-.03-.014-.04-.044-.037a13.125 13.125 0 0 1-.998.073c-.336.01-.672.02-1.008.016-.116-.001-.233-.014-.347-.039a.746.746 0 0 1-.45-.262c-.075-.1-.132-.211-.167-.33a3.324 3.324 0 0 1-.126-.773 9.113 9.113 0 0 1-.015-.749c0-.285.022-.57.065-.852.023-.158.066-.312.127-.46a.728.728 0 0 1 .518-.443 1.64 1.64 0 0 1 .397-.048c.628-.001 1.255.003 1.882.05.022.001.033-.006.036-.026l.003-.031.06-.55c.019-.177.036-.355.057-.532.004-.034-.005-.046-.04-.056a5.595 5.595 0 0 0-1.213-.21 10.783 10.783 0 0 0-.708-.02c-.24-.003-.48.01-.719.041a3.477 3.477 0 0 0-.625.14 1.912 1.912 0 0 0-.807.497c-.185.2-.33.433-.424.688a4.311 4.311 0 0 0-.24 1.096c-.031.286-.045.572-.042.86-.006.43.024.86.091 1.286.04.25.104.497.193.734.098.279.26.53.473.734.214.205.473.358.756.446.344.11.702.17 1.063.177a8.505 8.505 0 0 0 1.578-.083 6.11 6.11 0 0 0 .766-.18c.03-.008.047-.023.037-.057a.157.157 0 0 1-.003-.025Z\"/><path fill=\"#AFE229\" d=\"M6.016 6.69a1.592 1.592 0 0 0-.614.21c-.23.132-.422.32-.56.546-.044.072-.287.539-.287.539l-.836 1.528.009.006c.038.025.08.046.123.063.127.046.26.07.395.073.505.023 1.011-.007 1.517-.003.29.009.58.002.869-.022a.886.886 0 0 0 .395-.116.962.962 0 0 0 .312-.286c.056-.083.114-.163.164-.249.24-.408.48-.816.718-1.226.075-.128.148-.257.222-.386l.112-.192a1.07 1.07 0 0 0 .153-.518l-1.304.023s-1.258-.005-1.388.01Z\"/><path fill=\"#771BFF\" d=\"m2.848 9.044.76-1.39.184-.352c-.124-.067-.245-.14-.367-.21-.346-.204-.706-.384-1.045-.6a.984.984 0 0 1-.244-.207c-.108-.134-.136-.294-.144-.46-.021-.409-.002-.818-.009-1.227-.003-.195 0-.39.003-.585.004-.322.153-.553.427-.713l.833-.488c.22-.13.44-.257.662-.385.05-.029.105-.052.158-.077.272-.128.519-.047.76.085l.044.028c.123.06.242.125.358.196.318.178.635.357.952.537.095.056.187.117.275.184.194.144.254.35.266.578.016.284.007.569.006.853-.001.28.004.558 0 .838.592-.003 1.259 0 1.259 0l.723-.013c-.003-.292-.007-.584-.007-.876 0-.524.015-1.048-.016-1.571-.024-.42-.135-.8-.492-1.067a5.02 5.02 0 0 0-.506-.339A400.52 400.52 0 0 0 5.94.787C5.722.664 5.513.524 5.282.423 5.255.406 5.228.388 5.2.373 4.758.126 4.305-.026 3.807.21c-.097.046-.197.087-.29.14A699.896 699.896 0 0 0 .783 1.948c-.501.294-.773.717-.778 1.31-.004.36-.009.718-.001 1.077.016.754-.017 1.508.024 2.261.016.304.07.6.269.848.127.15.279.28.448.382.622.4 1.283.734 1.92 1.11l.183.109Z\"/></svg>\n";
12045
+
12046
+ const DEFAULT_LABEL = 'Powered by';
12047
+ /**
12048
+ * A helper that enables the "powered by" feature in the editor and renders a link to the project's
12049
+ * webpage next to the bottom of the editable element (editor root, source editing area, etc.) when the editor is focused.
12050
+ *
12051
+ * @private
12052
+ */ class PoweredBy extends Badge {
12053
+ constructor(editor){
12054
+ super(editor, {
12055
+ balloonClass: 'ck-powered-by-balloon'
12056
+ });
12057
+ }
12058
+ /**
12059
+ * Enables "powered by" label.
12060
+ */ _isEnabled() {
12061
+ const editor = this.editor;
12062
+ const forceVisible = editor.config.get('ui.poweredBy.forceVisible');
12063
+ if (forceVisible) {
12064
+ return true;
12065
+ }
12066
+ const licenseKey = editor.config.get('licenseKey');
12067
+ if (licenseKey == 'GPL') {
12068
+ return true;
12069
+ }
12070
+ const licenseContent = parseBase64EncodedObject(licenseKey.split('.')[1]);
12071
+ if (!licenseContent) {
12072
+ return true;
12073
+ }
12074
+ return !licenseContent.whiteLabel;
12075
+ }
12076
+ /**
12077
+ * Creates a "powered by" badge content.
12078
+ */ _createBadgeContent() {
12079
+ return new PoweredByView(this.editor.locale, this._getNormalizedConfig().label);
12080
+ }
12081
+ /**
12082
+ * Returns the normalized configuration for the "powered by" badge.
12083
+ * It takes the user configuration into account and falls back to the default one.
12084
+ */ _getNormalizedConfig() {
12085
+ const badgeConfig = super._getNormalizedConfig();
12086
+ const userConfig = this.editor.config.get('ui.poweredBy') || {};
12087
+ const position = userConfig.position || badgeConfig.position;
12088
+ const verticalOffset = position === 'inside' ? 5 : badgeConfig.verticalOffset;
12089
+ return {
12090
+ position,
12091
+ side: userConfig.side || badgeConfig.side,
12092
+ label: userConfig.label === undefined ? DEFAULT_LABEL : userConfig.label,
12093
+ verticalOffset: userConfig.verticalOffset !== undefined ? userConfig.verticalOffset : verticalOffset,
12094
+ horizontalOffset: userConfig.horizontalOffset !== undefined ? userConfig.horizontalOffset : badgeConfig.horizontalOffset,
12095
+ forceVisible: !!userConfig.forceVisible
12096
+ };
12097
+ }
12098
+ }
11943
12099
  /**
11944
12100
  * A view displaying a "powered by" label and project logo wrapped in a link.
11945
12101
  */ class PoweredByView extends View {
11946
12102
  /**
11947
- * Created an instance of the "powered by" view.
12103
+ * Creates an instance of the "powered by" view.
11948
12104
  *
11949
12105
  * @param locale The localization services instance.
11950
12106
  * @param label The label text.
@@ -11956,14 +12112,6 @@ const DEFAULT_LABEL = 'Powered by';
11956
12112
  content: poweredByIcon,
11957
12113
  isColorInherited: false
11958
12114
  });
11959
- iconView.extendTemplate({
11960
- attributes: {
11961
- style: {
11962
- width: ICON_WIDTH + 'px',
11963
- height: ICON_HEIGHT + 'px'
11964
- }
11965
- }
11966
- });
11967
12115
  this.setTemplate({
11968
12116
  tag: 'div',
11969
12117
  attributes: {
@@ -12006,67 +12154,101 @@ const DEFAULT_LABEL = 'Powered by';
12006
12154
  });
12007
12155
  }
12008
12156
  }
12009
- function getBalloonAttachOptions(editor, focusedEditableElement) {
12010
- const poweredByConfig = getNormalizedConfig(editor);
12011
- const positioningFunction = poweredByConfig.side === 'right' ? getLowerRightCornerPosition(focusedEditableElement, poweredByConfig) : getLowerLeftCornerPosition(focusedEditableElement, poweredByConfig);
12012
- return {
12013
- target: focusedEditableElement,
12014
- positions: [
12015
- positioningFunction
12016
- ]
12157
+
12158
+ /**
12159
+ * A helper that enables the "evaluation badge" feature in the editor at the bottom of the editable element
12160
+ * (editor root, source editing area, etc.) when the editor is focused.
12161
+ *
12162
+ * @private
12163
+ */ class EvaluationBadge extends Badge {
12164
+ licenseTypeMessage = {
12165
+ evaluation: 'For evaluation purposes only',
12166
+ trial: 'For evaluation purposes only',
12167
+ development: 'For development purposes only'
12017
12168
  };
12018
- }
12019
- function getLowerRightCornerPosition(focusedEditableElement, config) {
12020
- return getLowerCornerPosition(focusedEditableElement, config, (rootRect, balloonRect)=>{
12021
- return rootRect.left + rootRect.width - balloonRect.width - config.horizontalOffset;
12022
- });
12023
- }
12024
- function getLowerLeftCornerPosition(focusedEditableElement, config) {
12025
- return getLowerCornerPosition(focusedEditableElement, config, (rootRect)=>rootRect.left + config.horizontalOffset);
12026
- }
12027
- function getLowerCornerPosition(focusedEditableElement, config, getBalloonLeft) {
12028
- return (visibleEditableElementRect, balloonRect)=>{
12029
- const editableElementRect = new Rect(focusedEditableElement);
12030
- if (editableElementRect.width < NARROW_ROOT_WIDTH_THRESHOLD || editableElementRect.height < NARROW_ROOT_HEIGHT_THRESHOLD) {
12031
- return null;
12032
- }
12033
- let balloonTop;
12034
- if (config.position === 'inside') {
12035
- balloonTop = editableElementRect.bottom - balloonRect.height;
12036
- } else {
12037
- balloonTop = editableElementRect.bottom - balloonRect.height / 2;
12038
- }
12039
- balloonTop -= config.verticalOffset;
12040
- const balloonLeft = getBalloonLeft(editableElementRect, balloonRect);
12041
- // Clone the editable element rect and place it where the balloon would be placed.
12042
- // This will allow getVisible() to work from editable element's perspective (rect source).
12043
- // and yield a result as if the balloon was on the same (scrollable) layer as the editable element.
12044
- const newBalloonPositionRect = visibleEditableElementRect.clone().moveTo(balloonLeft, balloonTop).getIntersection(balloonRect.clone().moveTo(balloonLeft, balloonTop));
12045
- const newBalloonPositionVisibleRect = newBalloonPositionRect.getVisible();
12046
- if (!newBalloonPositionVisibleRect || newBalloonPositionVisibleRect.getArea() < balloonRect.getArea()) {
12047
- return null;
12048
- }
12169
+ constructor(editor){
12170
+ super(editor, {
12171
+ balloonClass: 'ck-evaluation-badge-balloon'
12172
+ });
12173
+ }
12174
+ /**
12175
+ * Enables "evaluation badge" label.
12176
+ */ _isEnabled() {
12177
+ const editor = this.editor;
12178
+ const licenseKey = editor.config.get('licenseKey');
12179
+ const licenseType = getLicenseTypeFromLicenseKey(licenseKey);
12180
+ return Boolean(licenseType && this.licenseTypeMessage[licenseType]);
12181
+ }
12182
+ /**
12183
+ * Creates the content of the "evaluation badge".
12184
+ */ _createBadgeContent() {
12185
+ const licenseKey = this.editor.config.get('licenseKey');
12186
+ const licenseType = getLicenseTypeFromLicenseKey(licenseKey);
12187
+ return new EvaluationBadgeView(this.editor.locale, this.licenseTypeMessage[licenseType]);
12188
+ }
12189
+ /**
12190
+ * Returns the normalized configuration for the "evaluation badge".
12191
+ * It takes 'ui.poweredBy' configuration into account to determine the badge position and side.
12192
+ */ _getNormalizedConfig() {
12193
+ const badgeConfig = super._getNormalizedConfig();
12194
+ const userConfig = this.editor.config.get('ui.poweredBy') || {};
12195
+ const position = userConfig.position || badgeConfig.position;
12196
+ const poweredBySide = userConfig.side || badgeConfig.side;
12049
12197
  return {
12050
- top: balloonTop,
12051
- left: balloonLeft,
12052
- name: `position_${config.position}-side_${config.side}`,
12053
- config: {
12054
- withArrow: false
12055
- }
12198
+ position,
12199
+ side: poweredBySide === 'left' ? 'right' : 'left',
12200
+ verticalOffset: badgeConfig.verticalOffset,
12201
+ horizontalOffset: badgeConfig.horizontalOffset
12056
12202
  };
12057
- };
12203
+ }
12058
12204
  }
12059
- function getNormalizedConfig(editor) {
12060
- const userConfig = editor.config.get('ui.poweredBy');
12061
- const position = userConfig && userConfig.position || 'border';
12062
- return {
12063
- position,
12064
- label: DEFAULT_LABEL,
12065
- verticalOffset: position === 'inside' ? 5 : 0,
12066
- horizontalOffset: 5,
12067
- side: editor.locale.contentLanguageDirection === 'ltr' ? 'right' : 'left',
12068
- ...userConfig
12069
- };
12205
+ /**
12206
+ * A view displaying the "evaluation badge".
12207
+ */ class EvaluationBadgeView extends View {
12208
+ /**
12209
+ * Creates an instance of the "evaluation badge" view.
12210
+ *
12211
+ * @param locale The localization services instance.
12212
+ * @param label The label text.
12213
+ */ constructor(locale, label){
12214
+ super(locale);
12215
+ this.setTemplate({
12216
+ tag: 'div',
12217
+ attributes: {
12218
+ class: [
12219
+ 'ck',
12220
+ 'ck-evaluation-badge'
12221
+ ],
12222
+ 'aria-hidden': true
12223
+ },
12224
+ children: [
12225
+ {
12226
+ tag: 'span',
12227
+ attributes: {
12228
+ class: [
12229
+ 'ck',
12230
+ 'ck-evaluation-badge__label'
12231
+ ]
12232
+ },
12233
+ children: [
12234
+ label
12235
+ ]
12236
+ }
12237
+ ]
12238
+ });
12239
+ }
12240
+ }
12241
+ /**
12242
+ * Returns the license type based on the license key.
12243
+ */ function getLicenseTypeFromLicenseKey(licenseKey) {
12244
+ if (licenseKey == 'GPL') {
12245
+ return 'GPL';
12246
+ }
12247
+ const licenseContent = parseBase64EncodedObject(licenseKey.split('.')[1]);
12248
+ if (!licenseContent) {
12249
+ return null;
12250
+ }
12251
+ return licenseContent.licenseType || 'production';
12070
12252
  }
12071
12253
 
12072
12254
  /**
@@ -12737,6 +12919,7 @@ const NESTED_PANEL_HORIZONTAL_OFFSET = 5;
12737
12919
  * groupId: 'insertInline',
12738
12920
  * items: [
12739
12921
  * 'menuBar:link',
12922
+ * 'menuBar:bookmark',
12740
12923
  * 'menuBar:comment',
12741
12924
  * 'menuBar:insertMergeField'
12742
12925
  * ]
@@ -12993,6 +13176,7 @@ const DefaultMenuBarItems = [
12993
13176
  groupId: 'insertInline',
12994
13177
  items: [
12995
13178
  'menuBar:link',
13179
+ 'menuBar:bookmark',
12996
13180
  'menuBar:comment',
12997
13181
  'menuBar:insertMergeField'
12998
13182
  ]
@@ -13610,6 +13794,9 @@ function isMenuDefinition(definition) {
13610
13794
  /**
13611
13795
  * A helper that enables the "powered by" feature in the editor and renders a link to the project's webpage.
13612
13796
  */ poweredBy;
13797
+ /**
13798
+ * A helper that enables the "evaluation badge" feature in the editor.
13799
+ */ evaluationBadge;
13613
13800
  /**
13614
13801
  * A helper that manages the content of an `aria-live` regions used by editor features to announce status changes
13615
13802
  * to screen readers.
@@ -13644,6 +13831,7 @@ function isMenuDefinition(definition) {
13644
13831
  this.focusTracker = new FocusTracker();
13645
13832
  this.tooltipManager = new TooltipManager(editor);
13646
13833
  this.poweredBy = new PoweredBy(editor);
13834
+ this.evaluationBadge = new EvaluationBadge(editor);
13647
13835
  this.ariaLiveAnnouncer = new AriaLiveAnnouncer(editor);
13648
13836
  this.set('viewportOffset', this._readViewportOffsetFromConfig());
13649
13837
  this.once('ready', ()=>{
@@ -13684,6 +13872,7 @@ function isMenuDefinition(definition) {
13684
13872
  this.focusTracker.destroy();
13685
13873
  this.tooltipManager.destroy(this.editor);
13686
13874
  this.poweredBy.destroy();
13875
+ this.evaluationBadge.destroy();
13687
13876
  // Clean–up the references to the CKEditor instance stored in the native editable DOM elements.
13688
13877
  for (const domElement of this._editableElementsMap.values()){
13689
13878
  domElement.ckeditorInstance = null;
@@ -17204,7 +17393,7 @@ const toPx = /* #__PURE__ */ toUnit('px');
17204
17393
  fitInViewport: true,
17205
17394
  positions: this._panelPositions
17206
17395
  });
17207
- this.panelView.position = optimalPanelPosition ? optimalPanelPosition.name : this._panelPositions[0].name;
17396
+ this.panelView.position = optimalPanelPosition ? optimalPanelPosition.name : this._defaultMenuPositionName;
17208
17397
  });
17209
17398
  }
17210
17399
  /**
@@ -17251,6 +17440,24 @@ const toPx = /* #__PURE__ */ toUnit('px');
17251
17440
  }
17252
17441
  }
17253
17442
  }
17443
+ /**
17444
+ * The default position of the panel when the menu is opened.
17445
+ * It is used when the optimal position cannot be calculated.
17446
+ */ get _defaultMenuPositionName() {
17447
+ if (this.locale.uiLanguageDirection === 'ltr') {
17448
+ if (this.parentMenuView) {
17449
+ return 'es';
17450
+ } else {
17451
+ return 'se';
17452
+ }
17453
+ } else {
17454
+ if (this.parentMenuView) {
17455
+ return 'ws';
17456
+ } else {
17457
+ return 'sw';
17458
+ }
17459
+ }
17460
+ }
17254
17461
  /**
17255
17462
  * A function used to calculate the optimal position for the dropdown panel.
17256
17463
  *