@ckeditor/ckeditor5-integrations-common
Version:
This package implements common utility modules for integration projects.
889 lines (839 loc) • 27.3 kB
JavaScript
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 || ""
};
}
export { CKBOX_CDN_URL, CK_CDN_URL, INJECTED_SCRIPTS, INJECTED_STYLESHEETS, appendExtraPluginsToEditorConfig, assignAttributesPropToMultiRootEditorConfig, assignElementToEditorConfig, assignInitialDataToEditorConfig, assignInitialDataToMultirootEditorConfig, compareInstalledCKBaseVersion, compareSemanticVersions, createCKBoxCdnUrl, createCKCdnUrl, createDefer, createIntegrationUsageDataPlugin, destructureSemanticVersion, filterBlankObjectValues, filterObjectValues, getCKBaseBundleInstallationInfo, getCKBoxInstallationInfo, getInitialDataFromEditorConfig, getInstalledCKBaseFeatures, injectScript, injectScriptsInParallel, injectStylesheet, isCKEditorFreeLicense, isCKTestingVersion, isCKVersion, isCKZeroBaseVersion, isSSR, isSemanticVersion, kebabToCamelCase, loadCKEditorCloud, mapObjectKeys, mapObjectValues, omit, once, overwriteArray, overwriteObject, preloadResource, shallowCompareArrays, uid, uniq, waitFor, waitForWindowEntry, without };
//# sourceMappingURL=index.js.map