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.
Related
I want to create a CMS like wordpress. In my text editor I want the user to be able to create a hyperlink via a button click. But I don't want to show an alert so the user can input the url but a div shown under the selected word/sentence inside or over the text area with an text input. How do I get the location of the selected word?
I already tried to append a textnode to it like this:
window.getSelection().appendChild(document.createTextNode("testing"));
but I get an error, that .appendChild() is not a function.
$('#btnLink').click(function() {
window.getSelection().appendChild(document.createTextNode("testing"));
})
I expect the textnode is appended to the selected word, but it doesnt work
The getSelection() method will not return a node to append text to.
I've used some code from a different answer (added below the code) to achieve what you're asking.
$('#btnLink').click(function() {
var elm = getRange();
var div = document.createElement("div");
div.appendChild( document.createElement("input") );
elm.collapse(false);
elm.insertNode(div);
});
function getRange() {
var range, sel, container;
if (document.selection) {
range = document.selection.createRange();
range.collapse(isStart);
return range.parentElement();
} else {
sel = window.getSelection();
if (sel.getRangeAt) {
if (sel.rangeCount > 0) {
range = sel.getRangeAt(0);
}
} else {
// Old WebKit
range = document.createRange();
range.setStart(sel.anchorNode, sel.anchorOffset);
range.setEnd(sel.focusNode, sel.focusOffset);
// Handle the case when the selection was selected backwards (from the end to the start in the document)
if (range.collapsed !== sel.isCollapsed) {
range.setStart(sel.focusNode, sel.focusOffset);
range.setEnd(sel.anchorNode, sel.anchorOffset);
}
}
if (range) {
return range;
}
}
}
This code is copied and altered from How can I get the DOM element which contains the current selection? to demonstrate the use for this specific question.
A JSFiddle: https://jsfiddle.net/zuvq9nyc/5/
try this:
$('#btnLink').click(function() {
window.getSelection.append(document.createTextNode('testing'));
})
.appendchild() is a javascript function, jquery can't use it. use .append() instead and use .createTextNode() inside it.
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>
I have contentEditable element(maybe div...) and I would like to replace hash tag or url like twitter(or facebook) on keydown event.
I can normally archieve it with this below..
//onKeydown Event
....
_this.saveRange(); // save current range
var string = _this.getHtml(); //_this is contentEditable element (div)
var reg = /(:?#{1}|\s#{1})([A-Za-z0-9.;_\-ㄱ-ㅎ|ㅏ-ㅣ|가-힣]+)/gm;
if(reg.test(string)) {
var text = string.replace(reg, function(u) {
return '<a class="highlight" rel="external" id="test">' + u + '</a>';
});
_this.setHtml(text); // insert html what replaced using innerHtml
_this.restoreRange();
}
This works fine replace text.. on keydown event.
But when I set html that replaced, document range(cursor) is move first position.
this is problem.
I saved range before innerHtml and restore range after innerHtml. But it doesn't work.
Below that I used function saveRange and restoreRange.
var savedRange; //public variable
function saveRange() {
if(window.getSelection) {
savedRange = window.getSelection().getRangeAt(0);
} else if(document.selection) { //IE
savedRange = document.selection.createRange();
}
}
function restoreRange() {
if (savedRange != null) {
if (window.getSelection)//non IE and there is already a selection
{
var s = window.getSelection();
if (s.rangeCount > 0)
s.removeAllRanges();
s.addRange(savedRange);
}
else if (document.createRange)//non IE and no selection
{
window.getSelection().addRange(savedRange);
}
else if (document.selection)//IE
{
savedRange.select();
}
}
}
Can anybody come up with solution to this ???
or give me a link.....
You could save and restore the selection range by character index so long as the text content is remaining the same. I've provided simple functions to do this here:
https://stackoverflow.com/a/17694760/96100
I've created a simple tool so employees can personalize their company email signature. This tool creates some styled text with some bolded fonts and a bit of color, nothing fancy. If I then select the text and copy and paste it into my Gmail signature field all works well. It retains the formatting. However, I'd prefer to give the user the option of clicking a "Copy" button that copies the formatted content onto their clipboard.
I'm currently using ZeroClipboard to add the "copy to clipboard" functionality and it's working great. Thing is I don't know how to grab that formatted text. It just keeps copying the unformatted version. One thing I tried was adding a mouseDown listener to ZeroClipboard that selects the text like this:
function selectText() {
if (document.selection) {
var range = document.body.createTextRange();
range.moveToElementText(document.getElementById('clicktocopy'));
range.select();
}
else if (window.getSelection) {
var range = document.createRange();
range.selectNode(document.getElementById('clicktocopy'));
window.getSelection().addRange(range);
}
}
Then returns the selection like this:
function getText() {
if (window.getSelection) {
var range = window.getSelection();
return range.toString();
}
else {
if (document.selection.createRange) {
var range = document.selection.createRange();
return range.text;
}
}
}
However, this is only copying the unformatted text. Is it possible to copy the text formatted?
My formatted text is in a div with the id "results".
If you want an HTML string representing the current selection, the following function will do this (replacing your getText() function):
function getSelectionHtml() {
var html = "";
if (typeof window.getSelection != "undefined") {
var sel = window.getSelection();
if (sel.rangeCount) {
var container = document.createElement("div");
for (var i = 0, len = sel.rangeCount; i < len; ++i) {
container.appendChild(sel.getRangeAt(i).cloneContents());
}
html = container.innerHTML;
}
} else if (typeof document.selection != "undefined") {
if (document.selection.type == "Text") {
html = document.selection.createRange().htmlText;
}
}
return html;
}
I'm trying to write a function that allows a contenteditable div to do some auto formatting while the user is typing in the div. So far I only manage to make it work in IE. Anyone can help me?
function formatOnKeyUp(){
if (window.getSelection) {
// ???????
} else if (document.selection) {
cursorPos=document.selection.createRange().duplicate();
clickx = cursorPos.getBoundingClientRect().left;
clicky = cursorPos.getBoundingClientRect().top;
}
text = document.getElementById('div1').innerHTML;
text = text.replace(/this/gm, "<i>this</i>");
// .... some other formating here...
document.getElementById('div1').innerHTML = text;
if (window.getSelection) {
// ????????
} else if (document.selection) {
cursorPos = document.body.createTextRange();
cursorPos.moveToPoint(clickx, clicky);
cursorPos.select();
}
}
You could use the selection save and restore module in my Rangy library, which uses invisible marker elements at the selection boundaries. I'd also suggest doing the replacement after a certain period of keboard inactivity rather than on every keyup event:
function formatText(el) {
// Save the selection
var savedSel = rangy.saveSelection();
// Do your formatting here
var text = el.innerHTML.replace(/this/gm, "<i>this</i>");
el.innerHTML = text;
// Restore the original selection
rangy.restoreSelection(savedSel);
}
var keyTimer = null, keyDelay = 500;
document.getElementById('div1').onkeyup = function() {
if (keyTimer) {
window.clearTimeout(keyTimer);
}
keyTimer = window.setTimeout(function() {
keyTimer = null;
formatText(document.getElementById('div1'));
}, keyDelay);
};
The cursor position and text range stuff looks particular to Microsoft's JScript.
If you're replacing text as someone types, why do you need that code?