#javascript #html #selection
Вопрос:
Я пытался создать несколько кнопок, которые будут добавлять символы уценки при нажатии на них, поэтому я использовал Selection API
, чтобы получить выделенный текст и добавить символы, а затем вернуться caret
в конечную позицию выделенного текста, и все сделано правильно.
Когда я пытаюсь нажать кнопку отменитьCtrl z, это не возвращается к последнему тексту перед добавлением символов уценки, я знаю, что это потому, что я изменил значение текстового узла.
Есть ли способ сделать это без изменения текста узла и применить отмену, повтор?
let text = document.getElementById('test'),
btn = document.getElementById('btn')
//function to replace text by index
String.prototype.replaceAt = function(start, end, replacement) {
let text = '';
for (let i = 0; i < this.length; i ) {
if (i >= start amp;amp; i < end) {
text = ''
if (i === end - 1) {
text = replacement
}
} else {
text = this[i]
}
}
return text
}
function addNewStr(callback) {
var sel = window.getSelection()
try {
var range = sel.getRangeAt(0),
r = document.createRange()
//check if there's text is selected
if (!sel.isCollapsed) {
let startPos = sel.anchorOffset,
endPos = sel.focusOffset,
node = sel.anchorNode,
value = sel.anchorNode.nodeValue,
selectedText = value.substring(startPos, endPos),
parent = node.parentNode
//function to determine if selection start from left to right or right to left
function checkPos(callback) {
if (startPos > endPos) {
return callback(startPos, endPos)
} else {
return callback(endPos, startPos)
}
}
if (typeof callback === 'function') {
//getting the new str from the callback
var replacement = callback(selectedText),
caretIndex;
//apply changes
node.nodeValue = checkPos(function(end, start) {
return node.nodeValue.replaceAt(start, end, replacement)
})
//check if the returned text from the callback is less or bigger than selected text to move caret to the end of selected text
if (replacement.length > selectedText.length) {
caretIndex = checkPos(function(pos) {
return pos (replacement.length - selectedText.length);
})
} else if (selectedText.length > replacement.length) {
caretIndex = checkPos(function(pos) {
return (pos - selectedText.length) (replacement.length);
})
}
//back caret to the end of the new position
r.setStart(parent.childNodes[0], caretIndex)
r.collapse(true)
sel.removeAllRanges()
sel.addRange(r)
}
}
} catch (err) {
console.log("Nothing is selected")
}
}
btn.addEventListener("click", function() {
addNewStr(function(str) {
return '__' str '__'
})
})
<div contenteditable="true" id="test" placeholder="insertText">
try to select me
</div>
<button id="btn">to strong</button>
Ответ №1:
Поскольку вы изменяете содержимое программно, вам придется отменить его программно.
В вашем обработчике нажатия кнопки:
- зафиксируйте существующее состояние содержимого, прежде чем изменять его
- создайте функцию, которая возвращает его в это состояние.
- поместите эту функцию в массив («стек отмены»).
const undoStack = [];
function onButtonClick (e) {
// capture the existing state
const textContent = text.innerText;
// create a function to set the div content to its current value
const undoFn = () => text.innerText = textContent;
// push the undo function into the array
undoStack.push(undoFn);
// ...then do whatever the button does...
}
Имея это на месте, вы можете прослушивать ctrl-z
и вызывать самую последнюю функцию отмены:
// convenience to determine whether a keyboard event should trigger an undo
const isUndo = ({ctrlKey, metaKey, key}) => key === 'z' amp;amp; (ctrlKey || metaKey);
// keydown event handler for undo
const keydown = (e) => {
if(isUndo(e) amp;amp; undos.length) {
e.preventDefault();
undos.pop()();
}
}
// listen for keydowns
document.body.addEventListener('keydown', keydown);
Есть и некоторые другие соображения, например, должны ли определенные действия пользователя очищать стек отмены, но это основная идея.
Демонстрация с подтверждением концепции
В интересах ясности я заменил ваш код модификации контента, чтобы просто добавлять номер при каждом щелчке.
const div = document.getElementById('test');
const button = document.querySelector('button');
const undos = [];
button.addEventListener('click', e => {
const text = div.innerText;
undos.push(() => div.innerText = text);
div.innerText = ` ${undos.length} `;
});
const isUndo = ({ctrlKey, metaKey, key}) => key === 'z' amp;amp; (ctrlKey || metaKey);
const keydown = (e) => {
if(isUndo(e) amp;amp; undos.length) {
e.preventDefault();
undos.pop()();
}
}
document.body.addEventListener('keydown', keydown);
<div contenteditable="true" id="test" placeholder="insertText">
this is some text
</div>
<button id="btn">to strong</button>
Комментарии:
1. я искал что-то, что заставит браузер делать это нормально, не создавая кнопки отмены, повтора. Есть ли что-то, что может сделать это изначально браузером ?
2. Вам не нужно создавать для этого кнопки. Смотрите демонстрацию выше.
3. я знаю, что это будет применено при нажатии
Ctrl z
, я имею в виду, что есть способ заставить браузер сделать это изначально, не добавляя дополнительный код для отмены, повтора