how to insert a link to selected text - javascript

I am trying to write a function to add links , so that my users can select a text and attach a link to the text. ( I am trying to imitate how gmail easily lets us to add,delete or edit link to a selected text inside the email)
Here is the code what I came up after Google'ng for a solution.
var sel, range;
if (window.getSelection) {
sel = window.getSelection();
if (sel.rangeCount) {
range = sel.getRangeAt(0);
var temp = range;
var link =prompt("Please enter the link for this selection \n "+range+"","");
range.insertNode(document.createTextNode("<a href='"+link+"' target='_blank'>"+temp+"</a>"));
}
} else if (document.selection && document.selection.createRange) {
range = document.selection.createRange();
var link =prompt("Please enter the link for this selection \n "+range.text+"","");
range.text = "<a href='"+link+"' target='_blank' onClick='changeLink()'>"+range.text+"</a>";
}
The code doesn't work as I imagined, the link gets added to the content but it is showing the <a> tag also inside the content rather then showing the default blue colored text with underline. How to hide the <a> tag ? also how will I allow them to delete or edit the link.
(UPDATED) Solution :
function addFormat(type) {
var savedSel = saveSelection();
if(savedSel != '') {
var link =prompt("Please enter the link for this selection \n "+savedSel+"","");
restoreSelection(savedSel);
document.execCommand("CreateLink", false, link);
var links = getLinksInSelection();
for (var i = 0; i < links.length; ++i) {
links[i].setAttribute('target','_blank');
}
} else { alert("Please select some text to insert the link"); }
}
function getLinksInSelection() {
var selectedLinks = [];
var range, containerEl, links, linkRange;
if (window.getSelection) {
sel = window.getSelection();
if (sel.getRangeAt && sel.rangeCount) {
linkRange = document.createRange();
for (var r = 0; r < sel.rangeCount; ++r) {
range = sel.getRangeAt(r);
containerEl = range.commonAncestorContainer;
if (containerEl.nodeType != 1) {
containerEl = containerEl.parentNode;
}
if (containerEl.nodeName.toLowerCase() == "a") {
selectedLinks.push(containerEl);
} else {
links = containerEl.getElementsByTagName("a");
for (var i = 0; i < links.length; ++i) {
linkRange.selectNodeContents(links[i]);
if (linkRange.compareBoundaryPoints(range.END_TO_START, range) < 1 && linkRange.compareBoundaryPoints(range.START_TO_END, range) > -1) {
selectedLinks.push(links[i]);
}
}
}
}
linkRange.detach();
}
} else if (document.selection && document.selection.type != "Control") {
range = document.selection.createRange();
containerEl = range.parentElement();
if (containerEl.nodeName.toLowerCase() == "a") {
selectedLinks.push(containerEl);
} else {
links = containerEl.getElementsByTagName("a");
linkRange = document.body.createTextRange();
for (var i = 0; i < links.length; ++i) {
linkRange.moveToElementText(links[i]);
if (linkRange.compareEndPoints("StartToEnd", range) > -1 && linkRange.compareEndPoints("EndToStart", range) < 1) {
selectedLinks.push(links[i]);
}
}
}
}
return selectedLinks;
}
function saveSelection() {
if (window.getSelection) {
sel = window.getSelection();
if (sel.getRangeAt && sel.rangeCount) {
var ranges = [];
for (var i = 0, len = sel.rangeCount; i < len; ++i) {
ranges.push(sel.getRangeAt(i));
}
return ranges;
}
} else if (document.selection && document.selection.createRange) {
return document.selection.createRange();
}
return null;
}
function restoreSelection(savedSel) {
if (savedSel) {
if (window.getSelection) {
sel = window.getSelection();
sel.removeAllRanges();
for (var i = 0, len = savedSel.length; i < len; ++i) {
sel.addRange(savedSel[i]);
}
} else if (document.selection && savedSel.select) {
savedSel.select();
}
}
}
Thanks.

Related

How can I get and set the caret position in editable div if there is not only text, but also other html nodes in this div (span, div...)?

I'm trying to adjust caret position in editable DIV, but I can’t do it if DIV contains other nodes. Can someone write me how to do this? Thanks a lot.
I can enter text, and I can enter certain characters that will be inserted into this div in some kind of wrapper (span, div and etc), how can I get the caret position if the div contains other nodes?
For example, I typed a few characters and inserted my token (bracket).
First bracket inserted in span wrapper, but I can't get right position for next one:
<div contenteditable="true">12<span class="with-token">[</span>34[5</div>
***To get:***
const getNodeOffset = node => (node == null ? -1 : 1 +
getNodeOffset(node.previousSibling));
const getNodeTextLength = (node) => {
let textLength = 0;
if (node.nodeName === 'BR') {
textLength = 1;
} else if (node.nodeName === '#text') {
textLength = node.nodeValue.length;
} else if (node.childNodes != null) {
for (let i = 0; i < node.childNodes.length; i += 1) {
textLength += getNodeTextLength(node.childNodes[i]);
}
}
return textLength;
};
const getTextLength = (parent, node, offset) => {
let textLength = 0;
if (node && node.nodeName === '#text') {
textLength += offset;
} else {
for (let i = 0; i < offset; i += 1) {
textLength += getNodeTextLength(node.childNodes[i]);
}
}
if (node !== parent) {
textLength += getTextLength(parent, node.parentNode,
getNodeOffset(node));
}
return textLength;
};
const getTextSelection = (editor) => {
const selection = window.getSelection();
if (selection != null && selection.rangeCount > 0) {
const range = selection.getRangeAt(0);
return {
start: getTextLength(editor, range.startContainer,
range.startOffset),
end: getTextLength(editor, range.endContainer, range.endOffset)
};
} else return null;
};
***To set:***
function createRange(node, chars, range) {
let currentRange = range;
if (!currentRange) {
currentRange = document.createRange();
currentRange.selectNode(node);
currentRange.setStart(node, 0);
}
if (chars.count === 0) {
currentRange.setEnd(node, chars.count);
} else if (node && chars.count > 0) {
if (node.nodeType === Node.TEXT_NODE) {
if (node.textContent.length < chars.count) {
chars.count -= node.textContent.length;
} else {
currentRange.setEnd(node, chars.count);
chars.count = 0;
}
} else {
for (let lp = 0; lp < node.childNodes.length; lp += 1) {
currentRange = createRange(node.childNodes[lp], chars,
currentRange);
if (chars.count === 0) {
break;
}
}
}
}
return currentRange;}
function setCurrentCursorPosition(node, chars) {
if (chars >= 0) {
const selection = window.getSelection();
const range = createRange(node, { count: chars });
if (range) {
range.collapse(false);
selection.removeAllRanges();
selection.addRange(range);
}
}
}

Select a portion of text within a HTML element

I would like to create a function that select a given text inside a HTML element.
For example calling selectText('world') would select world in a markup like <span>Hello </span><strong>world</strong>!
Lots of answers on similar questions suggests to use range and selection but none of them work in my case (some would select all the text, some won't work with such markup, ...).
For now this is what I have (it doesn't work):
function selectText ( element, textToSelect ) {
var text = element.textContent,
start = text.indexOf( textToSelect ),
end = start + textToSelect.length - 1,
selection, range;
element.focus();
if( window.getSelection && document.createRange ) {
range = document.createRange();
range.setStart( element.firstChild, start );
range.setEnd( element.lastChild, end );
selection = window.getSelection();
selection.removeAllRanges();
selection.addRange( range );
} else if (document.body.createTextRange) {
range = document.body.createTextRange();
range.moveToElementText( element );
range.moveStart( 'character', start );
range.collapse( true );
range.moveEnd( 'character', end );
range.select();
}
}
Here is a jsfiddle so you see what is actually happening: http://jsfiddle.net/H2H2p/
Outputed error :
Uncaught IndexSizeError: Failed to execute 'setStart' on 'Range': The offset 11 is larger than or equal to the node's length (5).
P.S.: no jQuery please :)
You could use a combination of your approach of finding the text within the element's textContent and this function.
Demo: http://jsfiddle.net/H2H2p/3/
Code:
function selectText(element, textToSelect) {
var elementText;
if (typeof element.textContent == "string" && document.createRange && window.getSelection) {
elementText = element.textContent;
} else if (document.selection && document.body.createTextRange) {
var textRange = document.body.createTextRange();
textRange.moveToElement(element);
elementText = textRange.text;
}
var startIndex = elementText.indexOf(textToSelect);
setSelectionRange(element, startIndex, startIndex + textToSelect.length);
}
function getTextNodesIn(node) {
var textNodes = [];
if (node.nodeType == 3) {
textNodes.push(node);
} else {
var children = node.childNodes;
for (var i = 0, len = children.length; i < len; ++i) {
textNodes.push.apply(textNodes, getTextNodesIn(children[i]));
}
}
return textNodes;
}
function setSelectionRange(el, start, end) {
if (document.createRange && window.getSelection) {
var range = document.createRange();
range.selectNodeContents(el);
var textNodes = getTextNodesIn(el);
var foundStart = false;
var charCount = 0, endCharCount;
for (var i = 0, textNode; textNode = textNodes[i++]; ) {
endCharCount = charCount + textNode.length;
if (!foundStart && start >= charCount
&& (start < endCharCount ||
(start == endCharCount && i < textNodes.length))) {
range.setStart(textNode, start - charCount);
foundStart = true;
}
if (foundStart && end <= endCharCount) {
range.setEnd(textNode, end - charCount);
break;
}
charCount = endCharCount;
}
var sel = window.getSelection();
sel.removeAllRanges();
sel.addRange(range);
} else if (document.selection && document.body.createTextRange) {
var textRange = document.body.createTextRange();
textRange.moveToElementText(el);
textRange.collapse(true);
textRange.moveEnd("character", end);
textRange.moveStart("character", start);
textRange.select();
}
}

getSelection returning text after cleared

JS Fiddle
JS
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;
}
}
console.log(html); <-- returning text even not selected.
}
$(document).ready(function(){
$(document).bind("mouseup", getSelectionHtml);
});
I'm currently trying to understand the following behavior:
1) Select a few lines of text (console.log shows those lines) - expected.
2) Click within the selection you've made. Console.log then shows the same text as the previous, which was selected. - Not expected; here I expect getSelection to return nothing as nothing is currently selected.
Can anyone tell me what i'm missing here?
Thanks!
DEMO jsFiddle
JS
var previousText = '';
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;
}
}
if(html!= previousText) {
console.log(html);
}
previousText = html;
}
$(document).mousedown(function () {}).mouseup( function () {
getSelectionHtml();
});
To not show empty selections just change:
if(html!= previousText) {
to this:
if(html!= previousText && html != '') {
Note: I'm using jQuery because you were too

How to Copy the HTML Selection String with TextFormat information

I have seen answers given for Copying HTML using javascript. Almost all answers were to use clonecontents as Below
function() {
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;
};
But here if the tags are there in the selection region, then only formatting is getting copied, else it will be copied as a Text itself. I want to copy the formatting information associated with the selection. How can i achieve this.
Test it can select with parent node
function(){
var html = "";
if (typeof window.getSelection != "undefined") {
var sel = window.getSelection();
if (sel.rangeCount) {
var Node=sel.focusNode.parentNode.cloneNode(true);
//console.log(Node);
var container = document.createElement("div");
for (var i = 0, len = sel.rangeCount; i < len; ++i) {
container.appendChild(sel.getRangeAt(i).cloneContents());
}
html = container.innerHTML;
//
Node.innerHTML=html;
var co = document.createElement("div");
co.appendChild(Node);
html=co.innerHTML;
}
} else if (typeof document.selection != "undefined") {
if (document.selection.type == "Text") {
html = document.selection.createRange().htmlText;
}
}
return html;
}

Need to be able to add a text to arbitrary location in a div tag as a marker

I have a div tag in my page that could have an arbitrary amount of child nodes. But there is a certain length at which i need to slice it and only show the sliced text. This is the code i have:
var myDiv = document.getElementById("myDiv")
var range = document.body.createTextRange();
range.moveToElementText(myDiv);
range.move("character",150);
range.text = "!!!";
var html = myDiv.innerHTML;
html = html.slice(0,html.indexOf("!!!"));//+"...";
myDiv.innerHTML = html;
I am doing it this way so that i can conserve the html on the value of the div and at the same time i can make sure that i am not slicing in between a tag. This works fine in IE but obviously dosent so in firefox. Can anybody help me with giving me a equivalent code for firefox.
Thanks in advance!
Here's some code to extract the HTML for the first 150 characters of a <div>. It's based on this answer and the same caveats about the naivete of the implementation apply.
Live demo: http://jsfiddle.net/mrEme/2/
Code:
function getTextNodesIn(node) {
var textNodes = [];
if (node.nodeType == 3) {
textNodes.push(node);
} else {
var children = node.childNodes;
for (var i = 0, len = children.length; i < len; ++i) {
textNodes.push.apply(textNodes, getTextNodesIn(children[i]));
}
}
return textNodes;
}
function copyCharacterRange(srcEl, destEl, start, end) {
if (document.createRange && window.getSelection) {
var range = document.createRange();
range.selectNodeContents(srcEl);
var textNodes = getTextNodesIn(srcEl);
var foundStart = false;
var charCount = 0, endCharCount;
for (var i = 0, textNode; textNode = textNodes[i++]; ) {
endCharCount = charCount + textNode.length;
if (!foundStart && start >= charCount
&& (start < endCharCount ||
(start == endCharCount && i < textNodes.length))) {
range.setStart(textNode, start - charCount);
foundStart = true;
}
if (foundStart && end <= endCharCount) {
range.setEnd(textNode, end - charCount);
break;
}
charCount = endCharCount;
}
destEl.appendChild(range.cloneContents());
range.detach();
} else if (document.selection && document.body.createTextRange) {
var textRange = document.body.createTextRange();
textRange.moveToElementText(srcEl);
textRange.collapse(true);
textRange.moveEnd("character", end);
textRange.moveStart("character", start);
destEl.innerHTML = textRange.htmlText;
}
}
var srcEl = document.getElementById("src");
var destEl = document.getElementById("dest");
copyCharacterRange(srcEl, destEl, 0, 150);

Categories