Reputation: 193
My goal is to be able to undo (using ctrl + z
) a .replace
script after it is clicked.
e.g.
"word sample"
will turn into “word sample”
after clicking Fix it
button. I need a feature so that a user can reverse this change using ctrl + z
when they need to.
I know it's not automatically supported by the browser since this is a javascript manipulation.
Please see my code snippet to demonstrate that undo is not working after clicking Fix it
function fixTextarea(textarea) {
textarea.value = textarea.value.replace(" ,", ",")
.replace(" ;", ";")
.replace(" .", ".")
.replace(" ", " ")
.replace(" ", " ")
.replace("--", "—")
.replace(/(^|[-\u2014\s(\["])'/g, "$1\u2018")
.replace(/'/g, "\u2019")
.replace(/(^|[-\u2014/\[(\u2018\s])"/g, "$1\u201c")
.replace(/"/g, "\u201d")
};
function fixtext() {
let textarea = document.getElementById("textarea1");
textarea.select();
fixTextarea(textarea);
}
window.addEventListener('DOMContentLoaded', function(e) {
var area = document.getElementById("textarea1");
var getCount = function (str, search) {
return str.split(search).length - 1;
};
var replace = function (search, replaceWith) {
if (typeof(search) == "object") {
area.value = area.value.replace(search, replaceWith);
return;
}
if (area.value.indexOf(search) >= 0) {
var start = area.selectionStart;
var end = area.selectionEnd;
var textBefore = area.value.substr(0, end);
var lengthDiff = (replaceWith.length - search.length) * getCount(textBefore, search);
area.value = area.value.replace(search, replaceWith);
area.selectionStart = start + lengthDiff;
area.selectionEnd = end + lengthDiff;
}
};
});
textarea{
-webkit-box-shadow: none;
-moz-box-shadow: none;
box-shadow: none;x;
background-color: #fafafa;
width: 30%;
height: 100px;
font-family: 'Calibri', sans-serif;
outline: none;
}
.nbtn{
border: 1px solid;
border-color: #555555;
border-radius: 3px;
padding: 9px 9px;
font-size: 13px;
text-align: center;
cursor: pointer;
color: #000000;
background-color: #e0f2f1;
font-weight: bold;
margin-bottom: 10px;
}
.nbtn:hover{
background-color: #fafafa;
}
.nbtn:active{
background-color: #e0f7fa;
transform: translateY(1px);
outline: none;
box-shadow: 1px #666666;
}
<textarea id="textarea1" name="textarea1" spellcheck="true" onpaste="console.log('onpastefromhtml')">"click "Fix it" "to" see what will happen" and try to undo me if you can</textarea>
<br><br>
<button class="nbtn" onclick="fixtext()"> Fix it</button>
So in this sample I have, whenever a user clicks the Fix it
button, the script to replace double spaces into single space and straight quotes into curly quotes will be activated.
What I have in mind and where I need help as to how to integrate this on this script is this:
var isChrome = !!window.chrome && !!window.chrome.webstore;
var mapObj = {
}
jQuery(".cbtn").on("click", function(event) {
var currButtonId = jQuery(event.target).attr('id');
var mappedText = mapObj[currButtonId];
jQuery("#textarea1").focus();
document.execCommand('insertText', false, mappedText);
});
This will supposedly map all the button clicks so that a user can undo/redo them when they need to.
Please let me know about your suggestions. I would really appreciate it.
Upvotes: 0
Views: 364
Reputation: 597
You can use the code snippet below. Instead of reversing the Regex Op, I keep a stack (the usual data structure for manage ctrl+Z ops) to hold the previous text before click the "fix it" btn.
In this type of solution, I take advantage of the way javascript works and the closure made by declaring a new function. The stack array is captured inside the fixTextStackGen, that's way it's accessible to the function the "fixes" the text. By adding the undo function declaration inside the fixTextStackGen the array is accessible to both the "fix it" and the "undo" ops.
See the code below:
function fixTextarea(textarea) {
textarea.value = textarea.value
.replace(" ,", ",")
.replace(" ;", ";")
.replace(" .", ".")
.replace(" ", " ")
.replace(" ", " ")
.replace("--", "—")
.replace(/(^|[-\u2014\s(\["])'/g, "$1\u2018")
.replace(/'/g, "\u2019")
.replace(/(^|[-\u2014/\[(\u2018\s])"/g, "$1\u201c")
.replace(/"/g, "\u201d");
}
//The new fix function
function fixTextarea2(textarea){
textarea.value = textarea.value
.replace(/[f|c|e|i]/gim,"b");
}
const fixTextStackGen = () => {
let stack = [];
let textarea = document.getElementById("textarea1");
//Delaying the computation, now captures the 'fix' function in the closure as well as the stack array
let doFunc = (fixFunction) => () => {
stack.push(textarea.value)
textarea.select();
fixFunction(textarea);
}
let undoFunc = () => {
textarea.value = 0 < stack.length ? stack.pop() : textarea.value;
}
return [doFunc, undoFunc]
}
const textControlFunctions = fixTextStackGen();
//extracting the 'Do' functions generator
const fixtextFuncGen = textControlFunctions[0];
//assigning the appropriate function to the specific identifiers
const fixtext1 = fixtextFuncGen(fixTextarea);
const fixtext2 = fixtextFuncGen(fixTextarea2);
//Extracting the 'Undo' function
const unfixText = textControlFunctions[1];
// define a handler
function ctrl_z_handlar(e) {
// this would test for whichever key is 40 and the ctrl key at the same time
if (e.ctrlKey && e.keyCode == 90) {
// call your function to do the thing
unfixText();
}
}
// register the handler
document.addEventListener('keyup', ctrl_z_handlar, false);
window.addEventListener("DOMContentLoaded", function (e) {
var area = document.getElementById("textarea1");
var getCount = function (str, search) {
return str.split(search).length - 1;
};
var replace = function (search, replaceWith) {
if (typeof search == "object") {
area.value = area.value.replace(search, replaceWith);
return;
}
if (area.value.indexOf(search) >= 0) {
var start = area.selectionStart;
var end = area.selectionEnd;
var textBefore = area.value.substr(0, end);
var lengthDiff =
(replaceWith.length - search.length) * getCount(textBefore, search);
area.value = area.value.replace(search, replaceWith);
area.selectionStart = start + lengthDiff;
area.selectionEnd = end + lengthDiff;
}
};
});
<textarea id="textarea1" name="textarea1" spellcheck="true" onpaste="console.log('onpastefromhtml')">"click "Fix it" "to" see what will happen" and try to undo me if you can</textarea>
<br><br>
<button class="nbtn" onclick="fixtext1()"> Fix it1</button>
<button class="nbtn" onclick="fixtext2()"> Fix it2</button>
Keep in mind that there is no limitation on the array size and if the text wasn't changed and the user hit 'Fix it' pressing he will have to press 'ctrl+z' multiple times to see the previous text
EDIT: To make the code more modular, we'll pass the 'fix' function as an argument to the 'fixTextStackGen' function, as follow:
//passing 'fix' function as an argument
const fixTextStackGen = (fixFunction) => {
let stack = [];
let textarea = document.getElementById("textarea1");
let doFunc = () => {
stack.push(textarea.value)
textarea.select();
fixFunction(textarea);
}
let undoFunc = () => {
textarea.value = 0 < stack.length ? stack.pop() : textarea.value;
}
return [doFunc, undoFunc]
}
//passing 'fixTextarea' as an argument should yield with the same functionality
const textControlFunctions = fixTextStackGen(fixTextarea);
Upvotes: 1