/** * @file * Provides styles for CKEditor inside off-canvas dialogs. */ (($, CKEDITOR) => { /** * Takes a string of CKEditor CSS and modifies it for use in off-canvas. * * @param {string} originalCss * The CSS rules from CKEditor. * @return {string} * The rules from originalCss with extra specificity for off-canvas. */ const convertToOffCanvasCss = originalCss => { const selectorPrefix = '#drupal-off-canvas '; const skinPath = `${CKEDITOR.basePath}${CKEDITOR.skinName}/`; const css = originalCss .substring(originalCss.indexOf('*/') + 2) .trim() .replace(/}/g, `}${selectorPrefix}`) .replace(/,/g, `,${selectorPrefix}`) .replace(/url\(/g, skinPath); return `${selectorPrefix}${css}`; }; /** * Inserts CSS rules into DOM. * * @param {string} cssToInsert * CSS rules to be inserted */ const insertCss = cssToInsert => { const offCanvasCss = document.createElement('style'); offCanvasCss.innerHTML = cssToInsert; offCanvasCss.setAttribute('id', 'ckeditor-off-canvas-reset'); document.body.appendChild(offCanvasCss); }; /** * Adds CSS so CKEditor is styled properly in off-canvas. */ const addCkeditorOffCanvasCss = () => { // If #ckeditor-off-canvas-reset exists, this has already run. if (document.getElementById('ckeditor-off-canvas-reset')) { return; } // CKEDITOR.skin.getPath() requires the CKEDITOR.skinName property. // @see https://stackoverflow.com/a/17336982 CKEDITOR.skinName = CKEDITOR.skin.name; // Get the paths to the css CKEditor is using. const editorCssPath = CKEDITOR.skin.getPath('editor'); const dialogCssPath = CKEDITOR.skin.getPath('dialog'); // The key for cached CSS in localStorage is based on the CSS paths. const storedOffCanvasCss = window.localStorage.getItem( `Drupal.off-canvas.css.${editorCssPath}${dialogCssPath}`, ); // See if CSS is cached in localStorage, and use that when available. if (storedOffCanvasCss) { insertCss(storedOffCanvasCss); return; } // If CSS unavailable in localStorage, get the files via AJAX and parse. $.when($.get(editorCssPath), $.get(dialogCssPath)).done( (editorCss, dialogCss) => { const offCanvasEditorCss = convertToOffCanvasCss(editorCss[0]); const offCanvasDialogCss = convertToOffCanvasCss(dialogCss[0]); const cssToInsert = `#drupal-off-canvas .cke_inner * {background: transparent;} ${offCanvasEditorCss} ${offCanvasDialogCss}`; insertCss(cssToInsert); // The localStorage key for accessing the cached CSS is based on the // paths of the CKEditor CSS files. This prevents localStorage from // providing outdated CSS. If new files are used due to using a new // skin, a new localStorage key is created. // // The CSS paths also include the cache-busting query string that is // stored in state and CKEDITOR.timestamp. This query string changes on // update and cache clear and prevents localStorage from providing // stale CKEditor CSS. // // Before adding the CSS rules to localStorage, there is a check that // confirms the cache-busting query (CKEDITOR.timestamp) is in the CSS // paths. This prevents localStorage from caching something unbustable. // // @see ckeditor_library_info_alter() if ( CKEDITOR.timestamp && editorCssPath.indexOf(CKEDITOR.timestamp) !== -1 && dialogCssPath.indexOf(CKEDITOR.timestamp) !== -1 ) { Object.keys(window.localStorage).forEach(key => { if (key.indexOf('Drupal.off-canvas.css.') === 0) { window.localStorage.removeItem(key); } }); window.localStorage.setItem( `Drupal.off-canvas.css.${editorCssPath}${dialogCssPath}`, cssToInsert, ); } }, ); }; addCkeditorOffCanvasCss(); })(jQuery, CKEDITOR);