Сбрасывается он потому, что startContainer - это дочерний текстовый узел элемента #editable-block. И когда вы перезаписываете innerHTML, вы этот startContainer из документа изымаете и восстанавливать позицию курсора просто некуда.
Есть простые, но ограниченные решения:
Они подразумевают, что у вас содержимым блока является только текст, без HTML.
UPD: Нет, есть же и нормальное универсальное решение:
const editableBlock = document.getElementById("editable-block");
editableBlock.addEventListener("input", function (event) {
// Запоминаем текущую позицию курсора
const selection = window.getSelection();
const {startContainer, startOffset} = selection.getRangeAt(0);
// Обновляем содержимое элемента
startContainer.textContent = startContainer.textContent.substr(0, startOffset) + "!" + startContainer.textContent.substr(startOffset);
// Восстанавливаем позицию курсора
const newRange = document.createRange();
newRange.setStart(startContainer, startOffset + 1);
newRange.setEnd(startContainer, startOffset + 1);
selection.removeAllRanges();
selection.addRange(newRange);
});
Тут мы редактируем текстовое содержимое именно того элемента, в котором в данный момент находится курсор (это по определению всегда текстовая нода). И тогда содержимое основного блока может быть любым.