reorg quickedit block styles, to support <p> in <div>s and declarative styles
authorFrédéric Péters <fpeters@0d.be>
Sat, 6 Jun 2020 09:09:59 +0000 (11:09 +0200)
committerFrédéric Péters <fpeters@0d.be>
Sat, 6 Jun 2020 10:46:35 +0000 (12:46 +0200)
chloro/phyll/static/css/style.scss
chloro/phyll/static/js/chloro.js

index 473c22f..27d211c 100644 (file)
@@ -134,6 +134,13 @@ main {
                margin-top: 3em;
                color: lighten($text-color, 40%);
        }
+       div.note {
+               background: #fbf7c1;;
+               padding: 0.2em 0.5em 0.2em 2em;
+               p {
+                       margin: 0.5em 0;
+               }
+       }
 }
 
 .post-list {
@@ -191,7 +198,8 @@ div[contenteditable=true]:focus-within {
        }
 }
 
-.style-popup {
+.inline-style-popup,
+.block-style-popup {
        background: white;
        box-shadow: 0 0 5px #666;
        input {
@@ -207,7 +215,7 @@ div[contenteditable=true]:focus-within {
                }
        }
        button {
-               padding: 0;
+               padding: 0 0.5em;
                height: 2em;
                text-align: center;
                background: #eee;
@@ -224,8 +232,17 @@ div[contenteditable=true]:focus-within {
                        color: white;
                }
        }
-       &.short button {
+       &.inline-style-popup button {
                width: 2em;
+               padding: 0;
+       }
+       &.block-style-popup {
+               &.selected button {
+                       display: none;
+                       &.on {
+                               display: block;
+                       }
+               }
        }
 }
 
index c47bb0a..4b0ffea 100644 (file)
@@ -1,4 +1,24 @@
 $(function() {
+  var BLOCKS = [
+          {name: 'code', tag: 'PRE', klass: 'screen'},
+          {name: 'figure', tag: 'DIV', subtag: true, klass: 'figure'},
+          {name: 'note', tag: 'DIV', subtag: true, klass: 'note'},
+  ];
+  function get_contenteditable_subnode(node) {
+    if (node === null) return null;
+    if (node.parentNode.contentEditable === 'true') return node;
+    return get_contenteditable_subnode(node.parentNode);
+  }
+  function get_active_block(node) {
+    var main_node = get_contenteditable_subnode(node);
+    if (main_node === null) return null;
+    for (const block of BLOCKS) {
+      if (main_node.tagName === block.tag && main_node.classList.contains(block.klass))
+        return block;
+    }
+    return null;
+  }
+
   $('div[contenteditable]').on('input', function(event) {
     if (event.originalEvent.inputType == "insertParagraph") {
       var sel = document.getSelection();
@@ -18,19 +38,7 @@ $(function() {
     }
     return true;
   });
-  $('div[contenteditable]').on('keyup', function(event) {
-    var sel = document.getSelection();
-    if ((sel.anchorNode instanceof Element && (
-            (sel.anchorOffset == 0 && sel.isCollapsed) || // first line
-            (sel.anchorNode.tagName === 'PRE'))) ||
-        (sel.anchorNode.parentNode.tagName === 'PRE')) {
-      // start of line
-      show_block_style_popup();
-    } else {
-      hide_block_style_popup();
-    }
-    return true;
-  });
+  $('div[contenteditable]').on('keyup click', update_block_style_popup);
 
   $('#save').on('click', function() {
     var text = $('div[contenteditable]')[0].innerHTML;
@@ -45,56 +53,72 @@ $(function() {
 
   var block_style_popup = null;
   function block_style() {
-    var action = $(this).data('action');
-    var class_name = null;
-    if (action == 'code') {
-      action = 'pre';
-      class_name = 'screen';
-      if ($(this).hasClass('on')) {  // toggle off
-        action = 'p';
-        class_name = null;
-      }
-    }
-    document.execCommand('formatBlock', false, action);
     var sel = window.getSelection();
-    if (class_name) {
-      $(sel.anchorNode).addClass(class_name);
+    var current_anchor = sel.anchorNode;
+    if (this.classList.contains('on')) { // toggle off
+      if (this.action_block.subtag) {
+        // unwrap
+        var main_node = get_contenteditable_subnode(current_anchor);
+        $(current_anchor).detach().insertAfter(main_node);
+      } else {
+        document.execCommand('formatBlock', false, 'p');
+        current_anchor = sel.anchorNode;
+      }
+    } else {
+      action = this.action_block.subtag || this.action_block.tag;
+      if (this.action_block.subtag) {
+        // enclose current tag into a new parent;
+        var new_parent = document.createElement(this.action_block.tag);
+        new_parent.className = this.action_block.klass;
+        $(current_anchor).wrap(new_parent);
+      } else {
+        document.execCommand('formatBlock', false, this.action_block.tag);
+        sel.anchorNode.className = this.action_block.klass;
+        current_anchor = sel.anchorNode;
+      }
     }
     var range = document.createRange();
-    range.setStart(sel.anchorNode, 0);
+    range.setStart(current_anchor, 0);
     sel.removeAllRanges();
     sel.addRange(range);
+    update_block_style_popup();
   }
-  function show_block_style_popup() {
+  function update_block_style_popup() {
+    var sel = window.getSelection();
+    if (! ((sel.anchorNode instanceof Element && (sel.anchorOffset == 0 && sel.isCollapsed)) || get_active_block(sel.anchorNode))) {
+      if (block_style_popup) {
+        $(block_style_popup).hide();
+      }
+      return true;
+    }
     if (block_style_popup === null) {
-      block_style_popup = $(
-                      '<div class="style-popup">' +
-                      '<button data-action="code">code</button>' +
-                      '</div>');
+      block_style_popup = $('<div class="block-style-popup"></div>');
+      for (const block of BLOCKS) {
+        var button = document.createElement('button');
+        button.action_block = block;
+        button.dataset.action = block.name;
+        button.textContent = block.name;
+        block_style_popup.append(button);
+      }
       block_style_popup.hide();
-      block_style_popup.insertAfter($('.actions'));
+      block_style_popup.insertAfter(document.body);
       block_style_popup.find('button').on('click', block_style);
     }
     block_style_popup.css('position', 'absolute');
-    var sel = window.getSelection();
-    var anchor = sel.anchorNode;
-    if (anchor instanceof Text) {
-      anchor = anchor.parentNode;
-    }
-    if (anchor.tagName === "PRE") {
-      block_style_popup.find('[data-action=code]').addClass('on');
+    var block = get_active_block(sel.anchorNode);
+    block_style_popup.find('button').removeClass('on');
+    if (block) {
+      block_style_popup.find('[data-action=' + block.name + ']').addClass('on');
+      block_style_popup.addClass('selected');
     } else {
-      block_style_popup.find('[data-action=code]').removeClass('on');
+      block_style_popup.removeClass('selected');
     }
+    var anchor = get_contenteditable_subnode(sel.anchorNode);
     var pos = $(anchor).offset();
     block_style_popup.css('top', pos.top - 33);
     block_style_popup.css('left', pos.left);
     block_style_popup.show();
-  }
-  function hide_block_style_popup() {
-    if (block_style_popup) {
-      $(block_style_popup).hide();
-    }
+    return true;
   }
 
   var style_popup = null;
@@ -149,7 +173,7 @@ $(function() {
 
   function show_style_popup(sel) {
     if (style_popup === null) {
-      style_popup = $('<div class="style-popup short">' +
+      style_popup = $('<div class="inline-style-popup">' +
                       '<button data-action="italic"><i>i</i></button>' +
                       '<button data-action="bold"><b>b</b></button>' +
                       '<button data-action="code">#</button>' +