[upstream] CKEditor 5 merges nested and adjacent <span>s

Created on 26 September 2023, about 1 year ago
Updated 9 April 2024, 8 months ago

Problem/Motivation

When we add two adjacent tags using HTML source editing mode (General HTML Support (“GHS”) feature)
ckeditor5 merges those spans together.

Steps to reproduce

Try following code
<span> <span>aaaa</span> <span>bbbb</span> </span>

1. Copy above code and paste into editor with source mode.
2. Turn off source editing mode.
3. Turn ON source editing again and see above html changes.

There are couple of upstream issues for this:

🐛 Bug report
Status

Postponed: needs info

Version

11.0 🔥

Component
CKEditor 5 

Last updated about 18 hours ago

Created by

🇮🇳India sahal_va

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.

  • Issue created by @sahal_va
  • Status changed to Postponed: needs info about 1 year ago
  • 🇧🇪Belgium wim leers Ghent 🇧🇪🇪🇺

    That is quite the exotic use case!

    What is the purpose of such markup?

  • 🇺🇸United States jvogt Seattle, WA

    I wouldn't call it exotic for us. We're trying to write a ckeditor5 plugin that outputs a themed "button". The HTML structure is as follows:

    <a href="https://example.com" class="btn btn-lg arrow purple">
      <span>Button text</span>
      <span class="arrow-box"><span class="arrow"></span></span>
    </a>
    

    ckeditor5 rewrites that to the following which doesn't work with the theme:

    <a href="https://example.com" class="btn btn-lg arrow purple">
      <span>Button text</span>
      <span class="arrow-box arrow"></span>
    </a>
    

    The theme CSS and HTML are provided by our institution and aren't Drupal-specific. We'd like to stick to the main theme as much as possible for the sake of maintenance and future compatibility, rather than reworking it to avoid nested spans. Also, from what I can tell, nesting a <span> in another <span> is considered valid HTML so it seems reasonable for ckeditor to allow it.

  • First commit to issue fork.
  • 🇺🇸United States maddentim

    I have a similar situation with a site now running Drupal 10.2.1 with ckeditor4 still. The blocker to going to ckeditor5 is that they have existing buttons like this:

    <p>
    	<span class="button-wrapper-two-col">
    		<span class="button-wrapper">
    			<a href="https://new-philanthropists.org" target="_blank">Learn About NGAAP</a>
    		</span>
    		<span class="button-wrapper">
    			<a href="/donate/new_generation_african_american_philanthropists">Join NGAAP</a>
    		</span>
    	</span>
    </p>
    

    the outer wrapper is just to make two buttons go side by side on desktop.
    When this code is opened in ckeditor 5, it is transformed into:

    <p>
      <a href="https://new-philanthropists.org" target="_blank">
    		<span class="button-wrapper-two-col button-wrapper">Learn About NGAAP</span>
    	</a>
    	<span class="button-wrapper-two-col"> </span>
    	<a href="/donate/new_generation_african_american_philanthropists">
    		<span class="button-wrapper-two-col button-wrapper">Join NGAAP</span>
    	</a>
    </p>
    

    This rearranging of the code breaks our button styling. I am open to changing our button CSS, but I don't see how I can make buttons with the current output. I tried turning off the "Correct faulty and chopped off HTML" to no avail.

    Recently, I have been looking for a way to alter the input sent to the editor such that we could do some regex magic to find the buttons and rewrite the code so it works. My only solution that I see so far would be to set up a hook_form_alter function, look for the form_ids we need, then look for the fields and rewrite them. We have about 25 ckeditor fields so that could become a pretty hairy function. Open to suggestions.

  • 🇧🇪Belgium wim leers Ghent 🇧🇪🇪🇺

    Related but different: 🐛 [upstream] CKEditor 5 moves inside tag Postponed .

  • 🇧🇪Belgium wim leers Ghent 🇧🇪🇪🇺

    https://github.com/ckeditor/ckeditor5/issues/15408 is the new issue to track apparently 🤷‍♂️

  • 🇺🇸United States maddentim

    Just circling back here. We could not wait for a real fix from upstream so we opted to develop an alter hook that uses DOMDocument() to restructure the HTML prior to sending it into CK5. While our hook is very specific to our quirky button structure, maybe someone can use parts to fix their own issues!

    /**
     * Alters the existing ckeditor content before loading to tranform the buttons
     * into a compatible structure for ckeditor5.
     *
     * @param array $element
     * @param \Drupal\Core\Form\FormStateInterface $form_state
     * @param array $context
     */
    function MYMODULE_field_widget_single_element_form_alter(&$element, FormStateInterface $form_state, $context) {
      $formats = ['full_html', 'full_html_w_o_correction', 'basic_html'];
      if (key_exists('#format', $element) && in_array($element['#format'],$formats)) {
        $element['#default_value'] = _transformHtml($element['#default_value']);
      }
    }
    
    function _transformHtml($htmlFragment) {
      $doc = new DOMDocument();
      libxml_use_internal_errors(true);
      $doc->loadHTML('<div>' . mb_convert_encoding($htmlFragment, 'HTML-ENTITIES', 'UTF-8') . '</div>', LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD);
      libxml_clear_errors();
    
      $dummyRoot = $doc->getElementsByTagName('div')->item(0);
    
      $xpath = new DOMXPath($doc);
    
      /** Transform two cols buttons */
      $buttonWrapperTwoCols = $xpath->query(".//span[contains(concat(' ', normalize-space(@class), ' '), ' button-wrapper-two-col ')]", $dummyRoot);
      foreach ($buttonWrapperTwoCols as $span) {
          $div = $doc->createElement('div');
          $div->setAttribute('class', $span->getAttribute('class'));
    
          foreach ($span->getElementsByTagName('span') as $innerSpan) {
              foreach ($innerSpan->getElementsByTagName('a') as $a) {
                  $newA = $a->cloneNode(true);
                  $newA->setAttribute('class', 'btn');
                  $div->appendChild($newA);
              }
          }
    
          $span->parentNode->replaceChild($div, $span);
      }
    
      /** Transform standalone buttons */
      // De-nest any spans
      $nestedButtonWrappers = $xpath->query(".//span[contains(concat(' ', normalize-space(@class), ' '), ' button-wrapper ')]//span[contains(concat(' ', normalize-space(@class), ' '), ' button-wrapper-two-col ')]");
      foreach ($nestedButtonWrappers as $nestedSpan) {
        $parentSpan = $nestedSpan->parentNode;
        if ($parentSpan && $nestedSpan !== $parentSpan) {
          while ($nestedSpan->firstChild) {
            $child = $nestedSpan->firstChild;
            if ($child->nodeName === 'a') {
                $parentSpan->insertBefore($child, $nestedSpan);
            } else {
                $nestedSpan->removeChild($child);
            }
          }
          $parentSpan->removeChild($nestedSpan);
        }
      }
    
      // Process all 'button-wrapper' spans for transformation
      $buttonWrappers = $xpath->query(".//span[contains(concat(' ', normalize-space(@class), ' '), ' button-wrapper ')]", $dummyRoot);
      foreach ($buttonWrappers as $span) {
        if ($span->parentNode) {
          $aElements = iterator_to_array($span->getElementsByTagName('a'));
          foreach ($aElements as $a) {
            $newA = $a->cloneNode(true);
            $newA->setAttribute('class', 'btn');
    
            if (!empty($span->parentNode)) {
              $span->parentNode->replaceChild($newA, $span);
            }
            else {
              $dummyRoot->appendChild($newA);
            }
          }
        }
      }
    
      $transformedHtml = '';
      foreach ($dummyRoot->childNodes as $child) {
        $transformedHtml .= $doc->saveHTML($child);
      }
    
      return $transformedHtml;
    }
    
    
  • 🇧🇪Belgium wim leers Ghent 🇧🇪🇪🇺

    Thanks, @maddentim! I do hope it'll help somebody else in the short term :)

  • 🇺🇸United States marianojp

    Nested span tag should be allowed. I'm also having this issue.

  • 🇺🇸United States marianojp

    Hi All, I found a temporary solution. I created another text format - /admin/config/content/formats. I did not use ckeditor 5.

Production build 0.71.5 2024