Skip to content

Commit

Permalink
Merge pull request #550 from ckeditor/editor-integration/react/add-us…
Browse files Browse the repository at this point in the history
…age-data

Feature: Align integration to work with self-service for premium features.
  • Loading branch information
Mati365 authored Nov 12, 2024
2 parents 130010f + 5454c6e commit 506c297
Show file tree
Hide file tree
Showing 12 changed files with 227 additions and 12 deletions.
4 changes: 2 additions & 2 deletions demos/cdn-react/getCKCdnClassicEditor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@
*/

import type { ClassicEditor, Plugin, ContextPlugin, EditorConfig } from 'https://cdn.ckeditor.com/typings/ckeditor5.d.ts';
import type { CKEditorCloudResult } from '../../src/index.js';
import type { CKEditorCloudConfig, CKEditorCloudResult } from '../../src/index.js';

type ClassicEditorCreatorConfig = {
cloud: CKEditorCloudResult;
cloud: CKEditorCloudResult<CKEditorCloudConfig>;
additionalPlugins?: Array<typeof Plugin | typeof ContextPlugin>;
overrideConfig?: EditorConfig;
};
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
},
"dependencies": {
"prop-types": "^15.7.2",
"@ckeditor/ckeditor5-integrations-common": "^2.1.0"
"@ckeditor/ckeditor5-integrations-common": "^2.2.0"
},
"peerDependencies": {
"ckeditor5": ">=42.0.0 || ^0.0.0-nightly",
Expand Down
7 changes: 6 additions & 1 deletion src/ckeditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ import {
isContextWatchdogReadyToUse
} from './context/ckeditorcontext.js';

import { appendAllIntegrationPluginsToConfig } from './plugins/appendAllIntegrationPluginsToConfig.js';

const REACT_INTEGRATION_READ_ONLY_LOCK_ID = 'Lock from React integration (@ckeditor/ckeditor5-react)';

// eslint-disable-next-line @typescript-eslint/ban-types
Expand Down Expand Up @@ -307,7 +309,10 @@ export default class CKEditor<TEditor extends Editor> extends React.Component<Pr
config = withCKEditorReactContextMetadata( contextItemMetadata, config );
}

return this.props.editor.create( element as HTMLElement, config )
return this.props.editor.create(
element as HTMLElement,
appendAllIntegrationPluginsToConfig( config )
)
.then( editor => {
if ( 'disabled' in this.props ) {
// Switch to the read-only mode if the `[disabled]` attribute is specified.
Expand Down
20 changes: 20 additions & 0 deletions src/plugins/ReactIntegrationUsageDataPlugin.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/**
* @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md.
*/

import React from 'react';
import { createIntegrationUsageDataPlugin } from '@ckeditor/ckeditor5-integrations-common';

/**
* This part of the code is not executed in open-source implementations using a GPL key.
* It only runs when a specific license key is provided. If you are uncertain whether
* this applies to your installation, please contact our support team.
*/
export const ReactIntegrationUsageDataPlugin = createIntegrationUsageDataPlugin(
'react',
{
version: __REACT_INTEGRATION_VERSION__,
frameworkVersion: React.version
}
);
33 changes: 33 additions & 0 deletions src/plugins/appendAllIntegrationPluginsToConfig.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/**
* @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md.
*/

import { appendExtraPluginsToEditorConfig, isCKEditorFreeLicense } from '@ckeditor/ckeditor5-integrations-common';
import type { EditorConfig } from 'ckeditor5';

import { ReactIntegrationUsageDataPlugin } from './ReactIntegrationUsageDataPlugin.js';

/**
* Appends all integration plugins to the editor configuration.
*
* @param editorConfig The editor configuration.
* @returns The editor configuration with all integration plugins appended.
*/
export function appendAllIntegrationPluginsToConfig( editorConfig: EditorConfig ): EditorConfig {
/**
* Do not modify the editor configuration if the editor is using a free license.
*/
if ( isCKEditorFreeLicense( editorConfig.licenseKey ) ) {
return editorConfig;
}

return appendExtraPluginsToEditorConfig( editorConfig, [
/**
* This part of the code is not executed in open-source implementations using a GPL key.
* It only runs when a specific license key is provided. If you are uncertain whether
* this applies to your installation, please contact our support team.
*/
ReactIntegrationUsageDataPlugin
] );
}
7 changes: 6 additions & 1 deletion src/useMultiRootEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ import { LifeCycleElementSemaphore } from './lifecycle/LifeCycleElementSemaphore
import { useRefSafeCallback } from './hooks/useRefSafeCallback.js';
import { useInstantEditorEffect } from './hooks/useInstantEditorEffect.js';

import { appendAllIntegrationPluginsToConfig } from './plugins/appendAllIntegrationPluginsToConfig.js';

const REACT_INTEGRATION_READ_ONLY_LOCK_ID = 'Lock from React integration (@ckeditor/ckeditor5-react)';

/* eslint-disable @typescript-eslint/no-use-before-define */
Expand Down Expand Up @@ -279,7 +281,10 @@ const useMultiRootEditor = ( props: MultiRootHookProps ): MultiRootHookReturns =
overwriteObject( { ...props.data }, data );
overwriteArray( Object.keys( props.data ), roots );

return props.editor.create( initialData, config )
return props.editor.create(
initialData,
appendAllIntegrationPluginsToConfig( config )
)
.then( ( editor: MultiRootEditor ) => {
const editorData = editor.getFullData();

Expand Down
149 changes: 149 additions & 0 deletions tests/ckeditor.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { createDefer } from './_utils/defer.js';
import { PromiseManager } from './_utils/promisemanager.js';
import CKEditor, { type Props } from '../src/ckeditor.js';
import { expectToBeTruthy } from './_utils/expectToBeTruthy.js';
import { ReactIntegrationUsageDataPlugin } from '../src/plugins/ReactIntegrationUsageDataPlugin.js';

import type { LifeCycleElementSemaphore } from '../src/lifecycle/LifeCycleElementSemaphore.js';
import type { EditorSemaphoreMountResult } from '../src/lifecycle/LifeCycleEditorSemaphore.js';
Expand Down Expand Up @@ -160,6 +161,154 @@ describe( '<CKEditor> Component', () => {
} );
} );

describe( 'license v2', () => {
it( 'should not add usage data plugin on free', async () => {
window.CKEDITOR_VERSION = '41.0.0';

const createSpy = vi.spyOn( MockEditor, 'create' ).mockResolvedValue( new MockEditor() );

const editorConfig = {
plugins: [],
toolbar: {
items: [ 'bold' ]
}
};

component = render(
<CKEditor
editor={MockEditor}
config={editorConfig}
onReady={manager.resolveOnRun()}
/>
);

await manager.all();

expect( createSpy ).toHaveBeenCalledOnce();
expect( createSpy.mock.calls[ 0 ][ 1 ] ).to.deep.equal( {
toolbar: {
items: [ 'bold' ]
},
initialData: '',
plugins: [],
context: undefined
} );
} );

it( 'should add usage data plugin on commercial', async () => {
window.CKEDITOR_VERSION = '41.0.0';

const createSpy = vi.spyOn( MockEditor, 'create' ).mockResolvedValue( new MockEditor() );

const editorConfig = {
plugins: [],
licenseKey: '<YOUR_LICENSE_KEY>',
toolbar: {
items: [ 'bold' ]
}
};

component = render(
<CKEditor
editor={MockEditor}
config={editorConfig}
onReady={manager.resolveOnRun()}
/>
);

await manager.all();

expect( createSpy ).toHaveBeenCalledOnce();
expect( createSpy.mock.calls[ 0 ][ 1 ] ).to.deep.equal( {
extraPlugins: [
ReactIntegrationUsageDataPlugin
],
toolbar: {
items: [ 'bold' ]
},
initialData: '',
licenseKey: '<YOUR_LICENSE_KEY>',
plugins: [],
context: undefined
} );
} );
} );

describe( 'license v3', () => {
it( 'should not add usage data plugin on free', async () => {
window.CKEDITOR_VERSION = '44.0.0';

const createSpy = vi.spyOn( MockEditor, 'create' ).mockResolvedValue( new MockEditor() );

const editorConfig = {
plugins: [],
licenseKey: 'GPL',
toolbar: {
items: [ 'bold' ]
}
};

component = render(
<CKEditor
editor={MockEditor}
config={editorConfig}
onReady={manager.resolveOnRun()}
/>
);

await manager.all();

expect( createSpy ).toHaveBeenCalledOnce();
expect( createSpy.mock.calls[ 0 ][ 1 ] ).to.deep.equal( {
toolbar: {
items: [ 'bold' ]
},
initialData: '',
licenseKey: 'GPL',
plugins: [],
context: undefined
} );
} );

it( 'should add usage data plugin on commercial', async () => {
window.CKEDITOR_VERSION = '44.0.0';

const createSpy = vi.spyOn( MockEditor, 'create' ).mockResolvedValue( new MockEditor() );

const editorConfig = {
plugins: [],
licenseKey: '<YOUR_LICENSE_KEY>',
toolbar: {
items: [ 'bold' ]
}
};

component = render(
<CKEditor
editor={MockEditor}
config={editorConfig}
onReady={manager.resolveOnRun()}
/>
);

await manager.all();

expect( createSpy ).toHaveBeenCalledOnce();
expect( createSpy.mock.calls[ 0 ][ 1 ] ).to.deep.equal( {
extraPlugins: [
ReactIntegrationUsageDataPlugin
],
toolbar: {
items: [ 'bold' ]
},
initialData: '',
licenseKey: '<YOUR_LICENSE_KEY>',
plugins: [],
context: undefined
} );
} );
} );

it( 'sets initial data if was specified (using the "data" property)', async () => {
const createSpy = vi.spyOn( MockEditor, 'create' ).mockResolvedValue( new MockEditor() );

Expand Down
2 changes: 0 additions & 2 deletions tests/cloud/useCKEditorCloud.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,6 @@ describe( 'useCKEditorCloud', { timeout: 8000 }, () => {
translations: [ 'es', 'de' ]
} ) );

expect( result.current.status ).toBe( 'loading' );

await waitFor( () => {
expect( result.current.status ).toBe( 'success' );

Expand Down
2 changes: 2 additions & 0 deletions tests/useMultiRootEditor.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { timeout } from './_utils/timeout.js';
import { createDefer } from './_utils/defer.js';
import { createTestMultiRootWatchdog, TestMultiRootEditor } from './_utils/multirooteditor.js';
import { turnOffErrors } from './_utils/turnOffErrors.js';
import { ReactIntegrationUsageDataPlugin } from '../src/plugins/ReactIntegrationUsageDataPlugin.js';

describe( 'useMultiRootEditor', () => {
const rootsContent = {
Expand Down Expand Up @@ -106,6 +107,7 @@ describe( 'useMultiRootEditor', () => {

await waitFor( () => {
expect( result.current.editor ).to.be.instanceof( TestMultiRootEditor );
expect( result.current.editor?.plugins ).to.include( ReactIntegrationUsageDataPlugin );
} );
} );

Expand Down
2 changes: 2 additions & 0 deletions vite-env.d.ts
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
declare const __REACT_VERSION__: number;

declare const __REACT_INTEGRATION_VERSION__: string;
3 changes: 2 additions & 1 deletion vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ export default defineConfig( {
},

define: {
__REACT_VERSION__: REACT_VERSION
__REACT_VERSION__: REACT_VERSION,
__REACT_INTEGRATION_VERSION__: JSON.stringify( pkg.version )
}
} );
8 changes: 4 additions & 4 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1225,10 +1225,10 @@
"@ckeditor/ckeditor5-utils" "43.2.0"
ckeditor5 "43.2.0"

"@ckeditor/ckeditor5-integrations-common@^2.1.0":
version "2.1.0"
resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-integrations-common/-/ckeditor5-integrations-common-2.1.0.tgz#5e1423ff764c421d8181a35f73677610038f1fb8"
integrity sha512-vn6qMb36sl6eSCc27dvThk6xISif59MxnxZmRBC440TyP7S9ZcS0ai4yHd5QyaH70ZIe0lhS7DWdLaiKtBggVQ==
"@ckeditor/ckeditor5-integrations-common@^2.2.0":
version "2.2.0"
resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-integrations-common/-/ckeditor5-integrations-common-2.2.0.tgz#3eb75e21eddc880c87a675125ec3fcfe0c258847"
integrity sha512-qH68tqgyMibuejo+VAJ+iSH3ZmZweqBEzaawv9hZb4zzSMkBityWBjSc2hKXMtmJgCNsbSK84cyHpa5J/MNyLg==

"@ckeditor/[email protected]":
version "43.2.0"
Expand Down

0 comments on commit 506c297

Please sign in to comment.