I have seen a few questions about this on StackOverflow, but it seems hard to find a jQuery-based solution. Hence, I would like to ask this question.
I would like to replace text on-the-fly inside a div with the attribute contenteditable="true".
I'm looking for a jQuery-based solution that will do the following:
Automatically replace written text on-the-fly (as you type)
Being able to continue writing (whilst the replacement is being done)
I looked at SC Editor (http://www.sceditor.com/), it seems like it does exactly that (for instance, if you try type :) it gets replaced by an emoticon.
I think a good start would be an array with all the elements to replace:
$.settings = {
path: 'https://example.com/images/',
emoticons: {
':(' : 'stupid.jpg',
':)' : 'smart.jpg',
}
}
I have been unable to find good examples of this. Would be happy if somebody can share their thoughts and any code regarding this.
How would the replacement get done in the best possible way, with the above code?
I found this. If you adjust it, it might suit your needs. It replaces { with {} and ( with () and the cursor ends up in the middle.
<script type="text/javascript">
$(document).ready(function () {
$("#d").keypress(function (e) {
var charTyped = String.fromCharCode(e.which);
if (charTyped == "{" || charTyped == "(") {
// Handle this case ourselves
e.preventDefault();
var sel = window.getSelection();
if (sel.rangeCount > 0) {
// First, delete the existing selection
var range = sel.getRangeAt(0);
range.deleteContents();
// Insert a text node with the braces/parens
var text = (charTyped == "{") ? "{}" : "()";
var textNode = document.createTextNode(text);
range.insertNode(textNode);
// Move the selection to the middle of the text node
range.setStart(textNode, 1);
range.setEnd(textNode, 1);
sel.removeAllRanges();
sel.addRange(range);
}
}
});
});
</script>
</head>
<body>
<div id="d" contentEditable="true">....</div>
</body>
</html>
$('div').keyup(function(){
//make here for loop which replace all emoticons
$(this).text().replace(':(', 'stupid.jpg');
});
Posting what I eventually wrote after failing to find an answer to this question. I hope that it will be helpful to someone else who comes to this question looking for an answer (:
I'm going to post a much more general find and replace solution (contained in a class). This is for content editable divs and works while the user is typing, additionally it does not affect the caret position. This implementation uses a case insensitive search (although it would be trivial to disable this in the code). Another advantage this has is that it will work even if you are typing in the middle of a paragraph (not just at the end of the line) and will work on pasted text. Give it a go!
class FindAndReplace {
constructor($contentEditable, findAndReplaceData) {
var self = this;
$contentEditable.on('input blur', function () {
var textNodes = self.getTextNodes($contentEditable);
textNodes.each(function (i) {
// Perform all replacements on text
findAndReplaceData.forEach(function (findAndReplaceDatum) {
var find = findAndReplaceDatum.find;
var replace = findAndReplaceDatum.replace;
var regexEscapedFind = self.escapeRegExp(find);
var regexEscapedReplace = self.escapeRegExp(replace);
var regexEscapedCaseInsensitiveFind = self.makeRegexCaseInsensitive(regexEscapedFind);
// Case insensitive search for the find with a negative lookahead to check its not a case sensitive match of the replacement (aka to check its actually going to make a difference)
var regexString = `(?!${regexEscapedReplace})${regexEscapedCaseInsensitiveFind}`;
do {
// Get the latest version of the text node
textNode = self.getTextNodes($contentEditable)[i];
var text = textNode.data;
var regex = new RegExp(regexString);
var matchIndex = text.search(regex);
var matchFound = (matchIndex !== -1);
if (matchFound) {
// Select the match
var range = document.createRange();
range.setStart(textNode, matchIndex);
range.setEnd(textNode, matchIndex + find.length);
// Delete it
range.deleteContents();
// Create the replacement node
var textNode = document.createTextNode(replace);
// Insert it
range.insertNode(textNode);
// Set the range to the end of the selected node
range.collapse(false);
// Set the user selection the range
var sel = window.getSelection();
sel.removeAllRanges();
sel.addRange(range);
// Make sure there a no adjacent or empty text nodes
$contentEditable[0].normalize();
}
} while (matchFound)
});
});
});
}
escapeRegExp(string) {
// https://stackoverflow.com/questions/3446170/escape-string-for-use-in-javascript-regex
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string
}
getTextNodes($contentEditable) {
return $contentEditable.contents().filter(function () {
return this.nodeType == 3; // Text node
});
}
makeRegexCaseInsensitive(string) {
var stringArray = string.split('');
stringArray = stringArray.map(function (char) {
if (char.toLowerCase() !== char.toUpperCase())
return '[' + char.toLowerCase() + char.toUpperCase() + ']';
else
return char;
});
return stringArray.join('');
}
}
div {
border: 1px solid black;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script>
$(function(){
var findAndReplaceData = [
{
'find': 'find me',
'replace': 'and replace with me!'
},
{
'find': 'foo',
'replace': 'bar'
},
{
'find': 'no',
'replace': 'yes'
}
];
$contentEditable = $('div');
new FindAndReplace($contentEditable,findAndReplaceData);
});
</script>
<div contenteditable="true"></div>
Related
I'm all new to this, but after spending a week trying to find an answer, I thought I would try asking directly.
I am building a text editor using javascript and jquery. I have a textarea (with contenteditable), a stylesheet and a js script. What I want is that for each letter pressed, the kerning will be random. I achieved that with a simple function, but I don't want ALL textarea text to have this kerning, only the last letter pressed and so on and so on, so this type of thing would be the result:
simulation
There is what I have so far in my js file:
$(document).ready(
function() {
$('#textarea').keypress(function(){
var KerningRandom = Math.floor((Math.random()*90)-20);
$(this).css('letter-spacing',KerningRandom);
});
Here is my jsfiddle that actually doesn't work in jsfiddle and I don't get why as it works fine in local...?
Thanks!
You cannot address individual characters ( and so glyphs ) in CSS. Only ::first-letter.
Options you have:
convert all characters to individual spans. That's too much I think.
use <canvas> to render text and so to implement text flow layout from scratch.
You can find a working plunker of what you want to achieve there (I forked yours).
https://jsfiddle.net/1gesLgsa/2/
Full code :
//Code from https://stackoverflow.com/questions/1125292/how-to-move-cursor-to-end-of-contenteditable-entity
//Namespace management idea from http://enterprisejquery.com/2010/10/how-good-c-habits-can-encourage-bad-javascript-habits-part-1/
(function( cursorManager ) {
//From: http://www.w3.org/TR/html-markup/syntax.html#syntax-elements
var voidNodeTags = ['AREA', 'BASE', 'BR', 'COL', 'EMBED', 'HR', 'IMG', 'INPUT', 'KEYGEN', 'LINK', 'MENUITEM', 'META', 'PARAM', 'SOURCE', 'TRACK', 'WBR', 'BASEFONT', 'BGSOUND', 'FRAME', 'ISINDEX'];
//From: https://stackoverflow.com/questions/237104/array-containsobj-in-javascript
Array.prototype.contains = function(obj) {
var i = this.length;
while (i--) {
if (this[i] === obj) {
return true;
}
}
return false;
}
//Basic idea from: https://stackoverflow.com/questions/19790442/test-if-an-element-can-contain-text
function canContainText(node) {
if(node.nodeType == 1) { //is an element node
return !voidNodeTags.contains(node.nodeName);
} else { //is not an element node
return false;
}
};
function getLastChildElement(el){
var lc = el.lastChild;
while(lc && lc.nodeType != 1) {
if(lc.previousSibling)
lc = lc.previousSibling;
else
break;
}
return lc;
}
//Based on Nico Burns's answer
cursorManager.setEndOfContenteditable = function(contentEditableElement)
{
while(getLastChildElement(contentEditableElement) &&
canContainText(getLastChildElement(contentEditableElement))) {
contentEditableElement = getLastChildElement(contentEditableElement);
}
var range,selection;
if(document.createRange)//Firefox, Chrome, Opera, Safari, IE 9+
{
range = document.createRange();//Create a range (a range is a like the selection but invisible)
range.selectNodeContents(contentEditableElement);//Select the entire contents of the element with the range
range.collapse(false);//collapse the range to the end point. false means collapse to end rather than the start
selection = window.getSelection();//get the selection object (allows you to change selection)
selection.removeAllRanges();//remove any selections already made
selection.addRange(range);//make the range you have just created the visible selection
}
else if(document.selection)//IE 8 and lower
{
range = document.body.createTextRange();//Create a range (a range is a like the selection but invisible)
range.moveToElementText(contentEditableElement);//Select the entire contents of the element with the range
range.collapse(false);//collapse the range to the end point. false means collapse to end rather than the start
range.select();//Select the range (make it the visible selection
}
}
}( window.cursorManager = window.cursorManager || {}));
// ACTUAL CODE MADE FOR THIS ANSWER
$('#textarea').keypress(function(event) {
event.preventDefault();
var KerningRandom = Math.floor((Math.random() * 90));
if ($("#last").length > 0)
{
var previousLast = $("#textarea #last").html();
$("#textarea #last").remove();
}
else
var previousLast = "";
$("#textarea").html($("#textarea").html().slice() + previousLast + "<span id='last'>" + String.fromCharCode(event.which) + "</span>")
$("#last").css('margin-left', KerningRandom + "px");
var editableDiv = document.getElementById("textarea");
cursorManager.setEndOfContenteditable(editableDiv)
});
var editableDiv = document.getElementById("textarea");
cursorManager.setEndOfContenteditable(editableDiv)
Point by point explanation :
$('#textarea').keypress(function(event) {
event.preventDefault();
var KerningRandom = Math.floor((Math.random() * 90));
if ($("#last").length > 0)
{
var previousLast = $("#textarea #last").html();
$("#textarea #last").remove();
}
else
var previousLast = "";
$("#textarea").html($("#textarea").html() + previousLast + "<span id='last'>" + String.fromCharCode(event.which) + "</span>")
$("#last").css('margin-left', KerningRandom + "px");
var editableDiv = document.getElementById("textarea");
cursorManager.setEndOfContenteditable(editableDiv)
});
The event.preventDefault() prevent the letter to be added when pressing a key.
Then, we calculate our left margin value, save the previous last letter we had and remove the span that contains the last letter as it's not the last letter anymore.
We append the previous last letter , and the span that has a random left margin (to simulate the kerning) and the value of the pressed key (thanks to
How to find out what character key is pressed?) to the actual content.
After that, we needed to move the carret at the end of the textarea manually, because it would stay at the beginning otherwise.
For that, I used the code from
How to move cursor to end of contenteditable entity so goes there for explanation.
I'm creating a markdown editor and I need to check if neighbor characters are specific characters, then remove them, else append them.
For e.g I want to check selected-text, tow neighbor characters are **, then remove them, else append them around selected text.
I can get selected text using this approach:
function getSelection(elem) {
var selectedText;
if (document.selection != undefined) { // IE
elem.focus();
var sel = document.selection.createRange();
selectedText = sel.text;
} else if (elem.selectionStart != undefined) { // Firefox
var startPos = elem.selectionStart;
var endPos = elem.selectionEnd;
selectedText = elem.value.substring(startPos, endPos)
}
return selectedText;
}
$(document).on('mousedown', 'button', function(e) {
var selection = getSelection( $('#txtarea').get(0) );
alert(selection);
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<textarea id="txtarea">this is a test</textarea>
<button>Bold (toggle)</button>
Now I need when user clicks on that button, it checks if selected text is between ** like this **selectedtext**, then remove them like this selected text else append them like this **selectedtext**. How can I do that?
Before anything I would like to refer to all the markdown editors out there: https://www.google.de/webhp?sourceid=chrome-instant&ion=1&espv=2&ie=UTF-8#q=javascript%20markdown%20library
So: do not try to reinvent the the wheel, and so on.
But for the sake of learning, my approach would look like this:
function toggleMarker(marker, el) {
var markerLength = marker.length;
var startPos, endPos, selection, range;
if (document.selection != undefined) { // IE
el.focus();
range = document.selection.createRange();
selection = range.text;
} else if (el.selectionStart != undefined) { // Firefox
startPos = el.selectionStart;
endPos = el.selectionEnd;
selection = el.value.substring(startPos, endPos);
}
if (!selection.length){
return;
}
if (el.value.substring(startPos-markerLength,startPos) === marker
&& el.value.substring(endPos,endPos+markerLength) === marker
){
el.value = el.value.substring(0,startPos-markerLength) +
selection +
el.value.substring(endPos+markerLength);
}
else{
el.value = el.value.substring(0,startPos) + marker +
selection + marker + el.value.substring(endPos);
}
}
$(document).on('mousedown', 'button', function(e) {
toggleMarker( $(this).data('marker'), $('#txtarea').get(0) ).text;
});
See it in action: https://jsfiddle.net/t4ro53v8/4/
The solution takes a very generic approach: the marker to toggle is set as a custom data attribute to make it easy to reuse the code.
The functionality is only implemented for the non-IE case. You will have to check, how to determine startPos and endPos for a range in IE.
In all other browsers:
the selection is identified
nothing is done if nothing is selected
sourroundings of the selection are checked against the given marker
if both markers are present, they get deleted
otherwise the markers are inserted
As a proof of concept this example works like a charm.
But there are some shortcomings:
How to distinguish between bold text(**) and italics(*)?
How to handle markers that just appear just on one side of the selection
What to do, if a marker is selected?
But that is for you to solve now ...
You could use regex to find the occurance of a ** ** pattern.This regex will help you find the pattern similar to what you have.
[*][*][a-z]*[*][*] .
Using the exec() method, will help you extract that particular text.
Check the length of this using .length, if it is 4, then there is nothing in between, and you can replace it with the new text surrounded by **,
"**"+ newtext+"**"
For removing the **, you can use the replace() method, where you replace ** with whitespace or so.
In Google docs, this function changes the selected text to black
function selectedFontColorBlack() {
// DocumentApp.getUi().alert('selectedFontColorBlack');
var sel = DocumentApp.getActiveDocument().getSelection();
var elements = sel.getRangeElements();
for (var i = 0; i < elements.length; i++) {
var element = elements[i];
// Only modify elements that can be edited as text; skip images and other non-text elements.
if(element.getElement().editAsText) {
var text = element.getElement().editAsText();
// Bold the selected part of the element, or the full element if it's completely selected.
if (element.isPartial()) {
text.setForegroundColor(element.getStartOffset(), element.getEndOffsetInclusive(), "#000000");
} else {
text.setForegroundColor("#000000");
}
}
}
}
This function changes the entire paragraph in which the cursor (or selection) exists to uppercase:
function uppercaseSelected() {
// DocumentApp.getUi().alert('uppercaseSelected');
var sel = DocumentApp.getActiveDocument().getSelection();
var elements = sel.getRangeElements();
for (var i = 0; i < elements.length; i++) {
var element = elements[i];
// Only modify elements that can be edited as text; skip images and other non-text elements.
if(element.getElement().editAsText) {
var text = element.getElement().editAsText();
text.setText(text.getText().toUpperCase());
}
}
}
I don't see any corresponding setText function that works on the selection's "offset", as does the setForegroundColor(Integer,Integer,String). (Both of these functions are in class Text.)
How can I change the actually selected text to uppercase, and not the entire paragraph in which the selection exists?
Thank you.
Try using the setAttributes(startOffset, endOffsetInclusive, attributes) method. Check out the documentation
[EDIT: my bad, i don't think that'll do it. I'll look a bit longer tho]
The gem hidden in the post that #Mogsdad is referring to is this: var selectedText = elementText.substring(startOffset,endOffset+1);. to be little more verbose on how this is used: you can use the string method substring on objects such as DocumentApp.getActiveDocument().getSelection().getSelectedElements()[i].getElement().editAsText().getText()
so, essentially, grab that substring, convert it to uppercase, delete the text in the range (selectedElement.getstartOffset,selectedElement.endOffsetInclusive) and insert the bolded text at selectedElement.getstartOffset
Tada! check it out:
function uppercaseSelected() {
// Try to get the current selection in the document. If this fails (e.g.,
// because nothing is selected), show an alert and exit the function.
var selection = DocumentApp.getActiveDocument().getSelection();
if (!selection) {
DocumentApp.getUi().alert('Cannot find a selection in the document.');
return;
}
var selectedElements = selection.getSelectedElements();
for (var i = 0; i < selectedElements.length; ++i) {
var selectedElement = selectedElements[i];
// Only modify elements that can be edited as text; skip images and other
// non-text elements.
var text = selectedElement.getElement().editAsText();
// Change the background color of the selected part of the element, or the
// full element if it's completely selected.
if (selectedElement.isPartial()) {
var bitoftext = text.getText().substring(selectedElement.getStartOffset(), selectedElement.getEndOffsetInclusive() + 1);
text.deleteText(selectedElement.getStartOffset(), selectedElement.getEndOffsetInclusive());
text.insertText(selectedElement.getStartOffset(), bitoftext.toUpperCase());
} else {
text.setText(text.getText().toUpperCase());
}
}
}
Started with the code from Google App script Document App get selected lines or words?, and made this almost a year ago. I'm happy if it helps you.
The "trick" is that you need to delete the original text and insert the converted text.
This script produces a menu with options for UPPER, lower and Title Case. Because of the delete / insert, handling more than one paragraph needs special attention. I've left that to you!
function onOpen() {
DocumentApp.getUi().createMenu('Change Case')
.addItem("UPPER CASE", 'toUpperCase' )
.addItem("lower case", 'toLowerCase' )
.addItem("Title Case", 'toTitleCase' )
.addToUi();
}
function toUpperCase() {
_changeCase(_toUpperCase);
}
function toLowerCase() {
_changeCase(_toLowerCase);
}
function toTitleCase() {
_changeCase(_toTitleCase);
}
function _changeCase(newCase) {
var doc = DocumentApp.getActiveDocument();
var selection = doc.getSelection();
var ui = DocumentApp.getUi();
var report = ""; // Assume success
if (!selection) {
report = "Select text to be modified.";
}
else {
var elements = selection.getSelectedElements();
if (elements.length > 1) {
report = "Select text in one paragraph only.";
}
else {
var element = elements[0].getElement();
var startOffset = elements[0].getStartOffset(); // -1 if whole element
var endOffset = elements[0].getEndOffsetInclusive(); // -1 if whole element
var elementText = element.asText().getText(); // All text from element
// Is only part of the element selected?
if (elements[0].isPartial())
var selectedText = elementText.substring(startOffset,endOffset+1);
else
selectedText = elementText;
// Google Doc UI "word selection" (double click)
// selects trailing spaces - trim them
selectedText = selectedText.trim();
endOffset = startOffset + selectedText.length - 1;
// Convert case of selected text.
var convertedText = newCase(selectedText);
element.deleteText(startOffset, endOffset);
element.insertText(startOffset, convertedText);
}
}
if (report !== '') ui.alert( report );
}
function _toUpperCase(str) {
return str.toUpperCase();
}
function _toLowerCase(str) {
return str.toLowerCase();
}
// https://stackoverflow.com/a/196991/1677912
function _toTitleCase(str)
{
return str.replace(/\w\S*/g, function(txt){return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();});
}
I have found a code snippet (can't remember where), and it's working fine - almost :-)
The problem is, that it copies the selection no matter where the selection is made on the entire website, and it must only copy the selection if it is in a specific div - but how is that done?
function getHTMLOfSelection () {
var range;
if (document.selection && document.selection.createRange) {
range = document.selection.createRange();
return range.htmlText;
}
else if (window.getSelection) {
var selection = window.getSelection();
if (selection.rangeCount > 0) {
range = selection.getRangeAt(0);
var clonedSelection = range.cloneContents();
var div = document.createElement('div');
div.appendChild(clonedSelection);
return div.innerHTML;
} else {
return '';
}
} else {
return '';
}
}
$(document).ready(function() {
$("#test").click(function() {
var kopitekst = document.getElementById("replytekst");
var kopitjek=getHTMLOfSelection(kopitekst);
if (kopitjek=='')
{
alert("Please select some content");
}
else
{
alert(kopitjek);
}
});
});
I have made a Jsfiddle
This is my first post here. Hopefully I done it right :-)
That's because it checks the entire document with:
if (document.selection && document.selection.createRange) {
range = document.selection.createRange();
return range.htmlText;
}
Not a specific section. If you want to check specific sections for selected text, you need to identify that you are searching for them in the search selection, something that nails your range down to a particular div:
range = $('#replytekst');
Specify a particular DOM element instead of using document object.
var oDiv = document.getElementById( 'selDiv' );
then use
if ( oDiv.selection && oDiv.selection.createRange ) {
range = oDiv.selection.createRange();
return range.htmlText;
}
You need to check if the section contains the selection. This is separate from getting the selection. There is a method for doing this in this answer: How to know if selected text is inside a specific div
I've updated your fiddle
Basically you need to check the id of the parent/ascendant of the selected text node.
selection.baseNode.parentElement.id or selection.baseNode.parentElement.parentElement.id will give you that.
Edit: I've thought of another, somewhat hack-y, way of doing it.
If
kopitekst.innerHTML.indexOf(kopitjek) !== -1
gives true, you've selected the right text.
DEMO1
DEMO2
(these work in Chrome and Firefox, but you might want to restructure the getHTMLOfSelection function a little)
If it possible for you I recommend to use rangy framework. Then your code might look like this:
// get the selection
var sel = rangy.getSelection();
var ranges = sel.getAllRanges();
if (!sel.toString() || !sel.toString().length)
return;
// create range for element, where selection is allowed
var cutRange = rangy.createRange();
cutRange.selectNode(document.getElementById("replytekst"));
// make an array of intersections of current selection ranges and the cutRange
var goodRanges = [];
$.each(ranges, function(j, tr) {
var rr = cutRange.intersection(tr);
if (rr)
goodRanges.push(rr);
});
sel.setRanges(goodRanges);
// do what you want with corrected selection
alert(sel.toString());
// release
sel.detach();
In this code if text was selected in your specific div then it will be kept, if there was selection where other elements take part too, these selection ranges will be cut off.
I have a contentEditable and I strip the formatting of pasted content on('paste') by catching the event. Then I focus a textarea, paste the content in there, and copy the value. Pretty much the answer from here. The problem is that I can’t do this:
$("#paste-output").text($("#area").val());
because that would replace my entire content with the pasted text. So I need to paste the content at caret position. I put together a script that does that:
pasteHtmlAtCaret($("#area").val());
// pastes CTRL+V content at caret position
function pasteHtmlAtCaret(html) {
var sel, range;
if (window.getSelection) {
sel = window.getSelection();
if (sel.getRangeAt && sel.rangeCount) {
range = sel.getRangeAt(0);
range.deleteContents();
var el = document.createElement("div");
el.innerHTML = html;
var frag = document.createDocumentFragment(), node, lastNode;
while ((node = el.firstChild)) {
lastNode = frag.appendChild(node);
}
range.insertNode(frag);
if (lastNode) {
range = range.cloneRange();
range.setStartAfter(lastNode);
range.collapse(true);
sel.removeAllRanges();
sel.addRange(range);
}
}
} else if (document.selection && document.selection.type != "Control") {
document.selection.createRange().pasteHTML(html);
}
}
The only problem is that it pastes HTML content, at caret position using the html variable. How can I transform that into plain text? I tried adding the jQuery .text(html) to variable without luck. Something like this might help:
el.textContent||el.innerText;
Any ideas or a better solution? Thanks!
EDIT:
Thanks to the answers below I modified my code and solved the issue. I basically copied the value of textarea into a div and grabbed only its .text():
// on paste, strip clipboard from HTML tags if any
$('#post_title, #post_content').on("paste", function() {
var text = $('<div>').html($("#area").val()).text();
pasteHtmlAtCaret(text);
}, 20);
});
Create an external div,
Put your html in that div,
Copy the text from that div
Insert it at the cursor's position.
Upon request from Jonathan Hobbs I am posting this as the answer.
Thanks to the answers above I modified my code and solved the issue. I basically copied the value of textarea into a div and grabbed only its .text():
// on paste, strip clipboard from HTML tags if any
$('#post_title, #post_content').on("paste", function() {
setTimeout(function(){
var text = $('<div>').html($("#area").val()).text();
pasteHtmlAtCaret(text);
}, 20);
});
Replace tag solution:
http://jsfiddle.net/tomwan/cbp1u2cx/1/
var $plainText = $("#plainText");
var $linkOnly = $("#linkOnly");
var $html = $("#html");
$plainText.on('paste', function (e) {
window.setTimeout(function () {
$plainText.html(removeAllTags(replaceStyleAttr($plainText.html())));
}, 0);
});
$linkOnly.on('paste', function (e) {
window.setTimeout(function () {
$linkOnly.html(removeTagsExcludeA(replaceStyleAttr($linkOnly.html())));
}, 0);
});
function replaceStyleAttr (str) {
return str.replace(/(<[\w\W]*?)(style)([\w\W]*?>)/g, function (a, b, c, d) {
return b + 'style_replace' + d;
});
}
function removeTagsExcludeA (str) {
return str.replace(/<\/?((?!a)(\w+))\s*[\w\W]*?>/g, '');
}
function removeAllTags (str) {
return str.replace(/<\/?(\w+)\s*[\w\W]*?>/g, '');
}
The solution I used is to set the innerText of the element on blur:
$element.on('blur', () => this.innerText = this.innerText);
This keeps the whitespace, but strips the formatting. It may work acceptable in your scenario as well.