UNPKG

@ckeditor/ckeditor5-integrations-common

Version:

This package implements common utility modules for integration projects.

1 lines 89.9 kB
{"version":3,"file":"index.umd.cjs","sources":["../src/utils/defer.ts","../src/utils/waitFor.ts","../src/utils/injectScript.ts","../src/utils/injectStylesheet.ts","../src/utils/isSSR.ts","../src/utils/once.ts","../src/utils/overwriteArray.ts","../src/utils/overwriteObject.ts","../src/utils/preloadResource.ts","../src/utils/shallowCompareArrays.ts","../src/utils/uid.ts","../src/utils/uniq.ts","../src/utils/waitForWindowEntry.ts","../src/utils/filterObjectValues.ts","../src/utils/filterBlankObjectValues.ts","../src/utils/mapObjectValues.ts","../src/utils/without.ts","../src/plugins/appendExtraPluginsToEditorConfig.ts","../src/utils/version/isSemanticVersion.ts","../src/cdn/ck/isCKCdnVersion.ts","../src/utils/version/destructureSemanticVersion.ts","../src/license/getLicenseVersionFromEditorVersion.ts","../src/installation-info/getCKBaseBundleInstallationInfo.ts","../src/installation-info/getSupportedLicenseVersionInstallationInfo.ts","../src/license/isCKEditorFreeLicense.ts","../src/plugins/IntegrationUsageDataPlugin.ts","../src/cdn/ck/createCKCdnUrl.ts","../src/cdn/ckbox/createCKBoxCdnUrl.ts","../src/docs/createCKDocsUrl.ts","../src/cdn/ck/createCKCdnBaseBundlePack.ts","../src/cdn/ck/createCKCdnPremiumBundlePack.ts","../src/cdn/utils/loadCKCdnResourcesPack.ts","../src/cdn/utils/combineCKCdnBundlesPacks.ts","../src/installation-info/getCKBoxInstallationInfo.ts","../src/cdn/ckbox/createCKBoxCdnBundlePack.ts","../src/license/isCKCdnSupportedByEditorVersion.ts","../src/cdn/plugins/combineCdnPluginsPacks.ts","../src/cdn/loadCKEditorCloud.ts"],"sourcesContent":["/**\n * @license Copyright (c) 2003-2025, CKSource Holding sp. z o.o. All rights reserved.\n * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options\n */\n\nexport type Defer<E> = {\n\tpromise: Promise<E>;\n\tresolve: ( value: E ) => void;\n};\n\n/**\n * This function generates a promise that can be resolved by invoking the returned `resolve` method.\n * It proves to be beneficial in the creation of various types of locks and semaphores.\n *\n * It can be replaced with `Promise.withResolvers()` in the future.\n */\nexport function createDefer<E = void>(): Defer<E> {\n\tconst deferred: Defer<E> = {\n\t\tresolve: null as any,\n\t\tpromise: null as any\n\t};\n\n\tdeferred.promise = new Promise<E>( resolve => {\n\t\tdeferred.resolve = resolve;\n\t} );\n\n\treturn deferred;\n}\n","/**\n * @license Copyright (c) 2003-2025, CKSource Holding sp. z o.o. All rights reserved.\n * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options\n */\n\nimport type { Awaitable } from '../types/Awaitable.js';\n\n/**\n * Waits for the provided callback to succeed. The callback is executed multiple times until it succeeds or the timeout is reached.\n * It's executed immediately and then with a delay defined by the `retry` option.\n *\n * @param callback The callback to execute.\n * @param config Configuration for the function.\n * @returns A promise that resolves when the callback succeeds.\n *\n * @example\n * ```ts\n * await waitFor( async () => {\n * \tconst element = document.querySelector( '.my-element' );\n * \tif ( !element ) {\n * \t\tthrow new Error( 'Element not found.' );\n * \t}\n * } );\n * ```\n */\nexport function waitFor<R>(\n\tcallback: () => Awaitable<R>,\n\t{\n\t\ttimeOutAfter = 500,\n\t\tretryAfter = 100\n\t}: WaitForConfig = {}\n): Promise<R> {\n\t// Retry the callback until it succeeds or the timeout is reached.\n\treturn new Promise<R>( ( resolve, reject ) => {\n\t\tconst startTime = Date.now();\n\t\tlet lastError: Error | null = null;\n\n\t\tconst timeoutTimerId = setTimeout( () => {\n\t\t\treject( lastError ?? new Error( 'Timeout' ) );\n\t\t}, timeOutAfter );\n\n\t\tconst tick = async () => {\n\t\t\ttry {\n\t\t\t\tconst result = await callback();\n\t\t\t\tclearTimeout( timeoutTimerId );\n\t\t\t\tresolve( result );\n\t\t\t} catch ( err: any ) {\n\t\t\t\tlastError = err;\n\n\t\t\t\tif ( Date.now() - startTime > timeOutAfter ) {\n\t\t\t\t\treject( err );\n\t\t\t\t} else {\n\t\t\t\t\tsetTimeout( tick, retryAfter );\n\t\t\t\t}\n\t\t\t}\n\t\t};\n\n\t\ttick();\n\t} );\n}\n\n/**\n * Configuration for the `waitFor` function.\n */\nexport type WaitForConfig = {\n\t// The time in milliseconds after which the function will stop retrying and reject the promise.\n\ttimeOutAfter?: number;\n\n\t// The time in milliseconds between retries.\n\tretryAfter?: number;\n};\n","/**\n * @license Copyright (c) 2003-2025, CKSource Holding sp. z o.o. All rights reserved.\n * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options\n */\n\n/**\n * Map of injected scripts. It is used to prevent injecting the same script multiple times.\n * It happens quite often in React Strict mode when the component is rendered twice.\n */\nexport const INJECTED_SCRIPTS = new Map<string, Promise<void>>();\n\n/**\n * Injects a script into the document.\n *\n * @param src The URL of the script to be injected.\n * @param props Additional properties used to decide how the script should be injected.\n * @param props.attributes Additional attributes to be set on the script element.\n * @returns A promise that resolves when the script is loaded.\n */\nexport function injectScript(\n\tsrc: string,\n\t{ attributes }: InjectScriptProps = {}\n): Promise<void> {\n\t// Return the promise if the script is already injected by this function.\n\tif ( INJECTED_SCRIPTS.has( src ) ) {\n\t\treturn INJECTED_SCRIPTS.get( src )!;\n\t}\n\n\t// Return the promise if the script is already present in the document but not injected by this function.\n\t// We are not sure if the script is loaded or not, so we have to show warning in this case.\n\tconst maybePrevScript = document.querySelector( `script[src=\"${ src }\"]` );\n\n\tif ( maybePrevScript ) {\n\t\tconsole.warn( `Script with \"${ src }\" src is already present in DOM!` );\n\t\tmaybePrevScript.remove();\n\t}\n\n\t// Inject the script and return the promise.\n\tconst promise = new Promise<void>( ( resolve, reject ) => {\n\t\tconst script = document.createElement( 'script' );\n\n\t\tscript.onerror = reject;\n\t\tscript.onload = () => {\n\t\t\tresolve();\n\t\t};\n\n\t\t// Set additional attributes if provided.\n\t\tfor ( const [ key, value ] of Object.entries( attributes || {} ) ) {\n\t\t\tscript.setAttribute( key, value );\n\t\t}\n\n\t\tscript.setAttribute( 'data-injected-by', 'ckeditor-integration' );\n\n\t\tscript.type = 'text/javascript';\n\t\tscript.async = true;\n\t\tscript.src = src;\n\n\t\tdocument.head.appendChild( script );\n\n\t\t// It should remove script if script is being removed from the DOM.\n\t\tconst observer = new MutationObserver( mutations => {\n\t\t\tconst removedNodes = mutations.flatMap( mutation => Array.from( mutation.removedNodes ) );\n\n\t\t\tif ( removedNodes.includes( script ) ) {\n\t\t\t\tINJECTED_SCRIPTS.delete( src );\n\t\t\t\tobserver.disconnect();\n\t\t\t}\n\t\t} );\n\n\t\tobserver.observe( document.head, {\n\t\t\tchildList: true,\n\t\t\tsubtree: true\n\t\t} );\n\t} );\n\n\tINJECTED_SCRIPTS.set( src, promise );\n\n\treturn promise;\n}\n\n/**\n * Props for the `injectScript` function.\n */\nexport type InjectScriptProps = {\n\tattributes?: Record<string, any>;\n};\n\n/**\n * Injects multiple scripts into the document in parallel.\n *\n * @param sources The URLs of the scripts to be injected.\n * @param props Additional properties used to decide how the script should be injected.\n * @returns A promise that resolves when all scripts are loaded.\n */\nexport async function injectScriptsInParallel( sources: Array<string>, props?: InjectScriptProps ): Promise<void> {\n\tawait Promise.all(\n\t\tsources.map( src => injectScript( src, props ) )\n\t);\n}\n","/**\n * @license Copyright (c) 2003-2025, CKSource Holding sp. z o.o. All rights reserved.\n * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options\n */\n\n/**\n * Map of injected stylesheets. It's used to prevent injecting the same stylesheet multiple times.\n * It happens quite often in React Strict mode when the component is rendered twice.\n */\nexport const INJECTED_STYLESHEETS = new Map<string, Promise<void>>();\n\n/**\n * Injects a stylesheet into the document.\n *\n * @param props.href The URL of the stylesheet to be injected.\n * @param props.placementInHead The placement of the stylesheet in the head.\n * @param props.attributes Additional attributes to be set on the link element.\n * @returns A promise that resolves when the stylesheet is loaded.\n */\nexport function injectStylesheet(\n\t{\n\t\thref,\n\t\tplacementInHead = 'start',\n\t\tattributes = {}\n\t}: InjectStylesheetProps\n): Promise<void> {\n\t// Return the promise if the stylesheet is already injected by this function.\n\tif ( INJECTED_STYLESHEETS.has( href ) ) {\n\t\treturn INJECTED_STYLESHEETS.get( href )!;\n\t}\n\n\t// Return the promise if the stylesheet is already present in the document but not injected by this function.\n\t// We are not sure if the stylesheet is loaded or not, so we have to show a warning in this case.\n\tconst maybePrevStylesheet = document.querySelector( `link[href=\"${ href }\"][rel=\"stylesheet\"]` );\n\n\tif ( maybePrevStylesheet ) {\n\t\tconsole.warn( `Stylesheet with \"${ href }\" href is already present in DOM!` );\n\t\tmaybePrevStylesheet.remove();\n\t}\n\n\t// Append the link tag to the head.\n\tconst appendLinkTagToHead = ( link: HTMLLinkElement ) => {\n\t\t// Inject styles after the stylesheets that are already present in the head.\n\t\t// Do not specify the `rel` attribute because we want to inject the stylesheet even after\n\t\t// preloading link tags.\n\t\tconst previouslyInjectedLinks = Array.from(\n\t\t\tdocument.head.querySelectorAll( 'link[data-injected-by=\"ckeditor-integration\"]' )\n\t\t);\n\n\t\tswitch ( placementInHead ) {\n\t\t\t// It'll append styles *before* the stylesheets that are already present in the head\n\t\t\t// but after the ones that are injected by this function.\n\t\t\tcase 'start':\n\t\t\t\tif ( previouslyInjectedLinks.length ) {\n\t\t\t\t\tpreviouslyInjectedLinks.slice( -1 )[ 0 ].after( link );\n\t\t\t\t} else {\n\t\t\t\t\tdocument.head.insertBefore( link, document.head.firstChild );\n\t\t\t\t}\n\t\t\t\tbreak;\n\n\t\t\t// It'll append styles *after* the stylesheets already in the head.\n\t\t\tcase 'end':\n\t\t\t\tdocument.head.appendChild( link );\n\t\t\t\tbreak;\n\t\t}\n\t};\n\n\t// Inject the stylesheet and return the promise.\n\tconst promise = new Promise<void>( ( resolve, reject ) => {\n\t\tconst link = document.createElement( 'link' );\n\n\t\t// Set additional attributes if provided.\n\t\tfor ( const [ key, value ] of Object.entries( attributes || {} ) ) {\n\t\t\tlink.setAttribute( key, value );\n\t\t}\n\n\t\tlink.setAttribute( 'data-injected-by', 'ckeditor-integration' );\n\n\t\tlink.rel = 'stylesheet';\n\t\tlink.href = href;\n\n\t\tlink.onerror = reject;\n\t\tlink.onload = () => {\n\t\t\tresolve();\n\t\t};\n\n\t\tappendLinkTagToHead( link );\n\n\t\t// It should remove stylesheet if stylesheet is being removed from the DOM.\n\t\tconst observer = new MutationObserver( mutations => {\n\t\t\tconst removedNodes = mutations.flatMap( mutation => Array.from( mutation.removedNodes ) );\n\n\t\t\tif ( removedNodes.includes( link ) ) {\n\t\t\t\tINJECTED_STYLESHEETS.delete( href );\n\t\t\t\tobserver.disconnect();\n\t\t\t}\n\t\t} );\n\n\t\tobserver.observe( document.head, {\n\t\t\tchildList: true,\n\t\t\tsubtree: true\n\t\t} );\n\t} );\n\n\tINJECTED_STYLESHEETS.set( href, promise );\n\n\treturn promise;\n}\n\n/**\n * Props for the `injectStylesheet` function.\n */\ntype InjectStylesheetProps = {\n\n\t/**\n\t * The URL of the stylesheet to be injected.\n\t */\n\thref: string;\n\n\t/**\n\t * The placement of the stylesheet in the head. It can be either at the start or at the end\n\t * of the head. Default is 'start' because it allows user to override the styles easily.\n\t */\n\tplacementInHead?: 'start' | 'end';\n\n\t/**\n\t * Additional attributes to set on the link tag.\n\t */\n\tattributes?: Record<string, any>;\n};\n","/**\n * @license Copyright (c) 2003-2025, CKSource Holding sp. z o.o. All rights reserved.\n * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options\n */\n\nexport function isSSR(): boolean {\n\treturn typeof window === 'undefined';\n}\n","/**\n * @license Copyright (c) 2003-2025, CKSource Holding sp. z o.o. All rights reserved.\n * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options\n */\n\n/**\n * Ensures that passed function will be executed only once.\n */\nexport function once<A extends Array<any>, R = void>( fn: ( ...args: A ) => R ): ( ...args: A ) => R {\n\tlet lastResult: { current: R } | null = null;\n\n\treturn ( ...args: A ): R => {\n\t\tif ( !lastResult ) {\n\t\t\tlastResult = {\n\t\t\t\tcurrent: fn( ...args )\n\t\t\t};\n\t\t}\n\n\t\treturn lastResult.current;\n\t};\n}\n","/**\n * @license Copyright (c) 2003-2025, CKSource Holding sp. z o.o. All rights reserved.\n * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options\n */\n\n/**\n * Clear whole array while keeping its reference.\n */\nexport function overwriteArray<A extends Array<any>>( source: A, destination: A ): A {\n\tdestination.length = 0;\n\tdestination.push( ...source );\n\n\treturn destination;\n}\n","/**\n * @license Copyright (c) 2003-2025, CKSource Holding sp. z o.o. All rights reserved.\n * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options\n */\n\n/**\n * Clears whole object while keeping its reference.\n */\nexport function overwriteObject<O extends Record<string, any>>( source: O, destination: O ): O {\n\tfor ( const prop of Object.getOwnPropertyNames( destination ) ) {\n\t\tdelete destination[ prop ];\n\t}\n\n\t// Prevent assigning self referencing attributes which crashes `Object.assign`.\n\tfor ( const [ key, value ] of Object.entries( source ) ) {\n\t\tif ( value !== destination && key !== 'prototype' && key !== '__proto__' ) {\n\t\t\t( destination as any )[ key ] = value;\n\t\t}\n\t}\n\n\treturn destination;\n}\n","/**\n * @license Copyright (c) 2003-2025, CKSource Holding sp. z o.o. All rights reserved.\n * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options\n */\n\n/**\n * Appends a `<link>` element to the `<head>` to preload a resource.\n *\n * \t* It should detect if the resource is already preloaded.\n * \t* It should detect type of the resource and set the `as` attribute accordingly.\n *\n * @param url The URL of the resource to preload.\n * @param props Additional properties for the preload.\n * @param props.attributes Additional attributes to be set on the `<link>` element.\n */\nexport function preloadResource(\n\turl: string,\n\t{ attributes }: PreloadResourceProps = {}\n): void {\n\tif ( document.head.querySelector( `link[href=\"${ url }\"][rel=\"preload\"]` ) ) {\n\t\treturn;\n\t}\n\n\tconst link = document.createElement( 'link' );\n\n\t// Set additional attributes if provided.\n\tfor ( const [ key, value ] of Object.entries( attributes || {} ) ) {\n\t\tlink.setAttribute( key, value );\n\t}\n\n\tlink.setAttribute( 'data-injected-by', 'ckeditor-integration' );\n\n\tlink.rel = 'preload';\n\tlink.as = detectTypeOfResource( url );\n\tlink.href = url;\n\n\tdocument.head.insertBefore( link, document.head.firstChild );\n}\n\n/**\n * Properties for the `preloadResource` function.\n */\ntype PreloadResourceProps = {\n\tattributes?: Record<string, any>;\n};\n\n/**\n * Detects the type of the resource based on its URL.\n */\nfunction detectTypeOfResource( url: string ): string {\n\tswitch ( true ) {\n\t\tcase /\\.css$/.test( url ):\n\t\t\treturn 'style';\n\n\t\tcase /\\.js$/.test( url ):\n\t\t\treturn 'script';\n\n\t\tdefault:\n\t\t\treturn 'fetch';\n\t}\n}\n","/**\n * @license Copyright (c) 2003-2025, CKSource Holding sp. z o.o. All rights reserved.\n * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options\n */\n\n/**\n * Shallow comparison of two arrays.\n */\nexport function shallowCompareArrays<T>(\n\ta: Readonly<Array<T> | null>,\n\tb: Readonly<Array<T> | null>\n): boolean {\n\tif ( a === b ) {\n\t\treturn true;\n\t}\n\n\tif ( !a || !b ) {\n\t\treturn false;\n\t}\n\n\tfor ( let i = 0; i < a.length; ++i ) {\n\t\tif ( a[ i ] !== b[ i ] ) {\n\t\t\treturn false;\n\t\t}\n\t}\n\n\treturn true;\n}\n","/**\n * @license Copyright (c) 2003-2025, CKSource Holding sp. z o.o. All rights reserved.\n * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options\n */\n\n/**\n * A hash table of hex numbers to avoid using toString() in uid() which is costly.\n * [ '00', '01', '02', ..., 'fe', 'ff' ]\n */\nconst HEX_NUMBERS = new Array( 256 ).fill( '' )\n\t.map( ( _, index ) => ( '0' + ( index ).toString( 16 ) ).slice( -2 ) );\n\n/**\n * Returns a unique id. The id starts with an \"e\" character and a randomly generated string of\n * 32 alphanumeric characters.\n *\n * **Note**: The characters the unique id is built from correspond to the hex number notation\n * (from \"0\" to \"9\", from \"a\" to \"f\"). In other words, each id corresponds to an \"e\" followed\n * by 16 8-bit numbers next to each other.\n *\n * @returns An unique id string.\n */\nexport function uid(): string {\n\t// Generate 4 random 32-bit numbers.\n\tconst [ r1, r2, r3, r4 ] = crypto.getRandomValues( new Uint32Array( 4 ) );\n\n\t// Make sure that id does not start with number.\n\treturn 'e' +\n\t\tHEX_NUMBERS[ r1 >> 0 & 0xFF ] +\n\t\tHEX_NUMBERS[ r1 >> 8 & 0xFF ] +\n\t\tHEX_NUMBERS[ r1 >> 16 & 0xFF ] +\n\t\tHEX_NUMBERS[ r1 >> 24 & 0xFF ] +\n\t\tHEX_NUMBERS[ r2 >> 0 & 0xFF ] +\n\t\tHEX_NUMBERS[ r2 >> 8 & 0xFF ] +\n\t\tHEX_NUMBERS[ r2 >> 16 & 0xFF ] +\n\t\tHEX_NUMBERS[ r2 >> 24 & 0xFF ] +\n\t\tHEX_NUMBERS[ r3 >> 0 & 0xFF ] +\n\t\tHEX_NUMBERS[ r3 >> 8 & 0xFF ] +\n\t\tHEX_NUMBERS[ r3 >> 16 & 0xFF ] +\n\t\tHEX_NUMBERS[ r3 >> 24 & 0xFF ] +\n\t\tHEX_NUMBERS[ r4 >> 0 & 0xFF ] +\n\t\tHEX_NUMBERS[ r4 >> 8 & 0xFF ] +\n\t\tHEX_NUMBERS[ r4 >> 16 & 0xFF ] +\n\t\tHEX_NUMBERS[ r4 >> 24 & 0xFF ];\n}\n","/**\n * @license Copyright (c) 2003-2025, CKSource Holding sp. z o.o. All rights reserved.\n * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options\n */\n\n/**\n * A utility function that removes duplicate elements from an array.\n */\nexport function uniq<A>( source: Array<A> ): Array<A> {\n\treturn Array.from( new Set( source ) );\n}\n","/**\n * @license Copyright (c) 2003-2025, CKSource Holding sp. z o.o. All rights reserved.\n * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options\n */\n\nimport { waitFor, type WaitForConfig } from './waitFor.js';\n\n/**\n * Waits for the provided window entry to be available. It's used mostly for waiting for the CKEditor 5 window object to be available.\n * In theory these entries should be available immediately, but in practice, they might be loaded asynchronously because browser might\n * delay execution of the script even if it's loaded synchronously.\n *\n * Function ensures that proper type declarations are present on global Window interface.\n *\n * @param entryNames The names of the window entries to wait for.\n * @param config Configuration for the function.\n * @returns A promise that resolves when the window entry is available.\n *\n * @example\n * ```ts\n * const ckeditor = await waitForWindowEntry( [ 'CKEditor' ] );\n * ```\n */\nexport async function waitForWindowEntry<\n\tN extends keyof Window,\n\tO = Window[ N ]\n>( entryNames: Array<N>, config?: WaitForConfig ): Promise<O> {\n\t// Try to pick the bundle from the window object.\n\tconst tryPickBundle = () => (\n\t\tentryNames\n\t\t\t.map( name => ( window as any )[ name ] )\n\t\t\t.filter( Boolean )[ 0 ]\n\t);\n\n\treturn waitFor<O>(\n\t\t() => {\n\t\t\tconst result = tryPickBundle();\n\n\t\t\tif ( !result ) {\n\t\t\t\tthrow new Error( `Window entry \"${ entryNames.join( ',' ) }\" not found.` );\n\t\t\t}\n\n\t\t\treturn result;\n\t\t},\n\t\tconfig\n\t);\n}\n","/**\n * @license Copyright (c) 2003-2025, CKSource Holding sp. z o.o. All rights reserved.\n * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options\n */\n\n/**\n * Filters object values using the provided filter function.\n *\n * @param obj Object to filter.\n * @param filter Function that filters the object values.\n * @returns Object with filtered values.\n *\n * @example\n * ```ts\n * const obj = {\n * \ta: 1,\n * \tb: 2\n * };\n *\n * const filteredObj = filterObjectValues( value => value > 1, obj );\n * // filteredObj is { b: 2 }\n * ```\n */\nexport function filterObjectValues<T>(\n\tobj: Record<string, T>,\n\tfilter: ( value: T, key: string ) => boolean\n): Record<string, T> {\n\tconst filteredEntries = Object\n\t\t.entries( obj )\n\t\t.filter( ( [ key, value ] ) => filter( value, key ) );\n\n\treturn Object.fromEntries( filteredEntries );\n}\n","/**\n * @license Copyright (c) 2003-2025, CKSource Holding sp. z o.o. All rights reserved.\n * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options\n */\n\nimport { filterObjectValues } from './filterObjectValues.js';\n\n/**\n * Removes null and undefined values from an object.\n *\n * @param obj Object to filter.\n * @returns Object with filtered values.\n * @example\n * ```ts\n * const obj = {\n * \ta: 1,\n * \tb: null,\n * \tc: undefined\n * };\n *\n * const filteredObj = filterBlankObjectValues( obj );\n * // filteredObj is { a: 1 }\n * ```\n */\nexport function filterBlankObjectValues<T>( obj: Record<string, T> ): FilterBlankRecordProperties<T> {\n\treturn filterObjectValues(\n\t\tobj,\n\t\tvalue => value !== null && value !== undefined\n\t) as FilterBlankRecordProperties<T>;\n}\n\n/**\n * Removes null and undefined values from an object.\n */\ntype FilterBlankRecordProperties<T> = Record<string, Exclude<T, null | undefined>>;\n","/**\n * @license Copyright (c) 2003-2025, CKSource Holding sp. z o.o. All rights reserved.\n * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options\n */\n\n/**\n * Maps object values using the provided mapper function.\n *\n * @param obj Object to map.\n * @param mapper Function that maps the object values.\n * @returns Object with mapped values.\n *\n * @example\n * ```ts\n * const obj = {\n * \ta: 1,\n * \tb: 2\n * };\n *\n * const mappedObj = mapObjectValues( obj, value => value * 2 );\n * // mappedObj is { a: 2, b: 4 }\n * ```\n */\nexport function mapObjectValues<T, U>(\n\tobj: Record<string, T>,\n\tmapper: ( value: T, key: string ) => U\n): Record<string, U> {\n\tconst mappedEntries = Object\n\t\t.entries( obj )\n\t\t.map( ( [ key, value ] ) => [ key, mapper( value, key ) ] as const );\n\n\treturn Object.fromEntries( mappedEntries );\n}\n","/**\n * @license Copyright (c) 2003-2025, CKSource Holding sp. z o.o. All rights reserved.\n * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options\n */\n\n/**\n * Removes items from an array by values.\n *\n * @param itemsToRemove Items to remove.\n * @param items Array to remove items from.\n * @returns Array without removed items.\n */\nexport function without<A>( itemsToRemove: Array<A>, items: Array<A> ): Array<A> {\n\treturn items.filter( item => !itemsToRemove.includes( item ) );\n}\n","/**\n * @license Copyright (c) 2003-2025, CKSource Holding sp. z o.o. All rights reserved.\n * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options\n */\n\nimport type { EditorConfig, PluginConstructor } from 'ckeditor5';\n\n/**\n * Appends plugins to the editor configuration.\n * It uses the `extraPlugins` property to append the plugins to avoid dealing with built-in constructor plugins.\n *\n * @param config The editor configuration.\n * @param plugins The plugins to append.\n * @returns The editor configuration with the plugins appended.\n * @example\n * ```ts\n * const editorConfig = appendExtraPluginsToEditorConfig(\n * \t{\n * \t\textraPlugins: [ 'Plugin1' ]\n * \t},\n * \t[ 'Plugin2' ]\n * );\n *\n * console.log( editorConfig.extraPlugins ); // [ 'Plugin1', 'Plugin2' ]\n * ```\n */\nexport function appendExtraPluginsToEditorConfig(\n\tconfig: EditorConfig,\n\tplugins: Array<PluginConstructor>\n): EditorConfig {\n\tconst extraPlugins = config.extraPlugins || [];\n\n\t// Do not use `uniq`. There might be integrations with duplicated plugins, so to\n\t// make it backward compatible, we need to keep the order of the plugins.\n\treturn {\n\t\t...config,\n\t\textraPlugins: [\n\t\t\t...extraPlugins,\n\t\t\t...plugins.filter( item => !extraPlugins.includes( item ) )\n\t\t]\n\t};\n}\n","/**\n * @license Copyright (c) 2003-2025, CKSource Holding sp. z o.o. All rights reserved.\n * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options\n */\n\nexport type SemanticVersion = `${ number }.${ number }.${ number }`;\n\n/**\n * Checks if the given string is a semantic version number.\n *\n * @param version - The string to check.\n * @returns `true` if the string is a semantic version, `false` otherwise.\n */\nexport function isSemanticVersion( version: string | undefined | null ): version is SemanticVersion {\n\treturn !!version && /^\\d+\\.\\d+\\.\\d+/.test( version );\n}\n","/**\n * @license Copyright (c) 2003-2025, CKSource Holding sp. z o.o. All rights reserved.\n * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options\n */\n\nimport { isSemanticVersion, type SemanticVersion } from '../../utils/version/isSemanticVersion.js';\n\n/**\n * A version of the CKEditor that is used for testing purposes.\n */\nexport type CKCdnTestingVersion =\n\t| 'nightly'\n\t| `nightly-${ string }`\n\t| 'alpha'\n\t| 'staging'\n\t| 'internal';\n\n/**\n * A version of a file on the CKEditor CDN.\n */\nexport type CKCdnVersion =\n\t| SemanticVersion\n\t| CKCdnTestingVersion;\n\n/**\n * Checks if the given string is a version of a file on the CKEditor CDN.\n *\n * @param version - The string to check.\n * @returns `true` if the string is a version of a file on the CKEditor CDN, `false` otherwise.\n * @example\n * ```ts\n * isCKCdnTestingVersion( '1.2.3-nightly-abc' ); // -> true\n * isCKCdnTestingVersion( '1.2.3-internal-abc' ); // -> true\n * isCKCdnTestingVersion( '1.2.3-alpha.1' ); // -> true\n * isCKCdnTestingVersion( '1.2.3' ); // -> false\n * isCKCdnTestingVersion( 'nightly' ); // -> true\n * isCKCdnTestingVersion( 'nightly-abc' ); // -> true\n * isCKCdnTestingVersion( 'staging' ); // -> true\n * ```\n */\nexport function isCKCdnTestingVersion( version: string | undefined ): version is CKCdnTestingVersion {\n\tif ( !version ) {\n\t\treturn false;\n\t}\n\n\treturn [ 'nightly', 'alpha', 'internal', 'nightly-', 'staging' ].some( testVersion => version.includes( testVersion ) );\n}\n\n/**\n * Checks if the given string is a version of a file on the CKEditor CDN.\n *\n * @param version - The string to check.\n * @returns `true` if the string is a version of a file on the CKEditor CDN, `false` otherwise.\n * @example\n * ```ts\n * isCKCdnVersion( 'nightly' ); // -> true\n * isCKCdnVersion( 'alpha' ); // -> true\n * isCKCdnVersion( 'rc-1.2.3' ); // -> true\n * isCKCdnVersion( '1.2.3' ); // -> true\n * isCKCdnVersion( 'nightly-abc' ); // -> true\n * isCKCdnVersion( 'staging' ); // -> true\n * ```\n */\nexport function isCKCdnVersion( version: string | undefined ): version is CKCdnVersion {\n\treturn isSemanticVersion( version ) || isCKCdnTestingVersion( version );\n}\n","/**\n * @license Copyright (c) 2003-2025, CKSource Holding sp. z o.o. All rights reserved.\n * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options\n */\n\nimport { isSemanticVersion, type SemanticVersion } from './isSemanticVersion.js';\n\ntype DestructuredSemanticVersion = {\n\tmajor: number;\n\tminor: number;\n\tpatch: number;\n};\n\n/**\n * Destructure a semantic version string into its major, minor, and patch components.\n *\n * @param version - The semantic version string to destructure.\n * @returns An object containing the major, minor, and patch numbers.\n * @throws Will throw an error if the provided version is not a valid semantic version.\n */\nexport function destructureSemanticVersion( version: SemanticVersion ): DestructuredSemanticVersion {\n\tif ( !isSemanticVersion( version ) ) {\n\t\tthrow new Error( `Invalid semantic version: ${ version || '<blank>' }.` );\n\t}\n\n\tconst [ major, minor, patch ] = version.split( '.' );\n\n\treturn {\n\t\tmajor: Number.parseInt( major, 10 ),\n\t\tminor: Number.parseInt( minor, 10 ),\n\t\tpatch: Number.parseInt( patch, 10 )\n\t};\n}\n","/**\n * @license Copyright (c) 2003-2025, CKSource Holding sp. z o.o. All rights reserved.\n * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options\n */\n\nimport { type CKCdnVersion, isCKCdnTestingVersion } from '../cdn/ck/isCKCdnVersion.js';\nimport { destructureSemanticVersion } from '../utils/version/destructureSemanticVersion.js';\nimport type { LicenseKeyVersion } from './LicenseKey.js';\n\n/**\n * Returns the license version that is supported by the given CKEditor version.\n *\n * @param version The CKEditor version (semantic version or testing version).\n * @returns The supported license version.\n */\nexport function getLicenseVersionFromEditorVersion( version: CKCdnVersion ): LicenseKeyVersion {\n\t// Assume that the testing version is always the newest one\n\t// so we can return the highest supported license version.\n\tif ( isCKCdnTestingVersion( version ) ) {\n\t\treturn 3;\n\t}\n\n\tconst { major } = destructureSemanticVersion( version );\n\n\tswitch ( true ) {\n\t\tcase major >= 44:\n\t\t\treturn 3;\n\n\t\tcase major >= 38:\n\t\t\treturn 2;\n\n\t\tdefault:\n\t\t\treturn 1;\n\t}\n}\n","/**\n * @license Copyright (c) 2003-2025, CKSource Holding sp. z o.o. All rights reserved.\n * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options\n */\n\nimport type { BundleInstallationInfo } from './types.js';\n\nimport { isCKCdnVersion, type CKCdnVersion } from '../cdn/ck/isCKCdnVersion.js';\n\n/**\n * Returns information about the base CKEditor bundle installation.\n */\nexport function getCKBaseBundleInstallationInfo(): BundleInstallationInfo<CKCdnVersion> | null {\n\tconst { CKEDITOR_VERSION, CKEDITOR } = window;\n\n\tif ( !isCKCdnVersion( CKEDITOR_VERSION ) ) {\n\t\treturn null;\n\t}\n\n\t// Global `CKEDITOR` is set only in CDN builds.\n\treturn {\n\t\tsource: CKEDITOR ? 'cdn' : 'npm',\n\t\tversion: CKEDITOR_VERSION\n\t};\n}\n","/**\n * @license Copyright (c) 2003-2025, CKSource Holding sp. z o.o. All rights reserved.\n * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options\n */\n\nimport { getLicenseVersionFromEditorVersion } from '../license/getLicenseVersionFromEditorVersion.js';\nimport type { LicenseKeyVersion } from '../license/LicenseKey.js';\n\nimport { getCKBaseBundleInstallationInfo } from './getCKBaseBundleInstallationInfo.js';\n\n/**\n * Returns information about the installed CKEditor version and the supported license version.\n *\n * @returns The supported license version or `null` if the CKEditor version is unknown.\n */\nexport function getSupportedLicenseVersionInstallationInfo(): LicenseKeyVersion | null {\n\tconst installationInfo = getCKBaseBundleInstallationInfo();\n\n\t// It looks like unknown version of Ckeditor is installed, so we can't determine the license version.\n\tif ( !installationInfo ) {\n\t\treturn null;\n\t}\n\n\treturn getLicenseVersionFromEditorVersion( installationInfo.version );\n}\n","/**\n * @license Copyright (c) 2003-2025, CKSource Holding sp. z o.o. All rights reserved.\n * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options\n */\n\nimport { getSupportedLicenseVersionInstallationInfo } from '../installation-info/getSupportedLicenseVersionInstallationInfo.js';\nimport { expectType } from '../types/expectType.js';\n\nimport type { LicenseKey, LicenseKeyVersion } from './LicenseKey.js';\n\n/**\n * Checks if passed license key is a free CKEditor license key.\n *\n * @param licenseKey The license key to check.\n * @param licenseVersion The version of the license key.\n * @returns `true` if the license key is free, `false` otherwise.\n */\nexport function isCKEditorFreeLicense( licenseKey: LicenseKey, licenseVersion?: LicenseKeyVersion ): boolean {\n\t// Pick the license version from the installation info if it's not provided.\n\t// Version should be present somewhere in the window object.\n\tlicenseVersion ||= getSupportedLicenseVersionInstallationInfo() || undefined;\n\n\tswitch ( licenseVersion ) {\n\t\tcase 1:\n\t\tcase 2:\n\t\t\treturn licenseKey === undefined;\n\n\t\tcase 3:\n\t\t\treturn licenseKey === 'GPL';\n\n\t\tdefault: {\n\t\t\texpectType<undefined>( licenseVersion );\n\n\t\t\treturn false;\n\t\t}\n\t}\n}\n","/**\n * @license Copyright (c) 2003-2025, CKSource Holding sp. z o.o. All rights reserved.\n * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options\n */\n\nimport type { Editor, PluginConstructor } from 'ckeditor5';\nimport { isCKEditorFreeLicense } from '../license/isCKEditorFreeLicense.js';\n\n/**\n * Creates a plugin that collects usage data for a specific integration.\n *\n * This part of the code is not executed in open-source implementations using a GPL key.\n * It only runs when a specific license key is provided. If you are uncertain whether\n * this applies to your installation, please contact our support team.\n *\n * @param integrationName The name of the integration.\n * @param usageData The usage data for the integration.\n * @returns The plugin that collects the usage data.\n * @example\n * ```ts\n * import { createUsageDataPlugin } from './usage-data.plugin';\n *\n * const integrationUsageDataPlugin = createUsageDataPlugin( 'react', {\n * \tversion: '1.0.0',\n * \tframeworkVersion: '17.0.0'\n * } );\n *\n * const editor = ClassicEditor.create( document.querySelector( '#editor' ), {\n * \tplugins: [ integrationUsageDataPlugin ]\n * } );\n * ```\n */\nexport function createIntegrationUsageDataPlugin(\n\tintegrationName: string,\n\tusageData: IntegrationUsageData\n): IntegrationUsageDataPlugin {\n\treturn function IntegrationUsageDataPlugin( editor: Editor ) {\n\t\t/**\n\t\t * Do not collect usage data for integrations when using a free CKEditor license.\n\t\t */\n\t\tif ( isCKEditorFreeLicense( editor.config.get( 'licenseKey' ) ) ) {\n\t\t\treturn;\n\t\t}\n\n\t\teditor.on<IntegrationCollectUsageDataEvent>( 'collectUsageData', ( source, { setUsageData } ) => {\n\t\t\tsetUsageData( `integration.${ integrationName }`, usageData );\n\t\t} );\n\t} satisfies PluginConstructor;\n}\n\n/**\n * The plugin collects usage data for an integration.\n */\nexport type IntegrationUsageDataPlugin = ( editor: Editor ) => void;\n\n/**\n * The usage data for an integration.\n */\ntype IntegrationUsageData = {\n\n\t/**\n\t * The version of the integration.\n\t */\n\tversion: string;\n\n\t/**\n\t * The version of the framework that the integration is using.\n\t */\n\tframeworkVersion?: string;\n};\n\n/**\n * The event fires when the editor collects usage data for integrations.\n * The editor should fire it after the `ready` event so the integrations can provide their usage data.\n */\ntype IntegrationCollectUsageDataEvent = {\n\tname: 'collectUsageData';\n\targs: [\n\t\t{\n\t\t\tsetUsageData( path: `integration.${ string }`, value: IntegrationUsageData ): void;\n\t\t}\n\t];\n};\n","/**\n * @license Copyright (c) 2003-2025, CKSource Holding sp. z o.o. All rights reserved.\n * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options\n */\n\nimport type { CKCdnVersion } from './isCKCdnVersion.js';\n\n/**\n * The URL of the CKEditor CDN.\n */\nexport const CK_CDN_URL = 'https://cdn.ckeditor.com';\n\n/**\n * Creates a URL to a file on the CKEditor CDN.\n *\n * @param bundle The name of the bundle.\n * @param file The name of the file.\n * @param version The version of the file.\n * @returns A function that accepts the version of the file and returns the URL.\n *\n * ```ts\n * const url = createCKCdnUrl( 'classic', 'ckeditor.js', '27.0.0' );\n *\n * expect( url ).to.be.equal( 'https://cdn.ckeditor.com/classic/27.0.0/ckeditor.js' );\n * ```\n */\nexport function createCKCdnUrl( bundle: string, file: string, version: CKCdnVersion ): string {\n\treturn `${ CK_CDN_URL }/${ bundle }/${ version }/${ file }`;\n}\n\n/**\n * A function that creates a URL to a file on the CKEditor CDN.\n */\nexport type CKCdnUrlCreator = typeof createCKCdnUrl;\n","/**\n * @license Copyright (c) 2003-2025, CKSource Holding sp. z o.o. All rights reserved.\n * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options\n */\n\nimport type { SemanticVersion } from '../../utils/version/isSemanticVersion.js';\n\n/**\n * The URL of the CKBox CDN.\n */\nexport const CKBOX_CDN_URL = 'https://cdn.ckbox.io';\n\n/**\n * A version of a file on the CKBox CDN.\n */\nexport type CKBoxCdnVersion = SemanticVersion;\n\n/**\n * Creates a URL to a file on the CKBox CDN.\n *\n * @param bundle The name of the bundle.\n * @param file The name of the file.\n * @param version The version of the file.\n * @returns A function that accepts the version of the file and returns the URL.\n *\n * ```ts\n * const url = createCKBoxCdnUrl( 'ckbox', 'ckbox.js', '2.5.1' );\n *\n * expect( url ).to.be.equal( 'https://cdn.ckbox.io/ckbox/2.5.1/ckbox.js' );\n * ```\n */\nexport function createCKBoxCdnUrl( bundle: string, file: string, version: CKBoxCdnVersion ): string {\n\treturn `${ CKBOX_CDN_URL }/${ bundle }/${ version }/${ file }`;\n}\n","/**\n * @license Copyright (c) 2003-2025, CKSource Holding sp. z o.o. All rights reserved.\n * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options\n */\n\nimport type { SemanticVersion } from '../utils/version/isSemanticVersion.js';\n\nexport const CK_DOCS_URL = 'https://ckeditor.com/docs/ckeditor5';\n\n/**\n * Creates a URL to a file on the CKEditor documentation.\n */\nexport function createCKDocsUrl( path: string, version: SemanticVersion | 'latest' = 'latest' ): string {\n\treturn `${ CK_DOCS_URL }/${ version }/${ path }`;\n}\n","/**\n * @license Copyright (c) 2003-2025, CKSource Holding sp. z o.o. All rights reserved.\n * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options\n */\n\nimport type { CKCdnResourcesAdvancedPack } from '../../cdn/utils/loadCKCdnResourcesPack.js';\n\nimport { waitForWindowEntry } from '../../utils/waitForWindowEntry.js';\nimport { injectScriptsInParallel } from '../../utils/injectScript.js';\nimport { without } from '../../utils/without.js';\n\nimport { getCKBaseBundleInstallationInfo } from '../../installation-info/getCKBaseBundleInstallationInfo.js';\nimport { createCKDocsUrl } from '../../docs/createCKDocsUrl.js';\n\nimport { createCKCdnUrl, type CKCdnUrlCreator } from './createCKCdnUrl.js';\nimport type { CKCdnVersion } from './isCKCdnVersion.js';\n\nimport './globals.js';\n\n/**\n * Creates a pack of resources for the base CKEditor bundle.\n *\n * @param config The configuration of the CKEditor Premium Features pack.\n * @returns A pack of resources for the base CKEditor bundle.\n * @example\n *\n * ```ts\n * const { Paragraph } = await loadCKCdnResourcesPack(\n * \tcreateCKCdnBaseBundlePack( {\n * \t\tversion: '44.0.0',\n * \t\ttranslations: [ 'es', 'de' ]\n * \t} )\n * );\n * ```\n */\nexport function createCKCdnBaseBundlePack(\n\t{\n\t\tversion,\n\t\ttranslations,\n\t\tcreateCustomCdnUrl = createCKCdnUrl\n\t}: CKCdnBaseBundlePackConfig\n): CKCdnResourcesAdvancedPack<Window['CKEDITOR']> {\n\tconst urls = {\n\t\tscripts: [\n\t\t\t// Load the main script of the base features.\n\t\t\tcreateCustomCdnUrl( 'ckeditor5', 'ckeditor5.umd.js', version ),\n\n\t\t\t// Load all JavaScript files from the base features.\n\t\t\t// EN bundle is prebuilt into the main script, so we don't need to load it separately.\n\t\t\t...without( [ 'en' ], translations || [] ).map( translation =>\n\t\t\t\tcreateCustomCdnUrl( 'ckeditor5', `translations/${ translation }.umd.js`, version )\n\t\t\t)\n\t\t],\n\n\t\tstylesheets: [\n\t\t\tcreateCustomCdnUrl( 'ckeditor5', 'ckeditor5.css', version )\n\t\t]\n\t};\n\n\treturn {\n\t\t// Preload resources specified in the pack, before loading the main script.\n\t\tpreload: [\n\t\t\t...urls.stylesheets,\n\t\t\t...urls.scripts\n\t\t],\n\n\t\tscripts: [\n\t\t\t// It's safe to load translations and the main script in parallel.\n\t\t\tasync attributes => injectScriptsInParallel( urls.scripts, attributes )\n\t\t],\n\n\t\t// Load all stylesheets of the base features.\n\t\tstylesheets: urls.stylesheets,\n\n\t\t// Pick the exported global variables from the window object.\n\t\tcheckPluginLoaded: async () =>\n\t\t\twaitForWindowEntry( [ 'CKEDITOR' ] ),\n\n\t\t// Check if the CKEditor base bundle is already loaded and throw an error if it is.\n\t\tbeforeInject: () => {\n\t\t\tconst installationInfo = getCKBaseBundleInstallationInfo();\n\n\t\t\tswitch ( installationInfo?.source ) {\n\t\t\t\tcase 'npm':\n\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t'CKEditor 5 is already loaded from npm. Check the migration guide for more details: ' +\n\t\t\t\t\t\tcreateCKDocsUrl( 'updating/migration-to-cdn/vanilla-js.html' )\n\t\t\t\t\t);\n\n\t\t\t\tcase 'cdn':\n\t\t\t\t\tif ( installationInfo.version !== version ) {\n\t\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t\t`CKEditor 5 is already loaded from CDN in version ${ installationInfo.version }. ` +\n\t\t\t\t\t\t\t`Remove the old <script> and <link> tags loading CKEditor 5 to allow loading the ${ version } version.`\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\n\t\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t};\n}\n\n/**\n * Configuration of the base CKEditor bundle pack.\n */\nexport type CKCdnBaseBundlePackConfig = {\n\n\t/**\n\t * The version of the base CKEditor bundle.\n\t */\n\tversion: CKCdnVersion;\n\n\t/**\n\t * The list of translations to load.\n\t */\n\ttranslations?: Array<string>;\n\n\t/**\n\t * The function that creates custom CDN URLs.\n\t */\n\tcreateCustomCdnUrl?: CKCdnUrlCreator;\n};\n","/**\n * @license Copyright (c) 2003-2025, CKSource Holding sp. z o.o. All rights reserved.\n * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options\n */\n\nimport type { CKCdnResourcesAdvancedPack } from '../../cdn/utils/loadCKCdnResourcesPack.js';\nimport type { CKCdnBaseBundlePackConfig } from './createCKCdnBaseBundlePack.js';\n\nimport { waitForWindowEntry } from '../../utils/waitForWindowEntry.js';\nimport { injectScriptsInParallel } from '../../utils/injectScript.js';\nimport { without } from '../../utils/without.js';\n\nimport { createCKCdnUrl } from './createCKCdnUrl.js';\n\n/**\n * Creates a pack of resources for the CKEditor Premium Features.\n *\n * @param config The configuration of the CKEditor Premium Features pack.\n * @returns A pack of resources for the CKEditor Premium Features.\n * @example\n *\n * ```ts\n * const { SlashCommand } = await loadCKCdnResourcesPack(\n * \tcreateCKCdnPremiumBundlePack( {\n * \t\tversion: '44.0.0',\n * \t\ttranslations: [ 'es', 'de' ]\n * \t} )\n * );\n * ```\n */\nexport function createCKCdnPremiumBundlePack(\n\t{\n\t\tversion,\n\t\ttranslations,\n\t\tcreateCustomCdnUrl = createCKCdnUrl\n\t}: CKCdnPremiumBundlePackConfig\n): CKCdnResourcesAdvancedPack<Window['CKEDITOR_PREMIUM_FEATURES']> {\n\tconst urls = {\n\t\tscripts: [\n\t\t\t// Load the main script of the premium features.\n\t\t\tcreateCustomCdnUrl( 'ckeditor5-premium-features', 'ckeditor5-premium-features.umd.js', version ),\n\n\t\t\t// Load all JavaScript files from the premium features.\n\t\t\t// EN bundle is prebuilt into the main script, so we don't need to load it separately.\n\t\t\t...without( [ 'en' ], translations || [] ).map( translation =>\n\t\t\t\tcreateCustomCdnUrl( 'ckeditor5-premium-features', `translations/${ translation }.umd.js`, version )\n\t\t\t)\n\t\t],\n\n\t\tstylesheets: [\n\t\t\tcreateCustomCdnUrl( 'ckeditor5-premium-features', 'ckeditor5-premium-features.css', version )\n\t\t]\n\t};\n\n\treturn {\n\t\t// Preload resources specified in the pack, before loading the main script.\n\t\tpreload: [\n\t\t\t...urls.stylesheets,\n\t\t\t...urls.scripts\n\t\t],\n\n\t\tscripts: [\n\t\t\t// It's safe to load translations and the main script in parallel.\n\t\t\tasync attributes => injectScriptsInParallel( urls.scripts, attributes )\n\t\t],\n\n\t\t// Load all stylesheets of the premium features.\n\t\tstylesheets: urls.stylesheets,\n\n\t\t// Pick the exported global variables from the window object.\n\t\tcheckPluginLoaded: async () =>\n\t\t\twaitForWindowEntry( [ 'CKEDITOR_PREMIUM_FEATURES' ] )\n\t};\n}\n\n/**\n * Configuration of the CKEditor Premium Features pack.\n */\nexport type CKCdnPremiumBundlePackConfig = Pick<\n\tCKCdnBaseBundlePackConfig,\n\t'translations' | 'version' | 'createCustomCdnUrl'\n>;\n","/**\n * @license Copyright (c) 2003-2025, CKSource Holding sp. z o.o. All rights reserved.\n * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options\n */\n\nimport type { Awaitable } from '../../types/Awaitable.js';\n\nimport { injectScript, type InjectScriptProps } from '../../utils/injectScript.js';\nimport { injectStylesheet } from '../../utils/injectStylesheet.js';\nimport { preloadResource } from '../../utils/preloadResource.js';\nimport { uniq } from '../../utils/uniq.js';\n\n/**\n * Loads pack of resources (scripts and stylesheets) and returns the exported global variables (if any).\n *\n * @param pack The pack of resources to load.\n * @returns A promise that resolves with the exported global variables.\n * @example\n *\n * ```ts\n * const ckeditor = await loadCKCdnResourcesPack<ClassicEditor>( {\n * \tscripts: [\n * \t\t'https://cdn.ckeditor.com/ckeditor5/30.0.0/classic/ckeditor.js'\n * \t],\n * \tcheckPluginLoaded: () => ( window as any ).ClassicEditor\n * } );\n * ```\n */\nexport async function loadCKCdnResourcesPack<P extends CKCdnResourcesPack<any>>( pack: P ): Promise<InferCKCdnResourcesPackExportsType<P>> {\n\tlet {\n\t\thtmlAttributes = {},\n\t\tscripts = [],\n\t\tstylesheets = [],\n\t\tpreload,\n\t\tbeforeInject,\n\t\tcheckPluginLoaded\n\t} = normalizeCKCdnResourcesPack( pack );\n\n\t// Execute the `beforeInject` callback if defined. It checks if the resources are already loaded.\n\tbeforeInject?.();\n\n\t// If preload is not defined, use all stylesheets and scripts as preload resources.\n\tif ( !preload ) {\n\t\tpreload = uniq( [\n\t\t\t...stylesheets.filter( item => typeof item === 'string' ),\n\t\t\t...scripts.filter( item => typeof item === 'string' )\n\t\t] );\n\t}\n\n\t// Preload resources specified in the pack.\n\tfor ( const url of preload ) {\n\t\tpreloadResource( url, {\n\t\t\tattributes: htmlAttributes\n\t\t} );\n\t}\n\n\t// Load stylesheet tags before scripts to avoid a flash of unstyled content.\n\tawait Promise.all(\n\t\tuniq( stylesheets ).map( href => injectStylesheet( {\n\t\t\thref,\n\t\t\tattributes: htmlAttributes,\n\t\t\tplacementInHead: 'start'\n\t\t} ) )\n\t);\n\n\t// Load script tags.\n\tfor ( const script of uniq( scripts ) ) {\n\t\tconst injectorProps: InjectScriptProps = {\n\t\t\tattributes: htmlAttributes\n\t\t};\n\n\t\tif ( typeof script === 'string' ) {\n\t\t\tawait injectScript( script, injectorProps );\n\t\t} else {\n\t\t\tawait script( injectorProps );\n\t\t}\n\t}\n\n\t// Wait for execution all injected scripts.\n\treturn checkPluginLoaded?.();\n}\n\n/**\n * Normalizes the CKCdnResourcesPack configuration to the advanced format.\n *\n * @param pack The pack of resources to normalize.\n * @returns The normalized pack of resources.\n */\nexport function normalizeCKCdnResourcesPack<R = any>( pack: CKCdnResourcesPack<R> ): CKCdnResourcesAdvancedPack<R> {\n\t// Check if it is array of URLs, if so, convert it to the advanced format.\n\tif ( Array.isArray( pack ) ) {\n\t\treturn {\n\t\t\tscripts: pack.filter(\n\t\t\t\titem => typeof item === 'function' || item.endsWith( '.js' )\n\t\t\t),\n\n\t\t\tstylesheets: pack.filter(\n\t\t\t\titem => item.endsWith( '.css' )\n\t\t\t)\n\t\t};\n\t}\n\n\t// Check if it is a local import function, if so, convert it to the advanced format.\n\tif ( typeof pack === 'function' ) {\n\t\treturn {\n\t\t\tcheckPluginLoaded: pack\n\t\t};\n\t}\n\n\t// Return the pack as it is, because it's already in the advanced format.\n\treturn pack;\n}\n\n/**\n * A pack of resources to load (scripts and stylesheets) and the exported global variables.\n */\nexport type CKCdnResourcesPack<R = any> =\n\t| CKCdnResourcesAdvancedPack<R>\n\t| CKCdnResourcesBasicUrlsPack\n\t| CKCdnResourcesLocalPack<R>;\n\n/**\n * A pack of resources to load as an async function that results with UMD module.\n *\n * @example\n * ```ts\n * const pack = async () => import( './your-module' );\n * ```\n */\ntype CKCdnResourcesLocalPack<R> = () => Awaitable<R>;\n\n/**\n * A pack of resources to load (scripts and stylesheets). In such configuration, the exported global variable\n * might be available but it's not guaranteed.\n *\n * @example\n * ```ts\n * const pack = [\n * \t'https://cdn.ckeditor.com/ckeditor5/30.0.0/classic/ckeditor.js'\n * ];\n * ```\n */\ntype CKCdnResourcesBasicUrlsPack = Array<string>;\n\n/**\n * A pack of resources to load (scripts and stylesheets) and the exported global variables.\n *\n * @example\n * ```ts\n * const pack = {\n * \tscripts: [\n * \t\t'https://cdn.ckeditor.com/ckeditor5/30.0.0/classic/ckeditor.js'\n * \t],\n * \tcheckPluginLoaded: () => ( window as any ).ClassicEditor\n * };\n * ```\n */\nexport type CKCdnResourcesAdvancedPack<R> = {\n\n\t/**\n\t * List of resources to preload, it should i