oodeveloper / editorjs-multicolumn

A test for EditorJS multi column layout
13 stars 4 forks source link

[question] EditorJS core modifications #1

Closed gatanaso closed 3 years ago

gatanaso commented 4 years ago

Hi @oodeveloper,

First of all, this is really nice work and the demo looks great!

I noticed that you are using a modified version of EditorJS in order to provide the multicolumn functionality as part of a Tune.

Would it be possible for you to share those modifications? I would be very interested to know and possibly experiment with this approach as well.

Thank you, Goran

KasperBindslev commented 4 years ago

Hi @oodeveloper,

I second that. Great work. I'm also very interested in the modifications. Are you working on a usable plugin?

All best, Kasper

kjohnson commented 3 years ago

I was able to re-create this functionality based on the example Layout Tune and the tutorial documentation using a forked copy of the Paragraph Tool.

Caveats:

Also, I'm thinking that I can extract this as a decorator so that it can be applied to any tool, as needed.

Here is a run down:

In the `constructor`: - Initialize the `column` property on `this.data`. - Add `this.settings` to include a `column` tune. In the `save` method: - Copy the saved data to `this.data` to preserve `column` property (and any other tune property). In a new method, `renderSettings`: - Create a wrapper element. - Append tune settings to wrapper. - Add a `click` event listener to toggle the tune. In a new method, `_acceptTuneView`: - Toggle tune related classes on the `ce-block` container, which is the top level of each block. In a new method, `_toggleTune`: - Update the tune's property value `this.data`. - Call `_acceptTuneView`.

... and the CSS:

.codex-editor__redactor {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
}
.ce-block {
  flex: 0 0 100%; /* Set full width for non "column-ed" blocks. */
}
.ce-block.column {
  flex: 1;
  max-width: 325px; /* Set max-width to half of origional value so that combined width aligns */
}
.ce-block.column:not(:first-child) {
  margin-left: 10px; /* Add a gutter between columns */
}

... and here is the resulting diff:

diff --git a/src/index.js b/src/index.js
index 1de3e62..1000ae0 100644
--- a/src/index.js
+++ b/src/index.js
@@ -66,6 +66,14 @@ class Paragraph {
     this._preserveBlank = config.preserveBlank !== undefined ? config.preserveBlank : false;

     this.data = data;
+    this.data.column = data.column !== undefined ? data.column : false
+
+    this.settings = [
+      {
+        name: 'column',
+        icon: `<svg width="17" height="10" viewBox="0 0 17 10" xmlns="http://www.w3.org/2000/svg"><path d="M13.568 5.925H4.056l1.703 1.703a1.125 1.125 0 0 1-1.59 1.591L.962 6.014A1.069 1.069 0 0 1 .588 4.26L4.38.469a1.069 1.069 0 0 1 1.512 1.511L4.084 3.787h9.606l-1.85-1.85a1.069 1.069 0 1 1 1.512-1.51l3.792 3.791a1.069 1.069 0 0 1-.475 1.788L13.514 9.16a1.125 1.125 0 0 1-1.59-1.591l1.644-1.644z"/></svg>`
+      }
+    ];
   }

   /**
@@ -152,9 +160,9 @@ class Paragraph {
    * @public
    */
   save(toolsContent) {
-    return {
+    return Object.assign(this.data, {
       text: toolsContent.innerHTML
-    };
+    });
   }

   /**
@@ -250,6 +258,46 @@ class Paragraph {
       title: 'Text'
     };
   }
+
+  renderSettings(){
+    const wrapper = document.createElement('div');
+
+    this.settings.forEach( tune => {
+      let button = document.createElement('div');
+
+      button.classList.add('cdx-settings-button');
+      button.classList.toggle('cdx-settings-button--active', this.data[tune.name]);
+      button.innerHTML = tune.icon;
+      wrapper.appendChild(button);
+
+      button.addEventListener('click', () => {
+        this._toggleTune(tune.name);
+        button.classList.toggle('cdx-settings-button--active');
+      });
+    });
+
+    return wrapper;
+  }
+
+  /**
+   * @private
+   * Click on the Settings Button
+   * @param {string} tune — tune name from this.settings
+   */
+  _toggleTune(tune) {
+    this.data[tune] = !this.data[tune];
+    this._acceptTuneView();
+  }
+
+  /**
+   * Add specified class corresponds with activated tunes
+   * @private
+   */
+  _acceptTuneView() {
+    this.settings.forEach( tune => {
+        this._element.parentElement.parentElement.classList.toggle(tune.name, !!this.data[tune.name]);
+    });
+  }
 }

 module.exports = Paragraph;
oodeveloper commented 3 years ago

@gatanaso @kjohnson Hey mann, sure, Actually I totally forget about this account :|, so sorry for late reply I uploaded the block tune code and icons: blockTuneLayout

the main issue is to get the data or initial data! I couldn't find any good solution or any API method for saving the data. I was working on an startup which EditorJS is part of it so I moved on for now by adding necessary functions to each tool unfortunately: here is the code if you need it. I will work for a better solution after, or at least fix the flexibility by UX like switching classes on moveUp/Down. I'll be happy and grateful if you could make it better :smile: for each tool FUNCTIONS TO GET COLUMN AND PADDING CLASS

_getCol(){
    let col = 12;
    let className = 'col-12';
    let elmBlock = this._element.parentNode.parentNode;
    const colClass = new RegExp(/\bcol-.+?\b/, 'g');
    if (elmBlock.className.match(colClass)) {
      elmBlock.classList.forEach( cn => {
        if(cn.match(colClass)){
          className = cn;
        }
      });
        let parts = className.split('-');
        col = parseInt(parts[1]);
    }
    return col;
  }

  _getPadding(direction = 'l'){
    let padding = 0;
    let className = 'p'+direction+'-0';
    let elmBlock = this._element.parentNode.parentNode;
    const plClass = new RegExp(/\pl-.+?\b/, 'g');
    const prClass = new RegExp(/\pr-.+?\b/, 'g');
    const ptClass = new RegExp(/\pt-.+?\b/, 'g');
    const pbClass = new RegExp(/\pb-.+?\b/, 'g');
    if(direction == 'l'){
      if (elmBlock.className.match(plClass)) {
        elmBlock.classList.forEach( cn => {
          if(cn.match(plClass)){
            className = cn;
          }
        });
          let parts = className.split('-');
          padding = parseInt(parts[1]);
      }
    } else if(direction == 'r'){
      if (elmBlock.className.match(prClass)) {
        elmBlock.classList.forEach( cn => {
          if(cn.match(prClass)){
            className = cn;
          }
        });
          let parts = className.split('-');
          padding = parseInt(parts[1]);
      }
    } else if(direction == 't'){
      if (elmBlock.className.match(ptClass)) {
        elmBlock.classList.forEach( cn => {
          if(cn.match(ptClass)){
            className = cn;
          }
        });
          let parts = className.split('-');
          padding = parseInt(parts[1]);
      }
    } else if(direction == 'b'){
      if (elmBlock.className.match(pbClass)) {
        elmBlock.classList.forEach( cn => {
          if(cn.match(pbClass)){
            className = cn;
          }
        });
          let parts = className.split('-');
          padding = parseInt(parts[1]);
      }
    }

    return padding;
  }

then depending on the tool methods you need to get initial data [usually on constructor] and save data method which can be little different on each tool [mostly the same though]

I used paragraph tool as an example setting initial data on load

constructor({data, config, api, readOnly}) {
.....
this.data = data;
this.col = data.col ? data.col : '12';
    this.pt = data.pt ? data.pt : '0';
    this.pr = data.pr ? data.pr : '0';
    this.pb = data.pb ? data.pb : '0';
    this.pl = data.pl ? data.pl : '0';
}

saving the data

....
save(toolsContent) {
    return {
        text: toolsContent.innerHTML,
        col:this._getCol(),
        pt:this._getPadding('t'),
        pr:this._getPadding('r'),
        pb:this._getPadding('b'),
        pl:this._getPadding('l')
    };
  }
....