How would I find the innerHTML index of text highlighted from textContent? - javascript

I am trying to program a simple text editor for fun.
I am stuck on this problem.
I want to add bold or italics to highlighted text on a button click.
I figure the best way to do this is get the index of the selected text and then add the bold tag / italic tag around the tag in the innerHTML.
However, I can not seem to get the position / index of the selected tag to carry over to the innerHTML. Obviosuly, the innerHTML code is offset by the tags.
Is there an easier way to do this?
I though finding the index of the highlighted text was the way to go. Okay. Unforunately, indexOf will only find the first occurance.
var word_document = document.getElementById('word-document');
/* This code is for our bold button */
var bold_button = document.getElementById('bold-button')
bold_button.addEventListener('click', (event) => {
/* Test to see if text is highlighted */
let text = window.getSelection().toString();
console.log("Selected Text: " + text);
if (text.length > 0) {
// Find the position of the highlighted text in the word document.
let position = word_document.innerHTML.indexOf(text); // Not a good way of doing it
console.log("Pos: ", position);
// Replace the highlighted text from the document with the bold text
word_document.innerHTML.replace(text, "<b>" + text + "</b>");
}
/* If text is not highlighted, add a bold tag */
else {
// Add bold tag to bottom of document
word_document.focus();
word_document.innerHTML += "<b></b>";
word_document.selectionEnd = word_document.innerHTML.length - 6;
}
});
/* This code is for our italic button */
var italic_button = document.getElementById('italics-button');
italic_button.addEventListener('click', function() {
let text = window.getSelection().toString();
// Same issue
});
<button id="bold-button">B</button>
<button id="italics-button">I</button>
<textarea id="word-document">Starting Text</textarea>
I suppose a possible way would be to iterate over the textContent and find if any text prior to the selected text matches it, and then set a variable to skip over that many matches. Is there an easier way to do this. Ideally, I would like to create a bold tag, or italic tag and append it to the textarea in a more proper fashion. I support traversing the DOM is probably a better way. Any ideas on how this might be more easily tackled?
Thanks
I use Plain / Vanilla Javascript.
Edit: Fixed code. Adding JsFiddle here

You can try this :
<html>
<header>
</header>
<body>
<button id="bold-button" onClick="makeBold()">B</button>
<div id="word-document" contenteditable>Starting Text</div>
<script>
function makeBold() {
var inputText = document.getElementById("word-document");
var innerHTML = inputText.innerHTML;
text = window.getSelection().toString();
var index = innerHTML.indexOf(text);
if (index >= 0) {
innerHTML = innerHTML.substring(0,index) + "<span style='font-weight: bold;'>" + innerHTML.substring(index,index+text.length) + "</span>" + innerHTML.substring(index + text.length);
inputText.innerHTML = innerHTML;
}
}
</script>
</html>
the idea here is to use a fake textArea: div with content editable.
I hope it helps u,
Good Luck!

simple dummy solution. this don't work for nested tags.
I highly recommended to read this tutorial
function action({tag, classes}, event){
const text = document.getElementById("word-document");
const selection = window.getSelection();
const range = selection.getRangeAt(0);
const before = text.innerHTML.substr(0, range.startOffset);
const after = text.innerHTML.substr(range.endOffset);
const selected = text.innerHTML.substr(range.startOffset,range.endOffset - range.startOffset );
const warpped = `<${tag} ${classes ? "class=" + classes : ""}>${selected}</${tag}>`
text.innerHTML = before + warpped + after;
}
#word-document {
border: 1px solid black;
padding: 10px;
}
.underline{
text-decoration-line: underline;
}
<button onclick="action({tag: 'b'})">B</button>
<button onclick="action({tag: 'i'})">I</button>
<button onclick="action({tag: 'span', classes:'underline'})">Under score</button>
<div id="word-document" contenteditable>Starting Text</div>

Related

How to continously bold the text in a div while typing

I have a to do app and I was requested to add more functionality, such as: add some buttons for Bold and Italic text. In a input, after I press the Bold button, the text that is going to be typed to be bolded, leaving the previous text (the one before I pressed the Bold button) the way it was, regular.
I understood that there's no way to bold a section of text in a input, so I simulated a input with a div :
const div = document.querySelector('div');
div.innerHTML = '';
const bold = document.getElementById('boldBtn');
const italic = document.getElementById('italicBtn')
bold.style.backgroundColor = 'white';
let previousText = '';
let boldString = '';
boldBtn.addEventListener('click', () => {
boldBtn.classList.toggle('bold-selected');
if (boldBtn.classList.contains('bold-selected')) {
boldBtn.style.backgroundColor = "gray";
previousText = div.innerHTML;
console.log(previousText)
div.addEventListener('keydown', boldText)
}
else {
bold.style.backgroundColor = "white";
div.removeEventListener('keydown', boldText);
}
})
function boldText(e) {
div.innerHTML = div.innerHTML.substr(1)
console.log("Previous text: " + previousText);
const NOT_ALLOWED = ['Backspace', 'Shift', 'Control', 'Alt'];
if (!NOT_ALLOWED.includes(e.key)) {
boldString += e.key;
console.log("Bold text: " + boldString)
console.log(previousText + boldString)
div.innerHTML = previousText + "<strong>" + boldString + "</strong>"
}
}
div {
border: 1px solid black;
width: 200px;
height: 20px;
}
.font-style {
border: 1px solid blue
}
<div contenteditable="true"></div>
<button id="boldBtn" class="font-style">Bold</button>
<button id="italicBtn" class="font-style">Italic</button>
Try to write something like "cool" in the div then press the Bold button, then type a letter. You'll see that the div gets this innerHTML: letter+cool+boldletter. And the cursor is set at the beginning of the div content. Please help me or at least give a hint to accomplish the wanted behavior! I spent 3-4 hours already and I am ready to give up...
EDIT:
I think I didn't make it clear: I don't want to make the entire content of the div be bold, but just a portion/section/part of it. If no button is pressed, the text that is gonna be written should be regular, if the Bold button is pressed, the text that is gonna be written should be Bold, the same with the Italic button. Maybe if the Bold and Italic are selected at the same time, the future text should be bold and italic. But that's another question... An example of what I want is https://html-online.com/editor/ . Remove the default text and just write words on the left panel, and try to use the bold, italic buttons above. In the left panel there will be the HTML generated code. I need the same functionality...
In order to make text bold when you select "bold", get range from current selection and insert element "strong" in it.
let range=window.getSelection.getRangeAt(0);
let elem=document.createElement('strong');
elem.innerHTML='​'
range.insertNode(elem);
this will make the text bold, same applies for the italic option too. Now the problem comes when you need to disable it, for that i had to clear current ranges in selection and set range to end of "div" and insert new element
"span"(i used span for normal text).
Likewise i handled below cases too
when bold and italic are pressed together( You need to store
previous Element in this case)
when enter is pressed( div was
getting created and was creating problems so i had to set keydown
listener and use document.execCommand to not create div instead
create br)
If anyone is interested in updating my solution to make it effective you are welcome
var boldButton=document.querySelector('#boldBtn'),
italicButton=document.querySelector('#italicBtn'),
contentDiv=document.querySelector('#content'),
bold=false,italic=false,range,prevElement,selection;
;
contentDiv.addEventListener('keydown',function(e){
if (e.keyCode === 13) {
window.document.execCommand('insertLineBreak', false, null);
range=window.getSelection().getRangeAt(0);
range.collapse();
prevElement=undefined;
if(bold) addElement('strong');
if(italic) addElement('i');
e.preventDefault();
return false;
}
return true;
});
boldButton.addEventListener('click',function(){
debugger
bold=!bold;
boldButton.classList.toggle('border-black');
if(!bold)
disable('bold');
else
addElement('strong');
});
italicButton.addEventListener('click',function(){
debugger;
italic=!italic;
italicButton.classList.toggle('border-black');
if(!italic)
disable('italic');
else
addElement('i');
});
function addElement(element){
if(!selection)selection=window.getSelection()
range=selection.getRangeAt(0);
let elem=document.createElement(element);
elem.innerHTML='​'
if(prevElement)
range.selectNodeContents(prevElement);
range.collapse();
range.insertNode(elem);
elem.focus();
prevElement=elem;
}
function disable(elem){
if(italic && bold) return;
setRangeAtEnd();
let element=document.createElement('span');
if(italic)
element=document.createElement('i');
else if(bold)
element=document.createElement('strong');
element.innerHTML='​'
range.insertNode(element);
range.setStart(element,1);
element.focus();
}
function setRangeAtEnd(){
let childNodes=contentDiv.childNodes;
range=document.createRange();
range.setStartAfter(childNodes[childNodes.length-1]);
prevElement=undefined;
if(!selection)selection=window.getSelection();
selection.removeAllRanges();
selection.addRange(range);
}
#content{
border:1px solid black;
height:150px;
}
.border-black{
border:2px dotted black;
}
<div id="content" contenteditable="true"></div><br>
<button id="boldBtn" class="font-style">Bold</button>
<button id="italicBtn" class="font-style">Italic</button>
Yes there is a way out for this as you can use jquery for this like
<script>
$(document).ready(function () {
$("#boldBtn").click( function() {
$("your input id or class").keyup(function () {
$("your input id or class").css("fontWeight", "bold");
});
});
});
</script>
Where your input id or class mentioned if you have class then put it as .classname or if you have an id write is as #id.
Hope this might help you.

Add html tags around highligted text in contenteditable div

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

replacing hashtags with anchor links in realtime

I want text within a textarea that has the hash # character to be replaced with an anchor link as they're typing.
eg. something #somethingelse somethingsomethingelse its actual code would be
something #somethingelse somethingelse
but in the textarea, I'd only want #somethingelse to be highlighted as I don't want it to actually have anchor code, kinda like how twitter and fb does it.
how does it work?
$('textarea').on("keyup", function() {
var str = $(this).val();
if (!str.match(/(http|ftp|https):\/\/[\w-]+(\.[\w-]+)+([\w.,#?^=%&:\/~+#-]*[\w#?^=%&\/~+#-])?#([a-zA-Z0-9]+)/g)) {
if (!str.match(/#([a-zA-Z0-9_]+)#/g)) {
str = str.replace(/#([a-zA-Z0-9_]+)/g, '#$1');
$('textarea').html(str)
}
}
});
.hashtag {
background: #000;
color: #fff;
}
I write it in javascript :
Html :
<textarea id="textArea" onkeyup="changeHash()"></textarea>
Javascript :
var outputString="";
function changeHash(){
var getObject= document.getElementById('textArea');
outputString =getObject.value.toString();
var checkTheLastChar = outputString.slice(-1);
if(checkTheLastChar=="#"){
outputString = outputString.substring(0, outputString.length - 1);
outputString += "⚓"; //Change it with anything you want
}
getObject.value = "";
getObject.value = outputString;
}
https://jsfiddle.net/emilvr/q27xgshe/1/
What you need to do is have a div below a transparent text area and duplicate the text from the textarea into the div with the links appended. If you append the text for a html tag to a text area it won't render because anything in a textarea only renders as editable text.

Getting text selection with javascript, then modify text

I have a text on an HTML page. If the user selects a word, I can retrieve the selected word and know exactly what he or she selected. However, I now want to also modify this word on the screen and make it bold. But I do not know how I would do this, since I only know the word clicked, but do not see an easy way to find the word in the HTML and modify it. I could of course search for it, but there is the risk of a word appearing multiple times.
A different way I thought about would be to give each word a unique idea and have something like this:
<span id="1">The</span>
<span id="2">fox</span>
<span id="3">jumps</span>
<span id="4">over</span>
<span id="5">the</span>
<span id="6">fence</span>
But I do not like this solution. This, too, seems overly complicated, does it not? Any suggestions how else I could access the exact words selected?
You can dynamically create a span surrounding the selected word:
const p = document.querySelector('p');
p.addEventListener('mouseup', () => {
const range = document.getSelection().getRangeAt(0);
do {
const charBefore = range.startContainer.textContent[range.startOffset - 1];
if (charBefore.match(/\s/)) break;
range.setStart(range.startContainer, range.startOffset - 1);
} while (range.startOffset !== 0);
do {
const charAfter = range.endContainer.textContent[range.endOffset];
if (charAfter.match(/\s/)) break;
range.setEnd(range.endContainer, range.endOffset + 1);
} while (range.endOffset !== range.endContainer.textContent.length);
const span = document.createElement('span');
span.style.fontWeight = 'bold';
range.surroundContents(span);
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<p>The fox jumps over the fence.</p>
No need of jQuery, also no need of IDs for each <span>.
The idea is to add a class to the span once it is clicked and later you can retrieve all elements with that bolded class.
Here is a solution with pure Javascript:
// comments inline
var spans = document.getElementsByTagName('span'); // get all <span> elements
for(var i=0, l = spans.length; i<l; i++){
spans[i].addEventListener('click', function(){ // add 'click' event listener to all spans
this.classList.toggle('strong'); // add class 'strong' when clicked and remove it when clicked again
});
}
.strong {
font-weight: bold;
}
<span>The</span>
<span>fox</span>
<span>jumps</span>
<span>over</span>
<span>the</span>
<span>fence</span>
Read up: Element.getElementsByTagName() - Web APIs | MDN
$("p").mouseup(function() {
var selection = getSelected().toString();
$(this).html(function(){
return $(this).text().replace(selection, '<strong>' + selection +'</strong>');
});
});
var getSelected = function(){
var t = '';
if(window.getSelection) {
t = window.getSelection();
} else if(document.getSelection) {
t = document.getSelection();
} else if(document.selection) {
t = document.selection.createRange().text;
}
return t;
}
strong{ font-weight: bold; }
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<p>The fox jumps over the fence.</p>

find-and-replace iteration with javascript

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.

Categories

Resources