UNPKG

@ckeditor/ckeditor5-integrations-common

Version:

This package implements common utility modules for integration projects.

944 lines (891 loc) 30 kB
(function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) : typeof define === 'function' && define.amd ? define(['exports'], factory) : (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.CKEDITOR_INTEGRATIONS_COMMON = {})); })(this, (function (exports) { 'use strict'; function createDefer() { const deferred = { resolve: null, promise: null }; deferred.promise = new Promise((resolve) => { deferred.resolve = resolve; }); return deferred; } function waitFor(callback, { timeOutAfter = 500, retryAfter = 100 } = {}) { return new Promise((resolve, reject) => { const startTime = Date.now(); let lastError = null; const timeoutTimerId = setTimeout(() => { reject(lastError ?? new Error("Timeout")); }, timeOutAfter); const tick = async () => { try { const result = await callback(); clearTimeout(timeoutTimerId); resolve(result); } catch (err) { lastError = err; if (Date.now() - startTime > timeOutAfter) { reject(err); } else { setTimeout(tick, retryAfter); } } }; tick(); }); } const INJECTED_SCRIPTS = /* @__PURE__ */ new Map(); function injectScript(src, { attributes } = {}) { if (INJECTED_SCRIPTS.has(src)) { return INJECTED_SCRIPTS.get(src); } const maybePrevScript = document.querySelector(`script[src="${src}"]`); if (maybePrevScript) { console.warn(`Script with "${src}" src is already present in DOM!`); maybePrevScript.remove(); } const promise = new Promise((resolve, reject) => { const script = document.createElement("script"); script.onerror = reject; script.onload = () => { resolve(); }; for (const [key, value] of Object.entries(attributes || {})) { script.setAttribute(key, value); } script.setAttribute("data-injected-by", "ckeditor-integration"); script.type = "text/javascript"; script.async = true; script.src = src; document.head.appendChild(script); const observer = new MutationObserver((mutations) => { const removedNodes = mutations.flatMap((mutation) => Array.from(mutation.removedNodes)); if (removedNodes.includes(script)) { INJECTED_SCRIPTS.delete(src); observer.disconnect(); } }); observer.observe(document.head, { childList: true, subtree: true }); }); INJECTED_SCRIPTS.set(src, promise); return promise; } async function injectScriptsInParallel(sources, props) { await Promise.all( sources.map((src) => injectScript(src, props)) ); } const INJECTED_STYLESHEETS = /* @__PURE__ */ new Map(); function injectStylesheet({ href, placementInHead = "start", attributes = {} }) { if (INJECTED_STYLESHEETS.has(href)) { return INJECTED_STYLESHEETS.get(href); } const maybePrevStylesheet = document.querySelector(`link[href="${href}"][rel="stylesheet"]`); if (maybePrevStylesheet) { console.warn(`Stylesheet with "${href}" href is already present in DOM!`); maybePrevStylesheet.remove(); } const appendLinkTagToHead = (link) => { const previouslyInjectedLinks = Array.from( document.head.querySelectorAll('link[data-injected-by="ckeditor-integration"]') ); switch (placementInHead) { // It'll append styles *before* the stylesheets that are already present in the head // but after the ones that are injected by this function. case "start": if (previouslyInjectedLinks.length) { previouslyInjectedLinks.slice(-1)[0].after(link); } else { document.head.insertBefore(link, document.head.firstChild); } break; // It'll append styles *after* the stylesheets already in the head. case "end": document.head.appendChild(link); break; } }; const promise = new Promise((resolve, reject) => { const link = document.createElement("link"); for (const [key, value] of Object.entries(attributes || {})) { link.setAttribute(key, value); } link.setAttribute("data-injected-by", "ckeditor-integration"); link.rel = "stylesheet"; link.href = href; link.onerror = reject; link.onload = () => { resolve(); }; appendLinkTagToHead(link); const observer = new MutationObserver((mutations) => { const removedNodes = mutations.flatMap((mutation) => Array.from(mutation.removedNodes)); if (removedNodes.includes(link)) { INJECTED_STYLESHEETS.delete(href); observer.disconnect(); } }); observer.observe(document.head, { childList: true, subtree: true }); }); INJECTED_STYLESHEETS.set(href, promise); return promise; } function isSSR() { return typeof window === "undefined"; } function omit(keys, obj) { const clone = { ...obj }; for (const key of keys) { if (Object.hasOwn(obj, key)) { delete clone[key]; } } return clone; } function once(fn) { let lastResult = null; return (...args) => { if (!lastResult) { lastResult = { current: fn(...args) }; } return lastResult.current; }; } function overwriteArray(source, destination) { destination.length = 0; destination.push(...source); return destination; } function overwriteObject(source, destination) { for (const prop of Object.getOwnPropertyNames(destination)) { delete destination[prop]; } for (const [key, value] of Object.entries(source)) { if (value !== destination && key !== "prototype" && key !== "__proto__") { destination[key] = value; } } return destination; } function preloadResource(url, { attributes } = {}) { if (document.head.querySelector(`link[href="${url}"][rel="preload"]`)) { return; } const link = document.createElement("link"); for (const [key, value] of Object.entries(attributes || {})) { link.setAttribute(key, value); } link.setAttribute("data-injected-by", "ckeditor-integration"); link.rel = "preload"; link.as = detectTypeOfResource(url); link.href = url; document.head.insertBefore(link, document.head.firstChild); } function detectTypeOfResource(url) { switch (true) { case /\.css$/.test(url): return "style"; case /\.js$/.test(url): return "script"; default: return "fetch"; } } function shallowCompareArrays(a, b) { if (a === b) { return true; } if (!a || !b) { return false; } for (let i = 0; i < a.length; ++i) { if (a[i] !== b[i]) { return false; } } return true; } const HEX_NUMBERS = new Array(256).fill("").map((_, index) => ("0" + index.toString(16)).slice(-2)); function uid() { const [r1, r2, r3, r4] = crypto.getRandomValues(new Uint32Array(4)); return "e" + HEX_NUMBERS[r1 >> 0 & 255] + HEX_NUMBERS[r1 >> 8 & 255] + HEX_NUMBERS[r1 >> 16 & 255] + HEX_NUMBERS[r1 >> 24 & 255] + HEX_NUMBERS[r2 >> 0 & 255] + HEX_NUMBERS[r2 >> 8 & 255] + HEX_NUMBERS[r2 >> 16 & 255] + HEX_NUMBERS[r2 >> 24 & 255] + HEX_NUMBERS[r3 >> 0 & 255] + HEX_NUMBERS[r3 >> 8 & 255] + HEX_NUMBERS[r3 >> 16 & 255] + HEX_NUMBERS[r3 >> 24 & 255] + HEX_NUMBERS[r4 >> 0 & 255] + HEX_NUMBERS[r4 >> 8 & 255] + HEX_NUMBERS[r4 >> 16 & 255] + HEX_NUMBERS[r4 >> 24 & 255]; } function uniq(source) { return Array.from(new Set(source)); } async function waitForWindowEntry(entryNames, config) { const tryPickBundle = () => entryNames.map((name) => window[name]).filter(Boolean)[0]; return waitFor( () => { const result = tryPickBundle(); if (!result) { throw new Error(`Window entry "${entryNames.join(",")}" not found.`); } return result; }, config ); } function filterObjectValues(obj, filter) { const filteredEntries = Object.entries(obj).filter(([key, value]) => filter(value, key)); return Object.fromEntries(filteredEntries); } function filterBlankObjectValues(obj) { return filterObjectValues( obj, (value) => value !== null && value !== void 0 ); } function mapObjectValues(obj, mapper) { const mappedEntries = Object.entries(obj).map(([key, value]) => [key, mapper(value, key)]); return Object.fromEntries(mappedEntries); } function without(itemsToRemove, items) { return items.filter((item) => !itemsToRemove.includes(item)); } function mapObjectKeys(obj, fn) { return Object.fromEntries( Object.entries(obj).map(([key, value]) => [fn(key), value]) ); } function kebabToCamelCase(str) { return str.replace(/-([a-z])/g, (match, letter) => letter.toUpperCase()); } function isSemanticVersion(version) { return !!version && /^\d+\.\d+\.\d+/.test(version); } function destructureSemanticVersion(version) { if (!isSemanticVersion(version)) { throw new Error(`Invalid semantic version: ${version || "<blank>"}.`); } const [major, minor, patch] = version.split("."); return { major: Number.parseInt(major, 10), minor: Number.parseInt(minor, 10), patch: Number.parseInt(patch, 10) }; } function compareSemanticVersions(a, b) { const parsedA = destructureSemanticVersion(a); const parsedB = destructureSemanticVersion(b); return Math.sign( parsedA.major - parsedB.major || parsedA.minor - parsedB.minor || parsedA.patch - parsedB.patch ); } function isCKTestingVersion(version) { if (!version) { return false; } return ["nightly", "alpha", "internal", "nightly-", "staging"].some((testVersion) => version.includes(testVersion)); } function isCKZeroBaseVersion(version) { return !!version?.startsWith("0.0.0-"); } function isCKVersion(version) { return isSemanticVersion(version) || isCKTestingVersion(version); } function appendExtraPluginsToEditorConfig(config, plugins) { const extraPlugins = config.extraPlugins || []; return { ...config, extraPlugins: [ ...extraPlugins, ...plugins.filter((item) => !extraPlugins.includes(item)) ] }; } function getLicenseVersionFromEditorVersion(version) { if (isCKTestingVersion(version)) { return 3; } const { major } = destructureSemanticVersion(version); switch (true) { case major >= 44: return 3; case major >= 38: return 2; default: return 1; } } function getCKBaseBundleInstallationInfo() { const { CKEDITOR_VERSION, CKEDITOR } = window; if (!isCKVersion(CKEDITOR_VERSION)) { return null; } return { source: CKEDITOR ? "cdn" : "npm", version: CKEDITOR_VERSION }; } function getSupportedLicenseVersionInstallationInfo() { const installationInfo = getCKBaseBundleInstallationInfo(); if (!installationInfo) { return null; } return getLicenseVersionFromEditorVersion(installationInfo.version); } function isCKEditorFreeLicense(licenseKey, licenseVersion) { licenseVersion ||= getSupportedLicenseVersionInstallationInfo() || void 0; switch (licenseVersion) { case 1: case 2: return licenseKey === void 0; case 3: return licenseKey === "GPL"; default: { return false; } } } function createIntegrationUsageDataPlugin(integrationName, usageData) { return function IntegrationUsageDataPlugin(editor) { if (isCKEditorFreeLicense(editor.config.get("licenseKey"))) { return; } editor.on("collectUsageData", (source, { setUsageData }) => { setUsageData(`integration.${integrationName}`, usageData); }); }; } const CK_CDN_URL = "https://cdn.ckeditor.com"; function createCKCdnUrl(bundle, file, version) { return `${CK_CDN_URL}/${bundle}/${version}/${file}`; } const CKBOX_CDN_URL = "https://cdn.ckbox.io"; function createCKBoxCdnUrl(bundle, file, version) { return `${CKBOX_CDN_URL}/${bundle}/${version}/${file}`; } const CK_DOCS_URL = "https://ckeditor.com/docs/ckeditor5"; function createCKDocsUrl(path, version = "latest") { return `${CK_DOCS_URL}/${version}/${path}`; } function createCKCdnBaseBundlePack({ version, translations, createCustomCdnUrl = createCKCdnUrl }) { const urls = { scripts: [ // Load the main script of the base features. createCustomCdnUrl("ckeditor5", "ckeditor5.umd.js", version), // Load all JavaScript files from the base features. // EN bundle is prebuilt into the main script, so we don't need to load it separately. ...without(["en"], translations || []).map( (translation) => createCustomCdnUrl("ckeditor5", `translations/${translation}.umd.js`, version) ) ], stylesheets: [ createCustomCdnUrl("ckeditor5", "ckeditor5.css", version) ] }; return { // Preload resources specified in the pack, before loading the main script. preload: [ ...urls.stylesheets, ...urls.scripts ], scripts: [ // It's safe to load translations and the main script in parallel. async (attributes) => injectScriptsInParallel(urls.scripts, attributes) ], // Load all stylesheets of the base features. stylesheets: urls.stylesheets, // Pick the exported global variables from the window object. checkPluginLoaded: async () => waitForWindowEntry(["CKEDITOR"]), // Check if the CKEditor base bundle is already loaded and throw an error if it is. beforeInject: () => { const installationInfo = getCKBaseBundleInstallationInfo(); switch (installationInfo?.source) { case "npm": throw new Error( "CKEditor 5 is already loaded from npm. Check the migration guide for more details: " + createCKDocsUrl("updating/migrations/vanilla-js.html") ); case "cdn": if (installationInfo.version !== version) { throw new Error( `CKEditor 5 is already loaded from CDN in version ${installationInfo.version}. Remove the old <script> and <link> tags loading CKEditor 5 to allow loading the ${version} version.` ); } break; } } }; } function createCKCdnPremiumBundlePack({ version, translations, createCustomCdnUrl = createCKCdnUrl }) { const urls = { scripts: [ // Load the main script of the premium features. createCustomCdnUrl("ckeditor5-premium-features", "ckeditor5-premium-features.umd.js", version), // Load all JavaScript files from the premium features. // EN bundle is prebuilt into the main script, so we don't need to load it separately. ...without(["en"], translations || []).map( (translation) => createCustomCdnUrl("ckeditor5-premium-features", `translations/${translation}.umd.js`, version) ) ], stylesheets: [ createCustomCdnUrl("ckeditor5-premium-features", "ckeditor5-premium-features.css", version) ] }; return { // Preload resources specified in the pack, before loading the main script. preload: [ ...urls.stylesheets, ...urls.scripts ], scripts: [ // It's safe to load translations and the main script in parallel. async (attributes) => injectScriptsInParallel(urls.scripts, attributes) ], // Load all stylesheets of the premium features. stylesheets: urls.stylesheets, // Pick the exported global variables from the window object. checkPluginLoaded: async () => waitForWindowEntry(["CKEDITOR_PREMIUM_FEATURES"]) }; } async function loadCKCdnResourcesPack(pack) { let { htmlAttributes = {}, scripts = [], stylesheets = [], preload, beforeInject, checkPluginLoaded } = normalizeCKCdnResourcesPack(pack); beforeInject?.(); if (!preload) { preload = uniq([ ...stylesheets.filter((item) => typeof item === "string"), ...scripts.filter((item) => typeof item === "string") ]); } for (const url of preload) { preloadResource(url, { attributes: htmlAttributes }); } await Promise.all( uniq(stylesheets).map((href) => injectStylesheet({ href, attributes: htmlAttributes, placementInHead: "start" })) ); for (const script of uniq(scripts)) { const injectorProps = { attributes: htmlAttributes }; if (typeof script === "string") { await injectScript(script, injectorProps); } else { await script(injectorProps); } } return checkPluginLoaded?.(); } function normalizeCKCdnResourcesPack(pack) { if (Array.isArray(pack)) { return { scripts: pack.filter( (item) => typeof item === "function" || item.endsWith(".js") ), stylesheets: pack.filter( (item) => item.endsWith(".css") ) }; } if (typeof pack === "function") { return { checkPluginLoaded: pack }; } return pack; } function combineCKCdnBundlesPacks(packs) { const normalizedPacks = mapObjectValues( filterBlankObjectValues(packs), normalizeCKCdnResourcesPack ); const mergedPacks = Object.values(normalizedPacks).reduce( (acc, pack) => { acc.scripts.push(...pack.scripts ?? []); acc.stylesheets.push(...pack.stylesheets ?? []); acc.preload.push(...pack.preload ?? []); return acc; }, { preload: [], scripts: [], stylesheets: [] } ); const checkPluginLoaded = async () => { const exportedGlobalVariables = /* @__PURE__ */ Object.create(null); for (const [name, pack] of Object.entries(normalizedPacks)) { exportedGlobalVariables[name] = await pack?.checkPluginLoaded?.(); } return exportedGlobalVariables; }; const beforeInject = () => { for (const pack of Object.values(normalizedPacks)) { pack.beforeInject?.(); } }; return { ...mergedPacks, beforeInject, checkPluginLoaded }; } function getCKBoxInstallationInfo() { const version = window.CKBox?.version; if (!isSemanticVersion(version)) { return null; } return { source: "cdn", version }; } function createCKBoxBundlePack({ version, theme = "lark", translations, createCustomCdnUrl = createCKBoxCdnUrl }) { return { // Load the main script of the base features. scripts: [ createCustomCdnUrl("ckbox", "ckbox.js", version), // EN bundle is prebuilt into the main script, so we don't need to load it separately. ...without(["en"], translations || []).map( (translation) => createCustomCdnUrl("ckbox", `translations/${translation}.js`, version) ) ], // Load optional theme, if provided. It's not required but recommended because it improves the look and feel. ...theme && { stylesheets: [ createCustomCdnUrl("ckbox", `styles/themes/${theme}.css`, version) ] }, // Pick the exported global variables from the window object. checkPluginLoaded: async () => waitForWindowEntry(["CKBox"]), // Check if the CKBox bundle is already loaded and throw an error if it is. beforeInject: () => { const installationInfo = getCKBoxInstallationInfo(); if (installationInfo && installationInfo.version !== version) { throw new Error( `CKBox is already loaded from CDN in version ${installationInfo.version}. Remove the old <script> and <link> tags loading CKBox to allow loading the ${version} version.` ); } } }; } function isCKCdnSupportedByEditorVersion(version) { if (isCKTestingVersion(version)) { return true; } const { major } = destructureSemanticVersion(version); const licenseVersion = getLicenseVersionFromEditorVersion(version); switch (licenseVersion) { // For newer license versions, we support all newer versions. case 3: return true; // For the license v1-v2, we support only the 43 version. default: return major === 43; } } function combineCdnPluginsPacks(pluginsPacks) { const normalizedPluginsPacks = mapObjectValues(pluginsPacks, (pluginPack, pluginName) => { if (!pluginPack) { return void 0; } const normalizedPluginPack = normalizeCKCdnResourcesPack(pluginPack); return { // Provide default window accessor object if the plugin pack does not define it. checkPluginLoaded: async () => waitForWindowEntry([pluginName]), // Transform the plugin pack to a normalized advanced pack. ...normalizedPluginPack }; }); return combineCKCdnBundlesPacks( normalizedPluginsPacks ); } function loadCKEditorCloud(config) { const { version, translations, plugins, premium, ckbox, createCustomCdnUrl, injectedHtmlElementsAttributes = { crossorigin: "anonymous" } } = config; validateCKEditorVersion(version); const pack = combineCKCdnBundlesPacks({ CKEditor: createCKCdnBaseBundlePack({ version, translations, createCustomCdnUrl }), ...premium && { CKEditorPremiumFeatures: createCKCdnPremiumBundlePack({ version, translations, createCustomCdnUrl }) }, ...ckbox && { CKBox: createCKBoxBundlePack(ckbox) }, loadedPlugins: combineCdnPluginsPacks(plugins ?? {}) }); return loadCKCdnResourcesPack( { ...pack, htmlAttributes: injectedHtmlElementsAttributes } ); } function validateCKEditorVersion(version) { if (isCKTestingVersion(version)) { console.warn( "You are using a testing version of CKEditor 5. Please remember that it is not suitable for production environments." ); } if (!isCKCdnSupportedByEditorVersion(version)) { throw new Error( `The CKEditor 5 CDN can't be used with the given editor version: ${version}. Please make sure you are using at least the CKEditor 5 version 44.` ); } } function compareInstalledCKBaseVersion(version) { const installedVersion = getCKBaseBundleInstallationInfo()?.version; if (!installedVersion) { return null; } if (isCKZeroBaseVersion(version)) { return -1; } if (!isSemanticVersion(installedVersion) || isCKZeroBaseVersion(installedVersion)) { return 1; } return compareSemanticVersions(installedVersion, version); } function getInstalledCKBaseFeatures() { const installedVersion = compareInstalledCKBaseVersion("48.0.0"); const isV48OrNewer = installedVersion !== null && installedVersion >= 0; return { rootsConfigEntry: isV48OrNewer, elementConfigAttachment: isV48OrNewer }; } function assignAttributesPropToMultiRootEditorConfig(attributes, config) { const supports = getInstalledCKBaseFeatures(); if (supports.rootsConfigEntry) { const knownRootsKeys = uniq([ ...Object.keys(attributes || {}), ...Object.keys(config.roots || {}), ...Object.keys(config.rootsAttributes || {}) ]); const roots = knownRootsKeys.reduce((acc, rootName) => { const legacyRootAttributes = config.rootsAttributes?.[rootName]; const configRootValue = config.roots?.[rootName]; acc[rootName] = { ...configRootValue, modelAttributes: attributes?.[rootName] ?? { ...legacyRootAttributes, ...configRootValue?.modelAttributes } }; return acc; }, /* @__PURE__ */ Object.create(null)); const mappedConfig = { ...config, roots }; delete mappedConfig.rootsAttributes; return mappedConfig; } return { ...config, rootsAttributes: attributes }; } function assignInitialDataToMultirootEditorConfig(data, config) { const supports = getInstalledCKBaseFeatures(); if (supports.rootsConfigEntry) { const knownRootsKeys = uniq([ ...Object.keys(data || {}), ...Object.keys(config.roots || {}), ...typeof config.initialData === "string" ? [] : Object.keys(config.initialData || {}) ]); const roots = knownRootsKeys.reduce((acc, rootName) => { const rootConfig = config.roots?.[rootName]; const rootInitialData = rootConfig?.initialData; if (rootInitialData && data?.[rootName]) { console.warn( `Editor data should be provided either using \`config.roots['${rootName}'].initialData\` or the bound component property. The config value takes precedence over the bound component property and will be used when both are specified.` ); } acc[rootName] = { ...rootConfig, initialData: rootInitialData || data?.[rootName] || config.initialData?.[rootName] || "" }; return acc; }, /* @__PURE__ */ Object.create(null)); const normalizedConfig = { ...config, roots }; delete normalizedConfig.initialData; return normalizedConfig; } if (data && config?.initialData) { console.warn( "Editor data should be provided either using `config.initialData` or the bound component property. The config value takes precedence over the bound component property and will be used when both are specified." ); } return { ...config, initialData: config?.initialData || data }; } function assignElementToEditorConfig(Editor, element, config) { if (!Editor.editorName || Editor.editorName === "ClassicEditor") { return { ...config, attachTo: element }; } const mappedConfig = { ...config, roots: { ...config.roots, main: { ...config.root, ...config.roots?.main, element } } }; delete mappedConfig.root; return mappedConfig; } function getInitialDataFromEditorConfig(config) { const supports = getInstalledCKBaseFeatures(); if (supports.rootsConfigEntry) { return config.roots?.main?.initialData || config.root?.initialData || /* legacy */ config.initialData; } return config.initialData; } function assignInitialDataToEditorConfig(config, data, ignoreConfigInitialData) { const supports = getInstalledCKBaseFeatures(); const configInitialData = ignoreConfigInitialData ? null : getInitialDataFromEditorConfig(config); if (supports.rootsConfigEntry) { const normalizedConfig = { ...config, roots: { ...config.roots, main: { ...config.root, ...config.roots?.main, initialData: configInitialData || data || "" } } }; if (data && configInitialData) { console.warn( "Editor data should be provided either via the config (`config.root.initialData`) or the component's `data` property, but not both. The configuration value takes precedence." ); } delete normalizedConfig.root; delete normalizedConfig.initialData; return normalizedConfig; } if (data && configInitialData) { console.warn( "Editor data should be provided either via the config (`config.initialData`) or the component's `data` property, but not both. The configuration value takes precedence." ); } return { ...config, initialData: configInitialData || data || "" }; } exports.CKBOX_CDN_URL = CKBOX_CDN_URL; exports.CK_CDN_URL = CK_CDN_URL; exports.INJECTED_SCRIPTS = INJECTED_SCRIPTS; exports.INJECTED_STYLESHEETS = INJECTED_STYLESHEETS; exports.appendExtraPluginsToEditorConfig = appendExtraPluginsToEditorConfig; exports.assignAttributesPropToMultiRootEditorConfig = assignAttributesPropToMultiRootEditorConfig; exports.assignElementToEditorConfig = assignElementToEditorConfig; exports.assignInitialDataToEditorConfig = assignInitialDataToEditorConfig; exports.assignInitialDataToMultirootEditorConfig = assignInitialDataToMultirootEditorConfig; exports.compareInstalledCKBaseVersion = compareInstalledCKBaseVersion; exports.compareSemanticVersions = compareSemanticVersions; exports.createCKBoxCdnUrl = createCKBoxCdnUrl; exports.createCKCdnUrl = createCKCdnUrl; exports.createDefer = createDefer; exports.createIntegrationUsageDataPlugin = createIntegrationUsageDataPlugin; exports.destructureSemanticVersion = destructureSemanticVersion; exports.filterBlankObjectValues = filterBlankObjectValues; exports.filterObjectValues = filterObjectValues; exports.getCKBaseBundleInstallationInfo = getCKBaseBundleInstallationInfo; exports.getCKBoxInstallationInfo = getCKBoxInstallationInfo; exports.getInitialDataFromEditorConfig = getInitialDataFromEditorConfig; exports.getInstalledCKBaseFeatures = getInstalledCKBaseFeatures; exports.injectScript = injectScript; exports.injectScriptsInParallel = injectScriptsInParallel; exports.injectStylesheet = injectStylesheet; exports.isCKEditorFreeLicense = isCKEditorFreeLicense; exports.isCKTestingVersion = isCKTestingVersion; exports.isCKVersion = isCKVersion; exports.isCKZeroBaseVersion = isCKZeroBaseVersion; exports.isSSR = isSSR; exports.isSemanticVersion = isSemanticVersion; exports.kebabToCamelCase = kebabToCamelCase; exports.loadCKEditorCloud = loadCKEditorCloud; exports.mapObjectKeys = mapObjectKeys; exports.mapObjectValues = mapObjectValues; exports.omit = omit; exports.once = once; exports.overwriteArray = overwriteArray; exports.overwriteObject = overwriteObject; exports.preloadResource = preloadResource; exports.shallowCompareArrays = shallowCompareArrays; exports.uid = uid; exports.uniq = uniq; exports.waitFor = waitFor; exports.waitForWindowEntry = waitForWindowEntry; exports.without = without; Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' }); })); //# sourceMappingURL=index.umd.cjs.map