I have the following script which allows me to select text, and will then visually highlight it by wrapping the selected text in a span tag.
This normally works fine, but if there is a highlight tag separated from another highlight tag by only a space, it joins the two highlights together.
Javascript
var HVleftPanelContent = $("#highlight-view .top .content");
HVoutputUl = $("#highlight-view .contentBottom ul");
$("p").on("copy", highlight);
function highlight() {
var text = window.getSelection().toString();
var selection = window.getSelection().getRangeAt(0);
var selectedText = selection.extractContents();
var textStr = selectedText.textContent;
if (textStr == "\n") {
clearSelection();
return false;
} else if (textStr[textStr.length - 1] == "\n") {
textStr = textStr.slice(0, -1);
var reg = new RegExp("\n", "g");
textStr = textStr.replace(reg, "\n<b data='
'></b>") + "\n";
} else if (textStr.indexOf("\n") >= 0) {
var reg = new RegExp("\n", "g");
textStr = textStr.replace(reg, "\n<b data='
'></b>");
}
var span = $("<span class='highlight'>" + textStr + "</span>");
selection.insertNode(span[0]);
if (selectedText.childNodes[1] != undefined) {
$(selectedText.childNodes[1]).remove();
}
var txt = HVleftPanelContent.html();
HVleftPanelContent.html(txt.replace(/<\/span>(?:\s)*<span class="highlight">/g, ''));
HVoutputUl.html("");
$("#highlight-view .top .content .highlight").each(function () {
$("#highlight-view .contentBottom ul").append("<li><span>" + $(this).html() + "</span></li>");
});
saveIt();
clearSelection();
}
Recap
If HTML looks like this:
This is a short paragraph
And I highlight "is", the markup changes to:
This <span>is</span> a short paragraph
And then I highlight either "this" or "a", the markup erroneously changes to:
This <span>isa</short> paragraph
Instead of how it should change:
This <span>is</span> <span>a</span> paragraph
Potential Problem
I assume the problem lays in this line:
HVleftPanelContent.html(txt.replace(/<\/span>(?:\s)*<span class="highlight">/g, ''));
Where the Regex statement is joining <span> tags that are next to each other, which it should so that if two span tags are directly next to each other, it becomes one span, but the Regex isn't limiting the joining to only when they're directly next to each other.
So, basically, how can I change the Regex to only join span tags if they're directly next to each other.
Fairly simple, replace:
HVleftPanelContent.html(txt.replace(/<\/span>(?:\s)*<span class="highlight">/g, ''));
With:
HVleftPanelContent.html(txt.replace(/<\/span><span class="highlight">/g, ''));
The problem was (?:\s)*, which means match any white space 0 or more times, which means that it would match even the spans that are separated with spaces.
Related
I have a contenteditable div and i would like to add some html tags around highlighted text, after user select the text and click the button..
Here is the sample code. It has some javascript codes but i couldnt make it work as desired. And i played with a lot actually.
https://codepen.io/anon/pen/ybzzXZ
P.S. I'm going to add , or like html tags after when we solve the how to add html tags around it.
Some of that js codes which i found in stackoverflow.
function getSelectionText() {
var text = "";
if (window.getSelection) {
text = window.getSelection().text;
} else if (document.selection && document.selection.type != "Control") {
text = document.selection.createRange().text;
}
return text;
}
and the other one is
function replaceSelectionWithHtml(html) {
var range;
if (window.getSelection && window.getSelection().getRangeAt) {
range = window.getSelection().getRangeAt(0);
range.deleteContents();
var div = document.createElement("div");
div.innerHTML = html;
var frag = document.createDocumentFragment(), child;
while ( (child = div.firstChild) ) {
frag.appendChild(child);
}
range.insertNode(frag);
} else if (document.selection && document.selection.createRange) {
range = document.selection.createRange();
range.pasteHTML(html);
}
}
There are several challenges with the problem you present.
First off you need to gather the selected text value. You have posted some examples of that - that is fairly well documented elsewhere so I will leave that up to you to isolate that issue.
Next you need to highlight the selected text. Often to highlight something in HTML we wrap that text that we wish to highlight in a simple element such as a span, then give that span some class - for example often this is used to give a background color to some text. <span style='background-color:yellow'>some text</span> - not so difficult to understand that portion.
The challenge with this then is to combine your "discovered text" with the highlight. Pretty easy to wrap that text as in the span example provided earlier. One issue however is that if that text is previously within some other HTML elements, we need to ensure that the text choice in the discovery is for example not contained within another element AND if so, handle that issue. Let's illustrate that with this span: Howdy <span style='background-color:yellow'>some text</span> Some more.
Now for this example suppose we wish to highlight the text "Howdy some" - a portion of that text is previously within a span with our desired markup, thus we must first extract that, remove that "highlight" and henceforth highlight the new text "choice" of "Howdy some".
To provide an illustration of that. Type the words "This I want" into the text box and see how it gets highlighted.
This is not exactly your problem however it provides the "highlight" which you could potentially combine with your selector. I have NOT fully vetted this for bugs such as typing in HTML in to "highlight".
/* highlight words */
function findStringLimit(searchChar, searchCharIndex, searchedString) {
return searchedString.substring(0, searchedString.lastIndexOf(searchChar, searchCharIndex));
};
function highlightWords(wordsy, text) { /* eliminate a bug with parenthesis */
wordsy = wordsy.replace("(", "");
wordsy = wordsy.replace(")", ""); /* escape other characters for bug */
text = text.replace(";", "");
text = text.replace("'", "'");
text = text.replace("<", "<");
text = text.replace(">", ">");
text = text.replace("<span", "<span");
text = text.replace('autoCompleteWord">', 'autoCompleteWord">');
text = text.replace("</span", "</span");
text = text.replace('span>', 'span>');
var re = '(' + wordsy + ')(?![^<]*(?:<\/span class=\"autoCompleteWord\"|>))';
var regExp = new RegExp(re, 'ig');
var sTag = '<span class="autoCompleteWord">';
var eTag = "</span>";
return text.replace(regExp, sTag + '$&' + eTag);
};
function parseAndHighlight(wordstring, htmlString) {
var htmlStringUn = htmlString;
var found = htmlStringUn.toLowerCase().indexOf(wordstring.toLowerCase(), 0);
if (found >= 0) {
htmlStringUn = highlightWords(wordstring, htmlStringUn);
}
else {
//split and parse the beast
var words = wordstring.split(/\W+/);
var allPhrases = [];
allPhrases.push(wordstring);
var i = 0;
i = words.length;
while (i--) {
allPhrases.push(findStringLimit(" ", allPhrases[(words.length - i) - 1].length, allPhrases[(words.length - i) - 1]));
};
i = allPhrases.length;
while (i--) {
if (allPhrases[i] != "") words = words.concat(allPhrases[i]);
};
i = words.length;
while (i--) {
htmlStringUn = highlightWords(words[i], htmlStringUn);
};
};
return htmlStringUn;
}
$(document).on('change', '#myInput', function() {
var myValue = $('#myInput').val(); //get what was typed
$('#found').text(myValue);
myValue = myValue.replace(/^\s+|\s+$/g, ""); //strip whitespace on ends
$('#found').text(myValue + ':stripped:');
var showText = $('#origshower').text();
var newShowString = parseAndHighlight(myValue, showText); //my original highlighter
$('#shower').html(newShowString);
});
#holder{border:red solid 2px; padding: 5px;}
#myInput{width:200px; background-color: aqua;}
span.autoCompleteWord /* this is the word(s) found */
{
font-weight: bold;
background-color: yellow;
}
#shower{border:lime 2px solid;}
<script
src="https://code.jquery.com/jquery-1.12.4.min.js"
integrity="sha256-ZosEbRLbNQzLpnKIkEdrPv7lOy9C27hHQ+Xp8a4MxAQ="
crossorigin="anonymous"></script>
<div id='holder'>
<input id='myInput' type='text' cols='60' rows='2' />Enter Text to match
</div>
<div id='origshower'>This is the span thistle with the whistle that I want matched is this neat</div>
<div id='shower'>none</div>
<div id='found'>enter</div>
You can just call executeCommand with formatBlock. You can find more information here:
https://developer.mozilla.org/en-US/docs/Web/API/Document/execCommand
I am making a text editor for my website, and I need a find-and-replace function that finds each time the text occurs, highlights it, and, if the user chooses to, replaces it. Then it moves onto the next occurrence, and does the same thing.
I saw this code:
var haystackText = "";
function findMyText(needle, replacement) {
if (haystackText.length == 0) {
haystackText = document.getElementById("haystack").innerHTML;
}
var match = new RegExp(needle, "ig");
var replaced = "";
if (replacement.length > 0) {
replaced = haystackText.replace(match, replacement);
}
else {
var boldText = "<div style=\"background-color: yellow; display: inline; font-weight: bold;\">" + needle + "</div>";
replaced = haystackText.replace(match, boldText);
}
document.getElementById("haystack").innerHTML = replaced;
}
but it only works with a div that has text it, and not a textarea, like I have, and also, it finds and/or replaces all of the occurrences at once, instead of iterating through them one by one.
How can I make this work?
If the code is working with <Div> tag then u should work with div tag . just make that div tag editable .
Try this:
<div contenteditable="true">This is an editable div</div>
The contenteditable attribute specifies whether the content of an element is editable or not.
On a random break I found myself wondering if it would be possible to use jQuery to determine a single character within a sentence when it is clicked on.
For example:
This
When the user clicks on first h, jQuery would return this to me.
The only way I could think of doing this would be to wrap each character within the sentence in a span with a class of its letter such as the following example:
<span class="clickable T">T</span>
<span class="clickable h">h</span>
<span class="clickable i">i</span>
<span class="clickable s">s</span>
Followed by a $('.clickable').click(function()) that would return its second class.
My question is: is this the most efficient way to do this?
Obviously wrapping every single letter of the document in span tags is not efficient.
I was able to spin something up that works in Chrome at least. Basically, when you click on a letter, it then triggers a double clicks which selects the word. We get the selection which actually gives us the text of the entire target element. From that, we get the letter that was clicked. We remove the selection and do what we want with the letter.
Fiddle here
$(function(){
$(document).click(function(e){
var target = e.target;
$(target).dblclick();
}).dblclick(function(){
var selection,
node,
text,
start,
end,
letter;
if (window.getSelection) {
selection = document.getSelection();
node = selection.anchorNode;
if (node.nodeType === 3) {
text = node.data;
start = selection.baseOffset;
end = selection.extentOffet;
if (!isNaN(start)) {
letter = text.substr(start, 1);
}
}
window.getSelection().removeAllRanges()
} else if(document.selection) {
//continue work here
}
if (letter) {
alert(letter);
}
});
});
You could return the innerHTML as well with:
$('.clickable').on('click', function(){
alert($(this).html());
});
As for a more efficient way to do it...maybe try this:
in Javascript/jQuery, how to check a specific part of a string and determine if it is a whitespace or letter?
You can do it with this script
$('.clickable').on('click', function(){
var html = $(this).text(); // if you want the text inside the span
var index = $(this).index(); // if you want the position among siblings
var classes = $(this).attr('class').split(" ");
var secondClass = getSecondClass(classes);
});
function getSecondClass(classArray){
if(classArray.length<2){
return null;
}else{
return classArray[1];
}
}
I've also included the html and index variables if you want to do something else with the clicked element.
Basically you split the classes of the element by spaces and then check if the array has less than two elements, in that case it returns null, otherwise it returns the second element.
jsFiddle
Well wrapping all text dyanamically with span tag , it is possible what you were looking for
JS
$(function(){
var lengthText = $('#singlecharacter').text().length;
var textValue = $('#singlecharacter').text();
var textArray = textValue.split('');
var newText = new Array();
for (var i = lengthText - 1; i >= 0; i--) {
newText[i] = "<span class='sp'>"+textArray[i]+"</span>";
};
$('#singlecharacter').html(newText);
$('.sp').click(function()
{
alert($(this).text());
});
});
HTML
<div id='singlecharacter'>THIS</div>
DEMO JSFIDDLE
I have following html<p class="get">This is some content.</p> what I would like to do is to make it like this:<p class="get">This is <span>some</span> content.</p>. To accomplish this I know I should: 1. get my certain text (done that...)
2. Wrap my text in span (done that also...)
3. replace my text with text that contains my span (haven't done it)
My problem is step 3 I just can't figure it out. So how can I replace my "old" text with new one?Thank you guys!!!
Fiddle here
My code looks like this:
$(document).ready(function(){
//get the whole text
var ptext = $(".get").text().split(" ");
var myText = ptext[2];
//alert(ptext[2])
//alert(myText);
if($('.get').text().contains(myText)){
//$('<span>'+myText+'</span>').appendTo($('.get'));
}
else{
alert('no text');
}
});
var word = "some",
regex = new RegExp("\\b" + word + "\\b", "g");
$(".get").html(function(i, html) {
return html.replace(regex, "<span>$&</span>");
});
DEMO: http://jsfiddle.net/V7Wnk/1/
$('.get').html(function (i, v) {
return v.replace('some', '<span>some</span>');
});
$('.get').html($(".get").html().replace("some","<span> some </span>"));
I wanted to detect a span element while looping through a div element i.e.
<div class="example">
These are some <span id ="special" class="anime">special</span> words
that should be faded in one after the other.
</div>
using javascript i split the words and fade them in one by one i.e.
JS:
function doSomething(spanID) {
alert(spanID);
}
var $el = $(".example:first"), text = $.trim($el.text()),
words = text.split(" "), html = "";
for (var i = 0; i < words.length; i++) {
html += "<span>" + words[i] + ((i+1) === words.length ? "" : " ") + "</span>";
};
$el.html(html).children().hide().each(function(i){
// if special span is detected
// get the span id pass it to doSomething function
$(this).delay(i*200).fadeIn(700);
});
$el.find("span").promise().done(function(){
$el.text(function(i, text){
return $.trim(text);
});
});
working example is here: http://jsfiddle.net/6czap/5/
everything works, it just that i need to detect any special spans so i can do something with them, and then the for loop should carry on doing what it deos. thanks
Try the following:
var orig = $(".example:first").html()
$el.html(html).children().hide().each(function(i){
$(this).delay(i*200).fadeIn(700);
}).promise().done(function(){
$('.example:first').html(orig);
});
demo!