Skip to content

Commit

Permalink
Merge branch 'master' into ck/16719
Browse files Browse the repository at this point in the history
  • Loading branch information
oleq committed Aug 22, 2024

Verified

This commit was signed with the committer’s verified signature. The key has expired.
hoangnt2 Nguyen Thai Hoang
2 parents ef327bb + eda3866 commit f109659
Showing 60 changed files with 3,779 additions and 117 deletions.
42 changes: 42 additions & 0 deletions .github/workflows/codeql.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
name: "CodeQL"
on:
push:
branches: [ "master", "stable", "release" ]
pull_request:
branches: [ "master", "stable", "release" ]
schedule:
- cron: '0 22 * * SUN'

jobs:
analyze:
name: Analyze (${{ matrix.language }})
runs-on: 'ubuntu-latest'
timeout-minutes: 360
permissions:
security-events: write
packages: read
actions: read
contents: read
strategy:
fail-fast: false
matrix:
include:
- language: javascript-typescript
build-mode: none
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Initialize CodeQL
uses: github/codeql-action/init@v3
with:
languages: ${{ matrix.language }}
build-mode: ${{ matrix.build-mode }}
config: |
paths-ignore:
- tests
- 'packages/*/tests'
- scripts
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v3
with:
category: "/language:${{matrix.language}}"
48 changes: 48 additions & 0 deletions docs/framework/architecture/ui-components.md
Original file line number Diff line number Diff line change
@@ -387,6 +387,54 @@ toolbarDropdown.render();
document.getElementById( 'toolbar-button' ).append( toolbarDropdown.element );
```

### Menu

Finally, you can add a multi-level menu to a dropdown. Use the {@link module:ui/dropdown/utils~addMenuToDropdown `addMenuToDropdown()`} helper function to simplify the process.

```js
import {
addMenuToDropdown,
createDropdown
} from 'ckeditor5';

const locale = new Locale(); // Can be `editor.locale`.
const body = new BodyCollection(); // Can be `editor.ui.view.body`.

const menuDropdown = createDropdown( locale );

// The menu items definitions.
const definition = [
{
id: 'menu_1',
menu: 'Menu 1',
children: [
{
id: 'menu_1_a',
label: 'Item A'
},
{
id: 'menu_1_b',
label: 'Item B'
}
]
},
{
id: 'top_a',
label: 'Top Item A'
},
{
id: 'top_b',
label: 'Top Item B'
}
];

addMenuToDropdown( menuDropdown, body, definition );

menuDropdown.render();

document.getElementById( 'menu-dropdown' ).append( menuDropdown.element );
```

### Split button

Besides the standard button, you can also use the split button for a dropdown. It has two clickable sections: one for the main action and a second for expanding the dropdown with more options.
61 changes: 55 additions & 6 deletions docs/framework/architecture/ui-library.md
Original file line number Diff line number Diff line change
@@ -310,10 +310,11 @@ The button can be either:

The dropdown panel exposes its {@link module:ui/dropdown/dropdownpanelview~DropdownPanelView#children children} collection which aggregates the child {@link module:ui/view~View views}. The most common views displayed in the dropdown panel are:

* {@link module:ui/list/listview~ListView}
* {@link module:ui/toolbar/toolbarview~ToolbarView}
* {@link module:ui/list/listview~ListView} - dropdown list
* {@link module:ui/toolbar/toolbarview~ToolbarView} - dropdown toolbar
* {@link module:ui/dropdown/menu/dropdownmenurootlistview~DropdownMenuRootListView} - dropdown menu

The framework provides a set of helpers to make the dropdown creation process easier. It is still possible to compose a custom dropdown from scratch using the base classes.
The framework provides a set of helpers to make the dropdown creation process easier. It is still possible to compose a custom dropdown from scratch using the base classes. However, for most needs, we highly recommend using provided helper functions.

The {@link module:ui/dropdown/utils~createDropdown} helper creates a {@link module:ui/dropdown/dropdownview~DropdownView} with either a {@link module:ui/button/buttonview~ButtonView} or a {@link module:ui/dropdown/button/splitbuttonview~SplitButtonView}.

@@ -428,7 +429,6 @@ A {@link module:ui/toolbar/toolbarview~ToolbarView} can be added to a dropdown u
```js
import { ButtonView, SplitButtonView, addToolbarToDropdown, createDropdown } from 'ckeditor5';


const buttons = [];

// Add a simple button to the array of toolbar items.
@@ -452,6 +452,55 @@ dropdownView.bind( 'isEnabled' ).toMany( buttons, 'isEnabled',
);
```

#### Adding a menu to a dropdown

A multi-level menu can be added to a dropdown using the {@link module:ui/dropdown/utils~addMenuToDropdown} helper.

```js
import { addMenuToDropdown, createDropdown } from 'ckeditor5';

// The default dropdown.
const dropdownView = createDropdown( editor.locale );

// The menu items definitions.
const definition = [
{
id: 'menu_1',
menu: 'Menu 1',
children: [
{
id: 'menu_1_a',
label: 'Item A'
},
{
id: 'menu_1_b',
label: 'Item B'
}
]
},
{
id: 'top_a',
label: 'Top Item A'
},
{
id: 'top_b',
label: 'Top Item B'
}
];

addMenuToDropdown( dropdownView, editor.body.ui.view, definition );
```

Most probably you will want to perform some action when one of the defined buttons is pressed:

```js
dropdownView.on( 'execute', evt => {
const id = evt.source.id;

console.log( id ); // E.g. will print "menu_1_a" when "Item A" is pressed.
} );
```

### Dialogs and modals

The framework provides the UI dialog component. The dialog system in CKEditor 5 is brought by the {@link module:ui/dialog/dialog~Dialog `Dialog` plugin}. It offers API for displaying [views](#views) in dialogs. In a sense, this plugin corresponds to another one that manages views in balloons (popovers) across the UI ({@link module:ui/panel/balloon/contextualballoon~ContextualBalloon `ContextualBalloon` plugin}).
@@ -589,7 +638,7 @@ editor.plugins.get( 'Dialog' ).show( {
}
},
{
label: 'This button will be enabled in 5...'
label: 'This button will be enabled in 5...',
withText: true,
onCreate: buttonView => {
buttonView.isEnabled = false;
@@ -745,7 +794,7 @@ You can also pass such code directly in the `show()` method call in the `onShow`
```js
editor.plugins.get( 'Dialog' ).show( {
onShow: dialog => {
dialog.view!.on( 'close', ( evt, data ) => {
dialog.view.on( 'close', ( evt, data ) => {
if ( data.source === 'escKeyPress' ) {
evt.stop();
}
2 changes: 1 addition & 1 deletion docs/getting-started/index.md
Original file line number Diff line number Diff line change
@@ -26,7 +26,7 @@ If you are familiar with our previous, discontinued product and would like to sw

## CKEditor 5 framework integrations

Do you to use a framework? Native integrations with the most popular libraries will save you time and effort. There are five official integrations so far:
Do you want to use a framework? Native integrations with the most popular libraries will save you time and effort. There are five official integrations so far:

* {@link getting-started/integrations/angular Integrate CKEditor 5 with Angular}
* {@link getting-started/integrations/react Integrate CKEditor 5 with React}
3 changes: 3 additions & 0 deletions packages/ckeditor5-cloud-services/src/cloudservicesconfig.ts
Original file line number Diff line number Diff line change
@@ -88,6 +88,9 @@ export interface CloudServicesConfig {
* } )
* ```
*
* If the request to the token endpoint fails, the editor will call the token request function every 5 seconds in attempt
* to refresh the token.
*
* You can find more information about token endpoints in the
* {@glink @cs guides/easy-image/quick-start#create-token-endpoint Cloud Services - Quick start}
* and {@glink @cs developer-resources/security/token-endpoint Cloud Services - Token endpoint} documentation.
60 changes: 52 additions & 8 deletions packages/ckeditor5-cloud-services/src/token/token.ts
Original file line number Diff line number Diff line change
@@ -9,15 +9,17 @@

/* globals XMLHttpRequest, setTimeout, clearTimeout, atob */

import { ObservableMixin, CKEditorError } from 'ckeditor5/src/utils.js';
import { ObservableMixin, CKEditorError, logWarning } from 'ckeditor5/src/utils.js';
import type { TokenUrl } from '../cloudservicesconfig.js';

const DEFAULT_OPTIONS = { autoRefresh: true };
const DEFAULT_TOKEN_REFRESH_TIMEOUT_TIME = 3600000;
const DEFAULT_TOKEN_REFRESH_TIMEOUT_TIME = 3600000; // 1 hour
const TOKEN_FAILED_REFRESH_TIMEOUT_TIME = 5000; // 5 seconds

/**
* Class representing the token used for communication with CKEditor Cloud Services.
* Value of the token is retrieving from the specified URL and is refreshed every 1 hour by default.
* The class representing the token used for communication with CKEditor Cloud Services.
* The value of the token is retrieved from the specified URL and refreshed every 1 hour by default.
* If the token retrieval fails, the token will automatically retry in 5 seconds intervals.
*/
export default class Token extends /* #__PURE__ */ ObservableMixin() {
/**
@@ -36,8 +38,14 @@ export default class Token extends /* #__PURE__ */ ObservableMixin() {
*/
private _refresh: () => Promise<string>;

/**
* Cached token options.
*/
private _options: { initValue?: string; autoRefresh: boolean };

/**
* `setTimeout()` id for a token refresh when {@link module:cloud-services/token/token~TokenOptions auto refresh} is enabled.
*/
private _tokenRefreshTimeout?: ReturnType<typeof setTimeout>;

/**
@@ -99,19 +107,55 @@ export default class Token extends /* #__PURE__ */ ObservableMixin() {
}

/**
* Refresh token method. Useful in a method form as it can be override in tests.
* Refresh token method. Useful in a method form as it can be overridden in tests.
*
* This method will be invoked periodically based on the token expiry date after first call to keep the token up-to-date
* (requires {@link module:cloud-services/token/token~TokenOptions auto refresh option} to be set).
*
* If the token refresh fails, the method will retry in 5 seconds intervals until success or the token gets
* {@link #destroy destroyed}.
*/
public refreshToken(): Promise<InitializedToken> {
const autoRefresh = this._options.autoRefresh;

return this._refresh()
.then( value => {
this._validateTokenValue( value );
this.set( 'value', value );

if ( this._options.autoRefresh ) {
if ( autoRefresh ) {
this._registerRefreshTokenTimeout();
}

return this as InitializedToken;
} )
.catch( err => {
/**
* You will see this warning when the CKEditor {@link module:cloud-services/token/token~Token token} could not be refreshed.
* This may be a result of a network error, a token endpoint (server) error, or an invalid
* {@link module:cloud-services/cloudservicesconfig~CloudServicesConfig#tokenUrl token URL configuration}.
*
* If this warning repeats, please make sure that the configuration is correct and that the token
* endpoint is up and running. {@link module:cloud-services/cloudservicesconfig~CloudServicesConfig#tokenUrl Learn more}
* about token configuration.
*
* **Note:** If the token's {@link module:cloud-services/token/token~TokenOptions auto refresh option} is enabled,
* attempts to refresh will be made until success or token's
* {@link module:cloud-services/token/token~Token#destroy destruction}.
*
* @error token-refresh-failed
* @param autoRefresh Whether the token will keep auto refreshing.
*/
logWarning( 'token-refresh-failed', { autoRefresh } );

// If the refresh failed, keep trying to refresh the token. Failing to do so will eventually
// lead to the disconnection from the RTC service and the editing session (and potential data loss
// if the user keeps editing).
if ( autoRefresh ) {
this._registerRefreshTokenTimeout( TOKEN_FAILED_REFRESH_TIMEOUT_TIME );
}

throw err;
} );
}

@@ -151,8 +195,8 @@ export default class Token extends /* #__PURE__ */ ObservableMixin() {
/**
* Registers a refresh token timeout for the time taken from token.
*/
private _registerRefreshTokenTimeout() {
const tokenRefreshTimeoutTime = this._getTokenRefreshTimeoutTime();
private _registerRefreshTokenTimeout( timeoutTime?: number ) {
const tokenRefreshTimeoutTime = timeoutTime || this._getTokenRefreshTimeoutTime();

clearTimeout( this._tokenRefreshTimeout );

Loading

0 comments on commit f109659

Please sign in to comment.