0

Edit: if self-closing tags arent "possible" in html I wonder how this tutorial does it with react: https://ckeditor.com/docs/ckeditor5/latest/framework/tutorials/using-react-in-a-widget.html


I've read through various threads including this where it is recommended to create a custom widget but it doesnt matter what I try, my custom html tag is never self closing. All of the examples below will cause CKE to turn <x-mycomponent /> into <x-mycomponent></x-mycomponent>. Since I am not using any module bundler it might be because of that, but since my plugin is recognized I doubt that.

I am using CKSource.Editor.builtinPlugins.push(BladeComponentPlugin); at the end of all examples since I am working without modules. Array.from( editor.ui.componentFactory.names() ) lists my plugin and console.logs from within my plugin also fire.

I've started with this:

function BladeComponentPlugin(editor) {
  editor.ui.componentFactory.add('bladeComponent', locale => {
    const viewTemplate = '<span class="blade-component">[x-mycomponent]</span>';

    const modelSchema = editor.model.schema;
    modelSchema.register('bladeComponent', {
      allowWhere: '$block',
      isSelfClosing: true
    });

    return {
      init() {
        const view = editor.editing.view;
        const model = editor.model;

        // Convert <x-mycomponent /> to 'bladeComponent' model
        model.schema.extend(view.elementToElement({
          model: 'bladeComponent',
          view: {
            name: 'span',
            classes: 'blade-component'
          }
        }));

        // Convert 'bladeComponent' model to <x-mycomponent />
        model.schema.extend(model.understand(viewTemplate));

        editor.editing.mapper.on(
          'viewToModelPosition',
          (evt, data) => {
            if (data.modelRange && data.modelRange.start) {
              const { start } = data.modelRange;

              if (start.nodeBefore && start.nodeBefore.name === 'span' && start.nodeBefore.hasClass('blade-component')) {
                data.modelRange.start = model.createPositionAt(start.nodeBefore, 1);
              }
            }
          }
        );
      },
      render() {
        ...
      }
    };
  });
}

CKSource.Editor.builtinPlugins.push(BladeComponentPlugin);

But self-closing wouldnt work. I've tried various things in the modelSchema.register options but no luck. I've then tried to somehow make the self closing happen in the data processing:

function BladeComponentPlugin(editor) {
  editor.ui.componentFactory.add('bladeComponent', locale => {
    const viewTemplate = '<span class="blade-component">[x-mycomponent /]</span>';

    editor.conversion.for('upcast').elementToElement({
      view: {
        name: 'span',
        classes: 'blade-component'
      },
      model: (viewElement, { writer }) => {
        const modelElement = writer.createElement('bladeComponent');
        return modelElement;
      }
    });

    editor.conversion.for('downcast').elementToElement({
      model: 'bladeComponent',
      view: (modelElement, { writer }) => {
        const viewElement = writer.createContainerElement('span', { class: 'blade-component' });
        return viewElement;
      }
    });

    editor.data.processor.htmlFilter.addRules({
      elements: {
        span: (element, { writer }) => {
          if (element.hasClass('blade-component')) {
            // Replace the self-closing syntax with the view template
            return writer.createText(viewTemplate);
          }
        }
      }
    });

    // Handle clicks on the component
    editor.editing.view.document.on('click', 'span.blade-component', evt => {
      const component = evt.target;

      // Open dialog or perform action on component click
      const componentName = 'x-mycomponent'; // Replace with the actual component name
      console.log('Clicked component:', componentName);
    });
  });
}

But still, self closing wouldnt work. Then I tried the block widget approach (I guess):

function BladeComponentPlugin(editor) {
  editor.ui.componentFactory.add('bladeComponent', locale => {
    editor.plugins.get('BlockToolbar').add({
      model: 'bladeComponent',
      view: 'span',
      label: 'Blade Component',
      icon: 'x-mycomponent'
    });

    editor.conversion.for('upcast').elementToElement({
      view: {
        name: 'span',
        classes: 'blade-component'
      },
      model: 'bladeComponent'
    });

    editor.conversion.for('dataDowncast').elementToElement({
      model: 'bladeComponent',
      view: (modelElement, { writer }) => {
        const span = writer.createContainerElement('span', { class: 'blade-component' });
        return span;
      }
    });

    editor.conversion.for('editingDowncast').elementToElement({
      model: 'bladeComponent',
      view: (modelElement, { writer }) => {
        const span = writer.createContainerElement('span', { class: 'blade-component' });
        return span;
      }
    });

    return {
      init() {
        editor.model.schema.extend('bladeComponent', { isObject: true, isBlock: true });

        editor.editing.mapper.on(
          'viewToModelPosition',
          evt => {
            const viewElement = evt.mapper.fromViewElement(evt.position.element);
            if (viewElement.hasClass('blade-component')) {
              evt.position.object = editor.model.document.getRoot();
              evt.position.path = [evt.position.object];
            }
          },
          { priority: 'high' }
        );

        // Handle clicks on the component
        editor.editing.view.document.on('click', 'span.blade-component', evt => {
          const component = evt.target;

          // Open dialog or perform action on component click
          const componentName = 'x-mycomponent'; // Replace with the actual component name
          console.log('Clicked component:', componentName);
        });
      }
    };
  });
}

And I've tried to somehow utilize the General HTML Support plugin:

function BladeComponentPlugin(editor) {
  editor.ui.componentFactory.add('bladeComponent', locale => {
    const viewTemplate = '<span class="blade-component">&lt;x-mycomponent /&gt;</span>';

    editor.data.processor.htmlFilter.addRules({
      elements: {
        span: (element, { writer }) => {
          if (element.hasClass('blade-component')) {
            // Replace the self-closing syntax with the view template
            return writer.createText(viewTemplate);
          }
        }
      }
    });

    editor.conversion.for('upcast').elementToElement({
      view: {
        name: 'span',
        classes: 'blade-component'
      },
      model: (viewElement, { writer }) => {
        const modelElement = writer.createElement('bladeComponent');
        return modelElement;
      }
    });

    editor.conversion.for('dataDowncast').elementToElement({
      model: 'bladeComponent',
      view: (modelElement, { writer }) => {
        const span = writer.createContainerElement('span', { class: 'blade-component' });
        return span;
      }
    });

    editor.conversion.for('editingDowncast').elementToElement({
      model: 'bladeComponent',
      view: (modelElement, { writer }) => {
        const span = writer.createContainerElement('span', { class: 'blade-component' });
        return span;
      }
    });

    // Handle clicks on the component
    editor.editing.view.document.on('click', 'span.blade-component', evt => {
      const component = evt.target;

      // Open dialog or perform action on component click
      const componentName = 'x-mycomponent'; // Replace with the actual component name
      console.log('Clicked component:', componentName);
    });
  });
}

// Initialize the plugin when CKEditor is ready
ClassicEditor.builtinPlugins.push(BladeComponentPlugin);

Still no luck. Before I try another editor I hope to somehow find a solution or answer why my self-closing approach doesnt work.

I noticed that whatever markup I type, like <x-foobar />, it would always add a separate closing tag. I dont know if that means my plugin doesnt work or load or whatever.

Alex
  • 9,911
  • 5
  • 33
  • 52
  • 1
    "if self-closing tags arent "possible" in html I wonder how this tutorial does it with react" — JSX isn't HTML. CKEditor generates HTML, not JSX. – Quentin Jun 30 '23 at 10:22

0 Answers0