Search code examples
apostrophe-cms

apostrophe-rich-text anchor name and adding attributes to elements in CKEditor programmatically


My Problem is that and anchor in apostrophe-rich-text produces a not working markup in html.

I have the following setup for my apostrophe-rich-text:

'apostrophe-rich-text': {
    toolbar: [
        'Styles',
        'Bold',
        'Italic',
        'Blockquote',
        'BulletedList',
        'Link',
        'Anchor',
        'Table'
  ],
    styles: [
        { name: 'Default', element: 'p' }
  ]
}

I'm using more styles in general but didn't wanted to list them all here. Everything works like expected except Anchor.

Anchor produces this this in html.

<p>Lorem ipsum dolor <a name="section-one">incididunt</a> qui officia deserunt</p>

Which obviously can not work. Did I made something wrong or is this a bug?

There is also the possibility to select "Link" and choose "Anchor" in the dropdown menu for "Link Type", unfortunately my anchors are not recognized, I only get the message "(No anchors present in the document)".

I use a dropdwon menu for on page navigation the anchors are definitely there. You can see it here.

Additionally i want to add a class to all a elements and for performance and safety reasons rel="noopener noreferrer" to all external links.


Solution

  • I was able to modify the CKEDITOR instance with a really good solution. With this you can programmatically add just about anything to any element.

    As described in the Global CKeditor configuration section I have added the following:

    // lib/modules/apostrophe-areas/public/js/user.js
    
    apos.define('apostrophe-areas', {
      construct: function(self, options) {
        // Use the super pattern - don't forget to call the original method
        var superEnableCkeditor = self.enableCkeditor;
        self.enableCkeditor = function() {
          superEnableCkeditor();
          // Now do as we please
          CKEDITOR.on('instanceReady', function(ev) {
            var editor = ev.editor;
            editor.dataProcessor.htmlFilter.addRules({
              elements : {
                // Add attributes to a
                a : function( element ) {
                  // Add accent-color class to all links
                  if ( !element.attributes.class ){
                    element.attributes.class = 'accent-color';
                  }
                  // Add _blank and noopener and noreferrer to external links
                  if ( !element.attributes.rel ){
                    // Get content's a href values
                    var url = element.attributes.href;
                    // Extract host names from URLs (IE safe)
                    var parser = document.createElement('a');
                    parser.href = url;
                    // Set additional attributes
                    var hostname = parser.hostname;
                    if ( hostname !== window.location.host) {
                        element.attributes.rel = 'noopener noreferrer';
                        element.attributes.target = '_blank';
                    }
                  }
                }
              }
            });
          })
        };
      }
    });
    

    For the rel attributes of course i had to allow it with the allowedAttributes method:

    // lib/modules/apostrophe-rich-text-widgets/index.js
    
    // Changing the allowed HTML tags in rich text widgets
    module.exports = {
      sanitizeHtml: {
        allowedTags: [
          'h2', 'h3', 'h4', 'p', 'a', 'ul', 'ol', 'li', 'strong', 'em', 'blockquote', 'iframe'
        ],
        allowedAttributes: {
          '*': ['style', 'class'],
          a: [ 'style', 'name', 'href', 'target', 'rel', 'class' ]
        },
        allowedSchemes: ['http', 'https', 'ftp', 'mailto', 'tel']
      }
    };
    
    

    That adds a lot of flexibility to the CKEditor used in Apostrophe and can be very useful for various situations where attributes should be programmatically added to elements.