GetmeUK / ContentTools

A JS library for building WYSIWYG editors for HTML content.
http://getcontenttools.com
MIT License
3.95k stars 395 forks source link

Adding new tool - an element with nested tags (e.g. <pre><code></code></pre>) #447

Open wasinator opened 6 years ago

wasinator commented 6 years ago

What is the right way to go about adding a new tool - an element composed of 2 elements; one nested inside the other. For example, <pre><blockquote></blockquote></pre>, <pre><code></code></pre>.

Will fixture be a suitable choice for this case? Like, making the preformat element a fixture?

anthonyjb commented 6 years ago

Hi @wasinator, so I'd recommend you take a look at the code for the recent image fixture element here: https://github.com/GetmeUK/ContentEdit/blob/master/src/scripts/images.coffee#L170

This is an element that supports a structured set of HTML (in this case <div><img></div>), and (despite the perhaps poor name) can be used as either a fixture or as a standard element (though it's typically used as a fixture).

Beyond that you may actually want to take a look at [ContentFlow]() and snippets, which perhaps will give you the flexibility you want here straight out of the box, here's a short video covering my work on ContentFlow (https://youtu.be/HQaNrI3yJwo) that gives an insight into how it works, you might find what you really wont here is a content flow snippet.

wasinator commented 6 years ago

Thank you very much for your advice and for sharing your work. I will try it. Thanks again. :-)

wasinator commented 6 years ago

I have look into the code of the image fixture as you suggested. So I changed a approach a bit and tried to imitate the image fixture by using the pre element and mounting it to a blockquote element on the DOM. However, when I commit the change the dom won't be replace by a blockquote element even though the mount method has run. I can't figure out what was missing. Here are my code ... First, I create a new class named PreQuote by inheriting from the Pretext class. ` ContentEdit.PreQuote = (function(_super) { __extends(PreQuote, _super);

function PreQuote(tagName, attributes, content) {
  if (content instanceof HTMLString.String) {
    this.content = content;
  } else {
    this.content = new HTMLString.String(content, true);
  }
  ContentEdit.Element.call(this, tagName, attributes);
}

PreQuote.prototype.cssTypeName = function() {
  return 'pre-quote';
};

PreQuote.prototype.type = function() {
  return 'PreQuote';
};

PreQuote.prototype.typeName = function() {
  return 'PreQuote';
};

PreQuote.prototype.mount = function() {
  var quoteTag, text,
      content = this.content.text(),
      classes = '';

  text = document.createTextNode(content); 
  quoteTag = document.createElement("blockquote");
  quoteTag.appendChild(text);

  this._domElement = quoteTag;

  if (this._attributes['class']) {
    classes += ' ' + this._attributes['class'];
  }
  this._domElement.setAttribute('class', classes);
  console.log(this._domElement);

  return PreQuote.__super__.mount.call(this);
};

PreQuote.prototype.unmount = function() {
  var domElement, wrapper;
  if (this.isFixed()) {
    wrapper = document.createElement('div');
    wrapper.innerHTML = this.html();
    domElement = wrapper.querySelector('pre');
    this._domElement.parentNode.replaceChild(domElement, this._domElement);
    this._domElement = domElement;
  }
  return PreQuote.__super__.unmount.call(this);
};

return PreQuote;

})(ContentEdit.PreText);

ContentEdit.TagNames.get().register(ContentEdit.PreQuote, 'pre'); Then, I create a new tool named Quote that adds a Prequote element ContentTools.Tools.Quote = (function(_super) { __extends(Quote, _super);

function Quote() {
  return Quote.__super__.constructor.apply(this, arguments);
}

ContentTools.ToolShelf.stow(Quote, 'quote');

Quote.label = 'Quote';

Quote.icon = 'quote';

Quote.tagName = 'pre';

Quote.canApply = function(element, selection) {
  if (element.hasCSSClass('ce-edit-only')) return false; 
  if (element.isFixed()) {
    return false;
  }
  return element.content !== void 0 && ['p', 'pre'].indexOf(element.tagName()) !== -1;
};

Quote.isApplied = function(element, selection) {
  if (!element.content) {
    return false;
  }
  if (['Text', 'PreText'].indexOf(element.type()) === -1) {
    return false;
  }
  return element.tagName() === this.tagName;
};

Quote.apply = function(element, selection, callback) {
  var toolDetail = {
    'tool': this,
    'element': element,
    'selection': selection
  };
  var insertAt, text, preText, parent;

  if (!this.dispatchEditorEvent('tool-apply', toolDetail)) {
    return; }

  if (element.type() === 'PreText') {
    ContentTools.Tools.Paragraph.apply(element, selection, callback);
    return; }

  text = element.content.text();
  preText = new ContentEdit.PreQuote('pre', { 'class': 'quote' }, HTMLString.String.encode(text));
  parent = element.parent();
  insertAt = parent.children.indexOf(element);
  parent.detach(element);
  parent.attach(preText, insertAt);
  element.blur();
  preText.focus();
  preText.selection(selection);

  // console.log(toolDetail);
  callback(true);
  return this.dispatchEditorEvent('tool-applied', toolDetail);
};

return Quote;

})(ContentTools.Tool); ` NOTE: I also disable the default PrFormatted tool when I try running the code. The new tool works fine, but as I mentioned the element won't mount as I expected it to when I commit the change.