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> <i>sonoris</i> <b>test </b><b style="font-style: italic;">this</b><i> feature</i><i style="text-decoration: underline;"> handling</i><b style="text-decoration: underline;"> ranges</b> 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();