bustle/mobiledoc-kit

Suggestions for allowing content editors to center justify a tags content.

Closed this issue · 11 comments

Do you have any suggestions for how mobildoc could allow users to center text?

or

Is there any way text-align: center; can be placed on elements?

For that to be possible, there should be a way to add attributes to sections (<p>, <h1> and such). Adding something like a data-text-align attribute would be preferred over explicit styling as it would allow more flexibility over the styling for the user of mobiledoc-kit.

The bad news is that the Mobiledoc format does not allow attributes on sections, unlike markers.

Proposal

Right now a section looks like

[sectionTypeIdentifier, tagName, markers]

This would have to be extended to something like

[sectionTypeIdentifier, tagName, markers, optionalAttributesArray]

Indicating a piece of text to be centered would then look like:

onCenter() {
  // I'm not 100% sure how to implement `getMarkupSectionsInRange` in the current API
  const markupSections = getMarkupSectionsInRange(editor.range);

  markupSections.forEach((markupSection) => {
    // `setAttribute` does not exist right now. It would have to be added.
    markupSection.setAttribute('data-text-align', 'center');
  });
}

In your CSS you can then target those elements.

[data-text-align='center'] {
  text-align: center;
}

Bottom line

So the bottom line is, I don't think it's possible right now. However, I believe it won't be too complicated to extend the library to support this use case. The biggest thing would be to extend the Mobiledoc format while still maintaining backwards compatibility.

I have solved this for my users with the following ugly hack....

  toggleAlign: function (type) {
      if (!this.editor.hasCursor()) {
        return
      }

      let activeType = null
      let marker = this.editor.range.head.marker
      let parent = marker.parent
      let head = parent.headPosition()
      let tail = parent.tailPosition()
      let range = head.toRange(tail)
      this.editor.selectRange(range)

      let alignments = ['div-left', 'div-center', 'div-right', 'div-justify']
      for (let alignment of alignments) {
        if (this.activeMarkupTags.includes(alignment)) {
          activeType = alignment.split('-')[1]
        }
      }

      if (activeType != null) {
        this.editor.run((editor) => {
          editor.toggleMarkup('div')
        })
      }

      if (activeType !== type) {
        this.editor.run((editor) => {
          let markup = editor.builder.createMarkup('div', {style: `text-align: ${type};`})
          editor.toggleMarkup(markup)
        })
      }

      this.editor.selectRange(tail.toRange())
    }
  }

Which also required these modifications....

import * as tagNames from 'mobiledoc-dom-renderer/dist/commonjs/mobiledoc-dom-renderer/utils/tag-names'
import { VALID_ATTRIBUTES, VALID_MARKUP_TAGNAMES } from 'mobiledoc-kit/dist/commonjs/mobiledoc-kit/models/markup'

tagNames.isValidMarkerType = function () {
  return true
}

VALID_ATTRIBUTES.push('style')
VALID_ATTRIBUTES.push('target')
VALID_ATTRIBUTES.push('class')
VALID_ATTRIBUTES.push('alt')

VALID_MARKUP_TAGNAMES.push('small')
VALID_MARKUP_TAGNAMES.push('div')

I realize this totally breaks compatibility and portability of mobiledoc, but those were never my reasons for using it.

Thank you for the taking the time to respond.

Justifying text is a totally reasonable thing someone would want to do in a rich text editor and there should be a first-class way to do it.

Thanks for the hack @internalfx I will probably end up doing something similar.

Actually I've been trying this today and cannot for the life of me get the over-riding of isValidMarkerType to work in my environment.

I'm using ember.js and have tried:

import * as tagNames from 'mobiledoc-dom-renderer/utils/tag-names';

tagNames.isValidMarkerType = function() { return true; };

However it does not work. No errors, just doesn't render my <small> markups.

I've also tried:

import { isValidMarkerType } from 'mobiledoc-dom-renderer/utils/tag-names';

isValidMarkerType = function() { return true; };

In this case, I get a build error: "isValidMarkerType" is read-only

I would very much appreciate some help!

Don't forget to also set small as a valid tagname.

VALID_MARKUP_TAGNAMES.push('small')

I think @YoranBrondsema is basically on the right track. The proposal is:

  • Add attributes to sections.
  • Where the meaning of an attribute diverges from it's HTML implementation, let's namespace it with the prefix data-md-. For example the attribute of href on an a markup provides the link to be opened upon click. The attribute data-md-text-align on a p section would provide the text alignment. The HTML rendering implementation would be decoupled, but could be achieved with inline styles or by shipping a CSS stylesheet with the renderer. The editor itself already ships a stylesheet, so we would add style there for the editor.

Yeah @internalfx pushing 'small' to VALID_MARKUP_TAGNAMES works fine and it renders in the editor, but then it does not render with the dom renderer 😞
Whatever import * as foo from 'foo' does in your environment, it doesn't do the same thing in Ember with broccoli I guess

@sdhull I remember now...

I also had to use jsdom for the renderer.

You can find the code I use to render the mobiledoc here
https://git.internalfx.com/internalfx/pageflo/src/branch/master/system/services/componentRender.js

Dang I can't add jsdom as a dependency. Guess it's back to my fork of the renderer 😫
Thanks for replying @internalfx I appreciate it! I'm certain it will help someone else trying to do something similar.

@mixonic If a native way to handle this is ever implemented I will happily start using it.

I finally figured it out! I'll add it here for posterity (and any other ember.js devs who happen upon this issue):
For some reason, ember-mobiledoc-dom-renderer namespaces the mobiledoc-dom-renderer, so the following works to override the isValidMarkerType method:

import * as tagNames from 'ember-mobiledoc-dom-renderer/mobiledoc-dom-renderer/utils/tag-names';

tagNames.isValidMarkerType = function() { return true; }; // for renderer