JSFiddle - React, Tailwind, and code Playground

by Felis Catus

HTML

<div class="menu">
  file edit view tools help
  <span class='textmode' data-command='bold' data-enabled>B</span>
  <span class='textmode' data-command='italic' data-enabled>I</span>
  <span class='textmode' data-command='underline' data-enabled>U</span>
  <span class='textmode' data-command='strikethrough' data-enabled>S</span>
</div>
<div class="workarea">
  <div class="textarea">
    <div class="gutter">

    </div>
    <div class="editor" contenteditable>
    lorem ipsum dolor sit <b>amet</b>&nbsp;<i>sonoris</i>&nbsp;<b>test </b><b style="font-style: italic;">this</b><i>&nbsp;feature</i><i style="text-decoration: underline;">&nbsp;handling</i><b style="text-decoration: underline;">&nbsp;ranges</b>&nbsp;like that<div>what did it insert, i wonder? a div?</div><div>and another one. let's see if this very very long line of text works. let's <b><i>see if this very very</i></b> long line of text works. let's see if this very very long line of text works. aha, it does as a single div.</div><div>enough, folks. Also interesting <b>how it <i>handles</i> ran<u>ges</u></b><u> po</u>st-factum</div>
    </div>
    <div class="minimap">
    <div class="content">
    
    </div>
    </div>
  </div>
</div>

SCSS

div {
  box-shadow: 0 0 5px -2px red inset;
}
body {
  min-height: 100vh;
  height: 100%;
  margin: 0;
  padding: 0;
}
.menu {
  height: 40px;
  line-height: 40px;
}
.workarea {
  position: relative;
  .textarea {
    height: calc(100vh - 40px);
    box-sizing: border-box;
    position: relative;
    padding: 10px 200px 10px 30px;
    .gutter {
      position: absolute;
      height: 100%;
      width: 30px;
      top:0;
      left:0;
    }
    .minimap {
      position: absolute;
      height: 100%;
      width: 200px;
      top:0;
      right:0;
    }
  }
}
span.textmode {
  padding: 4px;
  border: 1px solid black;
  margin: 1px;
  &[data-command='bold'] { font-weight: bold; }
  &[data-command='italic'] { font-style: italic; }
  &[data-command='underline'] { text-decoration: underline; }
  &[data-command='strikethrough'] { text-decoration: line-through; }
  &[data-enabled='true'] { text-shadow: 0 0 2px red; color: brown; }
}
.minimap .content {
  transform: scale(0.2);
  width: calc(100vw - 230px);
  transform-origin: 0 0;
  left: 15px;
}

JavaScript

function update_modes(){
	$('.textmode').each(function(idx, elem) {
  	var $elem = $(elem);
    $elem.attr('data-enabled', (document.queryCommandState($elem.attr('data-command'))?'true':'false'));
  });
}
function update_minimap(){
	$('.content').html($('.editor').html());
}
$('.textmode').mousedown(function(evt){
  evt.preventDefault();
	var $tgt = $(evt.target);
  var cmd = $tgt.attr('data-command');
  console.log(cmd);
  document.execCommand(cmd);
  update_modes();
  return false;
});
$('.editor').on('input', function(){
	update_minimap();
})
document.addEventListener('selectionchange', function(){setTimeout(update_modes(), 100);});
update_minimap();