CKEditor embedded media previews do not render with attached assets

Created on 2 August 2019, almost 5 years ago
Updated 27 April 2024, 2 months ago

Problem/Motivation

In #2994696: Render embedded media items in CKEditor β†’ , we added the ability for media items embedded in formatted text to be previewable in CKEditor. The previews are fully rendered media entities. The problem, though, is that if those media entities have attached asset libraries, those libraries are NOT included as part of the rendered preview.

This is totally intentional, for reasons that can be summed up as "overwhelming, unjustified complexity". As Wim Leers stated in #2994696-214: Render embedded media items in CKEditor β†’ :

We specifically do not want to load asset libraries associated with a rendered entity because A) that would also load JS, and we don't want previews to become interactive, they're just previews, B) a rendered media entity's hypothetical attached CSS (because there isn't any in Drupal core) would be written with the assumption that basic theme CSS is already present, which is not true in CKEditor iframe instances.

[...] Media entities rendered by Drupal don't have asset libraries attached by default. I explained above why we don't want this. I can expand on it more if you like. Loading attached CSS and JS β€” problematic as it is for the reasons I've outlined already β€” would require us to use the AJAX system. This would mean that requests would use the back-end theme instead of the front-end theme, thus loading both the wrong template (that of the admin theme) and the wrong asset libraries (those of the admin theme). It would also make the responses uncacheable (AJAX responses are uncacheable). If you want the nitty gritty background, see #2844822: The preview in CKEditor does not use the same Twig template as the one on the front end (default theme) β†’ . Specifically, see #2844822-41: The preview in CKEditor does not use the same Twig template as the one on the front end (default theme) β†’ , where I anticipated this would come up πŸ™‰. Entity Embed prior to 1.0 was using the AJAX system. One early user with a super advanced use case needed it. They acknowledged it would not work for everyone always. Many people ran into the problem of the wrong Twig template being used. By now 23% of all Drupal 8 Entity Embed installs is using 8.x-1.0 where no AJAX request is used, and hence no CSS or JS is loaded. There have been no complaints. (I just triaged the Entity Embed issue queue again to make sure.)

We agreed to open this issue as a place to further discuss this, if it's requested by enough people.

Proposed resolution

TBD

Remaining tasks

TBD

User interface changes

TBD

API changes

TBD

Data model changes

TBD

Release notes snippet

TBD

✨ Feature request
Status

Active

Version

11.0 πŸ”₯

Component
MediaΒ  β†’

Last updated about 9 hours ago

Created by

πŸ‡ΊπŸ‡ΈUnited States phenaproxima Massachusetts

Live updates comments and jobs are added and updated live.
Sign in to follow issues

Comments & Activities

Not all content is available!

It's likely this issue predates Contrib.social: some issue and comment data are missing.

  • πŸ‡ΊπŸ‡ΈUnited States KarlShea Minneapolis, πŸ‡ΊπŸ‡Έ

    It is actually insane that there's literally no way to tell if the media item is being rendered inside CKEditor.

    This does not work:

    function mymodule_support_preprocess_media(&$variables) {
      if (\Drupal::service('router.admin_context')->isAdminRoute()) {
        // Even inside the editor, the media item isn't in an admin route.
      }
    
      if (\Drupal::routeMatch()->getRouteName() === 'media.filter.preview') {
        // It's only a preview when you first embed it, but on edit it's just part of the content!
      }
    }
    

    This is the ridiculous thing I had to do in order to allow clicking on a media item so you can adjust display mode etc:

    function mymodule_support_preprocess_page(&$variables) {
      if (\Drupal::service('router.admin_context')->isAdminRoute()) {
        $variables['#attached']['library'][] = 'mymodule_support/ckeditor5_fixes';
      }
    }
    
    document.body.addEventListener('click', (e) => {
      // Prevent opening colorbox images in the current tab while in the editor.
      if (e.target.parentElement.classList.contains('colorbox')) {
        e.preventDefault();
      }
    });
    

    I can't imagine what other media embeds will need to go through to suppress behavior.

  • πŸ‡«πŸ‡·France mably

    In our special use case we add to load some JS after a custom web component was displayed as a media.

    We wrote a Javascript MutationObserver to detect the addition to the page of our custom element and load the related javascript library.

    Here is an quick and dirty piece of code for those interested:

    jQuery(document).ready(function() {
    	// Function to remove specific attribute
    	function initImageComparisonSlider(mutationsList, observer) {
    		mutationsList.forEach(mutation => {
    			// Check if nodes are added
    			if (mutation.type === 'childList') {
    				// Iterate through added nodes
    				mutation.addedNodes.forEach(node => {
    					// Check if the node is an element of type DIV
    					if ((node.nodeType === 1) && (node.tagName === "DIV")) {
    						var article = node.children[0];
    						// Check if the node is an element of type ARTICLE
    						if (article.tagName === "ARTICLE") {
    							var ics = article.children[1];
    							// Check if the node is an element of type IMG-COMPARISON-SLIDER
    							if (ics.tagName === "IMG-COMPARISON-SLIDER") {
    								once('ics-js-css', 'body').forEach(function (element) {
    									let scriptEle = document.createElement("script");
    									scriptEle.setAttribute("src", "https://cdn.jsdelivr.net/npm/img-comparison-slider@8/dist/index.js");
    									scriptEle.setAttribute("type", "text/javascript");
    									scriptEle.setAttribute("async", "async");
    									document.body.appendChild(scriptEle);
    								});
    							}
    						}
              }
    				});
    			}
    		});
    	}
    	
    	// Create a new MutationObserver
    	const observer = new MutationObserver(initImageComparisonSlider);
    	
    	// Define the target node to observe
    	const targetNode = document.body;
    	
    	// Configuration of the observer
    	const config = { childList: true, subtree: true };
    	
    	// Start observing the target node for configured mutations
    	observer.observe(targetNode, config);
    });

    We used Asset Injector for our test, script was activated only on node/*/edit pages.

Production build 0.69.0 2024