JSFiddle - React, Tailwind, and code Playground
HTML
<button id="bold">B</button>
<button id="italic">I</button>
<div contentEditable="true">Lorem</div>
JavaScript
document.getElementById('bold').addEventListener('click', () => edit('STRONG'));
document.getElementById('italic').addEventListener('click', () => edit('EM'));
function edit(format) {
let parentElementOfSelectedText = document.getSelection().getRangeAt(0).commonAncestorContainer;
// If element is already formatted, undo the format
if (parentElementOfSelectedText.tagName === format || parentElementOfSelectedText.parentElement.tagName === format) {
if (parentElementOfSelectedText.tagName !== format) parentElementOfSelectedText = parentElementOfSelectedText.parentElement;
let grandParentOfSelectedText = parentElementOfSelectedText.parentElement;
if (parentElementOfSelectedText.textContent) {
const selectedText = document.createTextNode(document.getSelection().toString());
//work with range of old element because
//text nodes are pass by value
//and we cant create a range after its a text node
const range = document.createRange();
range.selectNode(parentElementOfSelectedText);
//this replaces some of your code but uses a range
range.deleteContents();
range.insertNode(selectedText);
grandParentOfSelectedText.normalize();
//select the range again :)
const selection = window.getSelection();
selection.removeAllRanges();
selection.addRange(range);
}
} else {
let selectedText = document.getSelection().getRangeAt(0);
const node = document.createElement(format);
const fragment = selectedText.extractContents();
if (fragment) {
node.appendChild(fragment);
}
selectedText.insertNode(node);
//make only the inside of the node a range
//so [...].commonAncestorContainer is "STRONG" or "EM"
//and gets recognized
const range = document.createRange();
range.selectNodeContents(node);
const selection = window.getSelection();
window.getSelection().removeAllRanges()
selection.addRange(range);
}
}