Reputation: 527
Here I am using Ctrl+Z for undoing the replaced text, I have a scenario that, in text-area, I have a sentence with multiple words, where I select first word and replaced with stars,later I select another word and replaced with stars. But when did Ctrl+Z it is only working for latest selected word and not working for previous words.
JavaScript:
var selection = {};
function undo(e) {
var evtobj = window.event? window.event : e;
if (evtobj.keyCode == 90 && evtobj.ctrlKey && selection.text) {
evtobj.preventDefault();
var txtarea = document.getElementById("mytextarea");
var allText = txtarea.value;
var newText = allText.substring(0, selection.start) + selection.text + allText.substring(selection.finish, allText.length);
txtarea.value = newText;
}
}
function getSel() {
// obtain the object reference for the textarea>
var txtarea = document.getElementById("mytextarea");
// obtain the index of the first selected character
var start = txtarea.selectionStart;
// obtain the index of the last selected character
var finish = txtarea.selectionEnd;
//obtain all Text
var allText = txtarea.value;
selection.text = allText.substring(start, finish);
selection.start = start;
selection.finish = finish;
// obtain the selected text
var sel = allText.substring(start, finish);
sel = sel.replace(/[\S]/g, "*"); //append te text;
var newText = allText.substring(0, start) + sel + allText.substring(finish, allText.length);
txtarea.value = newText;
$('#newpost').offset({top: 0, left: 0}).hide();
}
function closePopUp() {
$('#newpost').offset({top: 0, left: 0}).hide();
}
$(document).ready(function () {
closePopUp();
var newpost = $('#newpost');
$('#mytextarea').on('select', function (e) {
var txtarea = document.getElementById("mytextarea");
var start = txtarea.selectionStart;
var finish = txtarea.selectionEnd;
newpost.offset(getCursorXY(txtarea, start, 20)).show();
newpost.find('div').text('replace with stars');
}).on('input', () => selection.text = null);
document.onkeydown = undo;
});
var getCursorXY = function getCursorXY(input, selectionPoint, offset) {
var inputX = input.offsetLeft,
inputY = input.offsetTop;
// create a dummy element that will be a clone of our input
var div = document.createElement('div');
// get the computed style of the input and clone it onto the dummy element
var copyStyle = getComputedStyle(input);
var _iteratorNormalCompletion = true;
var _didIteratorError = false;
var _iteratorError = undefined;
try {
for (var _iterator = copyStyle[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done) ; _iteratorNormalCompletion = true) {
var prop = _step.value;
div.style[prop] = copyStyle[prop];
}
// we need a character that will replace whitespace when filling our dummy element
// if it's a single line <input/>
} catch (err) {
_didIteratorError = true;
_iteratorError = err;
} finally {
try {
if (!_iteratorNormalCompletion && _iterator.return) {
_iterator.return();
}
} finally {
if (_didIteratorError) {
throw _iteratorError;
}
}
}
var swap = '.';
var inputValue = input.tagName === 'INPUT' ? input.value.replace(/ /g, swap) : input.value;
// set the div content to that of the textarea up until selection
var textContent = inputValue.substr(0, selectionPoint);
// set the text content of the dummy element div
div.textContent = textContent;
if (input.tagName === 'TEXTAREA') div.style.height = 'auto';
// if a single line input then the div needs to be single line and not break out like a text area
if (input.tagName === 'INPUT') div.style.width = 'auto';
// create a marker element to obtain caret position
var span = document.createElement('span');
// give the span the textContent of remaining content so that the recreated dummy element
// is as close as possible
span.textContent = inputValue.substr(selectionPoint) || '.';
// append the span marker to the div
div.appendChild(span);
// append the dummy element to the body
document.body.appendChild(div);
// get the marker position, this is the caret position top and left relative to the input
var spanX = span.offsetLeft,
spanY = span.offsetTop;
// lastly, remove that dummy element
// NOTE:: can comment this out for debugging purposes if you want to see where that span is rendered
document.body.removeChild(div);
// return an object with the x and y of the caret. account for input positioning
// so that you don't need to wrap the input
return {
left: inputX + spanX,
top: inputY + spanY + offset
};
};
Here is my Plunker.
Upvotes: 2
Views: 196
Reputation: 8491
You have to manually control all your textarea
changes.
Here I created the edits
array which is populated with textarea
text on change every few seconds (you can control it using the saveInterval
variable).
You also can set the max length of this array using maxHistorySize
. When the array is full, the old changes are lost.
var edits = [""];
var interval = true;
var maxHistorySize = 10;
var saveInterval = 3000;
function undo(e) {
var evtobj = window.event? window.event : e;
if (evtobj.keyCode == 90 && evtobj.ctrlKey) {
evtobj.preventDefault();
var txtarea = document.getElementById("mytextarea");
var previousText = edits.length === 1 ? edits[0] : edits.pop();
if (previousText !== undefined) {
txtarea.value = previousText;
}
}
}
function getSel() {
// obtain the object reference for the textarea>
var txtarea = document.getElementById("mytextarea");
// obtain the index of the first selected character
var start = txtarea.selectionStart;
// obtain the index of the last selected character
var finish = txtarea.selectionEnd;
//obtain all Text
var allText = txtarea.value;
edits.push(allText);
if (edits.length > maxHistorySize) edits.shift();
// obtain the selected text
var sel = Array(finish - start + 1).join("*");
//append te text;
var newText = allText.substring(0, start) + sel + allText.substring(finish, allText.length);
txtarea.value = newText;
$('#newpost').offset({top: 0, left: 0}).hide();
}
function closePopUp() {
$('#newpost').offset({top: 0, left: 0}).hide();
}
$(document).ready(function () {
closePopUp();
var newpost = $('#newpost');
$('#mytextarea').on('select', function (e) {
var txtarea = document.getElementById("mytextarea");
var start = txtarea.selectionStart;
var finish = txtarea.selectionEnd;
newpost.offset(getCursorXY(txtarea, start, 20)).show();
newpost.find('div').text(Array(finish - start + 1).join("*"));
}).on('input', function() {
if (interval) {
interval = false;
edits.push($(this).val());
if (edits.length > maxHistorySize) edits.shift();
setTimeout(() => interval = true, saveInterval);
}
});
document.onkeydown = undo;
});
const getCursorXY = (input, selectionPoint, offset) => {
const {
offsetLeft: inputX,
offsetTop: inputY,
} = input
// create a dummy element that will be a clone of our input
const div = document.createElement('div')
// get the computed style of the input and clone it onto the dummy element
const copyStyle = getComputedStyle(input)
for (const prop of copyStyle) {
div.style[prop] = copyStyle[prop]
}
// we need a character that will replace whitespace when filling our dummy element
// if it's a single line <input/>
const swap = '.'
const inputValue = input.tagName === 'INPUT' ? input.value.replace(/ /g, swap) : input.value
// set the div content to that of the textarea up until selection
const textContent = inputValue.substr(0, selectionPoint)
// set the text content of the dummy element div
div.textContent = textContent
if (input.tagName === 'TEXTAREA') div.style.height = 'auto'
// if a single line input then the div needs to be single line and not break out like a text area
if (input.tagName === 'INPUT') div.style.width = 'auto'
// create a marker element to obtain caret position
const span = document.createElement('span')
// give the span the textContent of remaining content so that the recreated dummy element
// is as close as possible
span.textContent = inputValue.substr(selectionPoint) || '.'
// append the span marker to the div
div.appendChild(span)
// append the dummy element to the body
document.body.appendChild(div)
// get the marker position, this is the caret position top and left relative to the input
const { offsetLeft: spanX, offsetTop: spanY } = span
// lastly, remove that dummy element
// NOTE:: can comment this out for debugging purposes if you want to see where that span is rendered
document.body.removeChild(div)
// return an object with the x and y of the caret. account for input positioning
// so that you don't need to wrap the input
return {
left: inputX + spanX,
top: inputY + spanY + offset,
}
}
#mytextarea {width: 600px; height: 200px; overflow:hidden; position:fixed}
#newpost {
position:absolute;
background-color:#ffffdc;
border:1px solid #DCDCDC;
border-radius:10px;
padding-right:5px;
width: auto;
height: 30px;
}
#newpost span {
cursor:pointer;
position: absolute;
top: 0;
right: 5px;
font-size: 22px;
}
#newpost div {
color:#0000ff;
padding:10px;
margin-right:10px;
width: auto;
cursor:pointer;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<html>
<head>
</head>
<body>
<textArea id="mytextarea"></textArea>
<div id="newpost">
<span onclick="closePopUp();">˟</span>
<div onclick="getSel()"></div>
</div>
</body>
</html>
Upvotes: 2
Reputation: 8219
You need to change your logic from using a single selection
object to using an array of selection
objects, like this:
Initialize to selections
instead of selection
var selections = [];
When getSel()
is called, make sure you update to push
to array:
function getSel() {
// obtain the object reference for the textarea>
var txtarea = document.getElementById("mytextarea");
// obtain the index of the first selected character
var start = txtarea.selectionStart;
// obtain the index of the last selected character
var finish = txtarea.selectionEnd;
//obtain all Text
var allText = txtarea.value;
selections.push({
start: start,
finish: finish,
text: allText.substring(start, finish)
});
// obtain the selected text
var sel = allText.substring(start, finish);
sel = sel.replace(/[\S]/g, "*"); //append te text;
var newText = allText.substring(0, start) + sel + allText.substring(finish, allText.length);
txtarea.value = newText;
$('#newpost').offset({top: 0, left: 0}).hide();
}
As such, we need to update undo
function, as follows:
function undo(e) {
var evtobj = window.event? window.event : e;
if (evtobj.keyCode == 90 && evtobj.ctrlKey) {
evtobj.preventDefault();
if (selections.length === 0) alert ("Can't do more undos");
else {
var thisSelection = selections.pop();
var txtarea = document.getElementById("mytextarea");
var allText = txtarea.value;
var newText = allText.substring(0, thisSelection.start) + thisSelection.text + allText.substring(thisSelection.finish, allText.length);
txtarea.value = newText;
}
}
}
Lastly, remove the on('input')
event listener from $('#mytextarea')
as I don't think it's needed
Here's the plunk with all the changes
Upvotes: 1