How to add extra info to copied web text - javascript

Some websites now use a JavaScript service from Tynt that appends text to copied content.
If you copy text from a site using this and then paste you get a link to the original content at the bottom of the text.
Tynt also tracks this as it happens. It's a neat trick well done.
Their script for doing this is impressive - rather than try to manipulate the clipboard (which only older versions of IE lets them do by default and which should always be turned off) they manipulate the actual selection.
So when you select a block of text the extra content is added as a hidden <div> included in your selection. When you paste the extra style is ignored and the extra link appears.
This is actually fairly easy to do with simple blocks of text, but a nightmare when you consider all the selections possible across complex HTML in different browsers.
I'm developing a web application - I don't want anyone to be able to track the content copied and I would like the extra info to contain something contextual, rather than just a link. Tynt's service isn't really appropriate in this case.
Does anyone know of an open source JavaScript library (maybe a jQuery plug in or similar) that provides similar functionality but that doesn't expose internal application data?

2022 Update
More complex solution that handles rich text formatting. The 2020 solution is still relevant if you only deal with plain text.
const copyListener = (event) => {
const range = window.getSelection().getRangeAt(0),
rangeContents = range.cloneContents(),
pageLink = `Read more at: ${document.location.href}`,
helper = document.createElement("div");
helper.appendChild(rangeContents);
event.clipboardData.setData("text/plain", `${helper.innerText}\n${pageLink}`);
event.clipboardData.setData("text/html", `${helper.innerHTML}<br>${pageLink}`);
event.preventDefault();
};
document.addEventListener("copy", copyListener);
#richText {
width: 415px;
height: 70px;
border: 1px solid #777;
overflow: scroll;
}
#richText:empty:before {
content: "Paste your copied text here";
color: #888;
}
<h4>Rich text:</h4>
<p>Lorem <u>ipsum</u> dolor sit <b>amet</b>, consectetur <i>adipiscing</i> elit.</p>
<h4>Plain text editor:</h4>
<textarea name="textarea" rows="5" cols="50" placeholder="Paste your copied text here"></textarea>
<h4>Rich text editor:</h4>
<div id="richText" contenteditable="true"></div>
2020 Update
Solution that works on all recent browsers.
Note that this solution will strip rich text formatting (such as bold and italic), even when pasting into a rich text editor.
document.addEventListener('copy', (event) => {
const pagelink = `\n\nRead more at: ${document.location.href}`;
event.clipboardData.setData('text/plain', document.getSelection() + pagelink);
event.preventDefault();
});
Lorem ipsum dolor sit <b>amet</b>, consectetur <i>adipiscing</i> elit.<br/>
<textarea name="textarea" rows="7" cols="50" placeholder="paste your copied text here"></textarea>
[Older post - before the 2020 update]
There are two main ways to add extra info to copied web text.
Manipulating the selection
The idea is to watch for the copy event, then append a hidden container with our extra info to the dom, and extend the selection to it.
This method is adapted from this article by c.bavota. Check also jitbit's version for more complex case.
Browser compatibility: All major browsers, IE > 8.
Demo: jsFiddle demo.
Javascript code:
function addLink() {
//Get the selected text and append the extra info
var selection = window.getSelection(),
pagelink = '<br /><br /> Read more at: ' + document.location.href,
copytext = selection + pagelink,
newdiv = document.createElement('div');
//hide the newly created container
newdiv.style.position = 'absolute';
newdiv.style.left = '-99999px';
//insert the container, fill it with the extended text, and define the new selection
document.body.appendChild(newdiv);
newdiv.innerHTML = copytext;
selection.selectAllChildren(newdiv);
window.setTimeout(function () {
document.body.removeChild(newdiv);
}, 100);
}
document.addEventListener('copy', addLink);
Manipulating the clipboard
The idea is to watch the copy event and directly modify the clipboard data. This is possible using the clipboardData property. Note that this property is available in all major browsers in read-only; the setData method is only available on IE.
Browser compatibility: IE > 4.
Demo: jsFiddle demo.
Javascript code:
function addLink(event) {
event.preventDefault();
var pagelink = '\n\n Read more at: ' + document.location.href,
copytext = window.getSelection() + pagelink;
if (window.clipboardData) {
window.clipboardData.setData('Text', copytext);
}
}
document.addEventListener('copy', addLink);

This is a vanilla javascript solution from a modified solution above but supports more browsers (cross browser method)
function addLink(e) {
e.preventDefault();
var pagelink = '\nRead more: ' + document.location.href,
copytext = window.getSelection() + pagelink;
clipdata = e.clipboardData || window.clipboardData;
if (clipdata) {
clipdata.setData('Text', copytext);
}
}
document.addEventListener('copy', addLink);

The shortest version for jQuery that I tested and is working is:
jQuery(document).on('copy', function(e)
{
var sel = window.getSelection();
var copyFooter =
"<br /><br /> Source: <a href='" + document.location.href + "'>" + document.location.href + "</a><br />© YourSite";
var copyHolder = $('<div>', {html: sel+copyFooter, style: {position: 'absolute', left: '-99999px'}});
$('body').append(copyHolder);
sel.selectAllChildren( copyHolder[0] );
window.setTimeout(function() {
copyHolder.remove();
},0);
});

Here is a plugin in jquery to do that
https://github.com/niklasvh/jquery.plugin.clipboard
From the project readme
"This script modifies the contents of a selection prior to a copy event being called, resulting in the copied selection being different from what the user selected.
This allows you to append/prepend content to the selection, such as copyright information or other content.
Released under MIT License"

Improving on the answer, restore selection after the alterations to prevent random selections after copy.
function addLink() {
//Get the selected text and append the extra info
var selection = window.getSelection(),
pagelink = '<br /><br /> Read more at: ' + document.location.href,
copytext = selection + pagelink,
newdiv = document.createElement('div');
var range = selection.getRangeAt(0); // edited according to #Vokiel's comment
//hide the newly created container
newdiv.style.position = 'absolute';
newdiv.style.left = '-99999px';
//insert the container, fill it with the extended text, and define the new selection
document.body.appendChild(newdiv);
newdiv.innerHTML = copytext;
selection.selectAllChildren(newdiv);
window.setTimeout(function () {
document.body.removeChild(newdiv);
selection.removeAllRanges();
selection.addRange(range);
}, 100);
}
document.addEventListener('copy', addLink);

Improvement for 2018
document.addEventListener('copy', function (e) {
var selection = window.getSelection();
e.clipboardData.setData('text/plain', $('<div/>').html(selection + "").text() + "\n\n" + 'Source: ' + document.location.href);
e.clipboardData.setData('text/html', selection + '<br /><br />Source');
e.preventDefault();
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<p>Example text with <b>bold</b> and <i>italic</i>. Try copying and pasting me into a rich text editor.</p>

Also a little shorter solution:
jQuery( document ).ready( function( $ )
{
function addLink()
{
var sel = window.getSelection();
var pagelink = "<br /><br /> Source: <a href='" + document.location.href + "'>" + document.location.href + "</a><br />© text is here";
var div = $( '<div>', {style: {position: 'absolute', left: '-99999px'}, html: sel + pagelink} );
$( 'body' ).append( div );
sel.selectAllChildren( div[0] );
div.remove();
}
document.oncopy = addLink;
} );

It's a compilation of 2 answers above + compatibility with Microsoft Edge.
I've also added a restore of the original selection at the end, as it is expected by default in any browser.
function addCopyrightInfo() {
//Get the selected text and append the extra info
var selection, selectedNode, html;
if (window.getSelection) {
var selection = window.getSelection();
if (selection.rangeCount) {
selectedNode = selection.getRangeAt(0).startContainer.parentNode;
var container = document.createElement("div");
container.appendChild(selection.getRangeAt(0).cloneContents());
html = container.innerHTML;
}
}
else {
console.debug("The text [selection] not found.")
return;
}
// Save current selection to resore it back later.
var range = selection.getRangeAt(0);
if (!html)
html = '' + selection;
html += "<br/><br/><small><span>Source: </span><a target='_blank' title='" + document.title + "' href='" + document.location.href + "'>" + document.title + "</a></small><br/>";
var newdiv = document.createElement('div');
//hide the newly created container
newdiv.style.position = 'absolute';
newdiv.style.left = '-99999px';
// Insert the container, fill it with the extended text, and define the new selection.
selectedNode.appendChild(newdiv); // *For the Microsoft Edge browser so that the page wouldn't scroll to the bottom.
newdiv.innerHTML = html;
selection.selectAllChildren(newdiv);
window.setTimeout(function () {
selectedNode.removeChild(newdiv);
selection.removeAllRanges();
selection.addRange(range); // Restore original selection.
}, 5); // Timeout is reduced to 10 msc for Microsoft Edge's sake so that it does not blink very noticeably.
}
document.addEventListener('copy', addCopyrightInfo);

Related

How to add extra info to copied web text [Beginning and End]

Following javascript is used to append a link at the end of the copied text. I want to add the link in the beginning of the text, not at the bottom. How can I do it?
EDIT: Got the answer for the above question, now i would like to know how to add it in the beginning and end at the same time. Also would like to know how to add it in the middle of the copied text.
function addLink() {
//Get the selected text and append the extra info
var selection = window.getSelection(),
pagelink = '<br /><br /> Read more at: ' + document.location.href,
copytext = selection + pagelink,
newdiv = document.createElement('div');
//hide the newly created container
newdiv.style.position = 'absolute';
newdiv.style.left = '-99999px';
//insert the container, fill it with the extended text, and define the new selection
document.body.appendChild(newdiv);
newdiv.innerHTML = copytext;
selection.selectAllChildren(newdiv);
window.setTimeout(function () {
document.body.removeChild(newdiv);
}, 100);
}
document.addEventListener('copy', addLink);

When copying selected text extra text is copied, How does it work? [duplicate]

Some websites now use a JavaScript service from Tynt that appends text to copied content.
If you copy text from a site using this and then paste you get a link to the original content at the bottom of the text.
Tynt also tracks this as it happens. It's a neat trick well done.
Their script for doing this is impressive - rather than try to manipulate the clipboard (which only older versions of IE lets them do by default and which should always be turned off) they manipulate the actual selection.
So when you select a block of text the extra content is added as a hidden <div> included in your selection. When you paste the extra style is ignored and the extra link appears.
This is actually fairly easy to do with simple blocks of text, but a nightmare when you consider all the selections possible across complex HTML in different browsers.
I'm developing a web application - I don't want anyone to be able to track the content copied and I would like the extra info to contain something contextual, rather than just a link. Tynt's service isn't really appropriate in this case.
Does anyone know of an open source JavaScript library (maybe a jQuery plug in or similar) that provides similar functionality but that doesn't expose internal application data?
2022 Update
More complex solution that handles rich text formatting. The 2020 solution is still relevant if you only deal with plain text.
const copyListener = (event) => {
const range = window.getSelection().getRangeAt(0),
rangeContents = range.cloneContents(),
pageLink = `Read more at: ${document.location.href}`,
helper = document.createElement("div");
helper.appendChild(rangeContents);
event.clipboardData.setData("text/plain", `${helper.innerText}\n${pageLink}`);
event.clipboardData.setData("text/html", `${helper.innerHTML}<br>${pageLink}`);
event.preventDefault();
};
document.addEventListener("copy", copyListener);
#richText {
width: 415px;
height: 70px;
border: 1px solid #777;
overflow: scroll;
}
#richText:empty:before {
content: "Paste your copied text here";
color: #888;
}
<h4>Rich text:</h4>
<p>Lorem <u>ipsum</u> dolor sit <b>amet</b>, consectetur <i>adipiscing</i> elit.</p>
<h4>Plain text editor:</h4>
<textarea name="textarea" rows="5" cols="50" placeholder="Paste your copied text here"></textarea>
<h4>Rich text editor:</h4>
<div id="richText" contenteditable="true"></div>
2020 Update
Solution that works on all recent browsers.
Note that this solution will strip rich text formatting (such as bold and italic), even when pasting into a rich text editor.
document.addEventListener('copy', (event) => {
const pagelink = `\n\nRead more at: ${document.location.href}`;
event.clipboardData.setData('text/plain', document.getSelection() + pagelink);
event.preventDefault();
});
Lorem ipsum dolor sit <b>amet</b>, consectetur <i>adipiscing</i> elit.<br/>
<textarea name="textarea" rows="7" cols="50" placeholder="paste your copied text here"></textarea>
[Older post - before the 2020 update]
There are two main ways to add extra info to copied web text.
Manipulating the selection
The idea is to watch for the copy event, then append a hidden container with our extra info to the dom, and extend the selection to it.
This method is adapted from this article by c.bavota. Check also jitbit's version for more complex case.
Browser compatibility: All major browsers, IE > 8.
Demo: jsFiddle demo.
Javascript code:
function addLink() {
//Get the selected text and append the extra info
var selection = window.getSelection(),
pagelink = '<br /><br /> Read more at: ' + document.location.href,
copytext = selection + pagelink,
newdiv = document.createElement('div');
//hide the newly created container
newdiv.style.position = 'absolute';
newdiv.style.left = '-99999px';
//insert the container, fill it with the extended text, and define the new selection
document.body.appendChild(newdiv);
newdiv.innerHTML = copytext;
selection.selectAllChildren(newdiv);
window.setTimeout(function () {
document.body.removeChild(newdiv);
}, 100);
}
document.addEventListener('copy', addLink);
Manipulating the clipboard
The idea is to watch the copy event and directly modify the clipboard data. This is possible using the clipboardData property. Note that this property is available in all major browsers in read-only; the setData method is only available on IE.
Browser compatibility: IE > 4.
Demo: jsFiddle demo.
Javascript code:
function addLink(event) {
event.preventDefault();
var pagelink = '\n\n Read more at: ' + document.location.href,
copytext = window.getSelection() + pagelink;
if (window.clipboardData) {
window.clipboardData.setData('Text', copytext);
}
}
document.addEventListener('copy', addLink);
This is a vanilla javascript solution from a modified solution above but supports more browsers (cross browser method)
function addLink(e) {
e.preventDefault();
var pagelink = '\nRead more: ' + document.location.href,
copytext = window.getSelection() + pagelink;
clipdata = e.clipboardData || window.clipboardData;
if (clipdata) {
clipdata.setData('Text', copytext);
}
}
document.addEventListener('copy', addLink);
The shortest version for jQuery that I tested and is working is:
jQuery(document).on('copy', function(e)
{
var sel = window.getSelection();
var copyFooter =
"<br /><br /> Source: <a href='" + document.location.href + "'>" + document.location.href + "</a><br />© YourSite";
var copyHolder = $('<div>', {html: sel+copyFooter, style: {position: 'absolute', left: '-99999px'}});
$('body').append(copyHolder);
sel.selectAllChildren( copyHolder[0] );
window.setTimeout(function() {
copyHolder.remove();
},0);
});
Here is a plugin in jquery to do that
https://github.com/niklasvh/jquery.plugin.clipboard
From the project readme
"This script modifies the contents of a selection prior to a copy event being called, resulting in the copied selection being different from what the user selected.
This allows you to append/prepend content to the selection, such as copyright information or other content.
Released under MIT License"
Improving on the answer, restore selection after the alterations to prevent random selections after copy.
function addLink() {
//Get the selected text and append the extra info
var selection = window.getSelection(),
pagelink = '<br /><br /> Read more at: ' + document.location.href,
copytext = selection + pagelink,
newdiv = document.createElement('div');
var range = selection.getRangeAt(0); // edited according to #Vokiel's comment
//hide the newly created container
newdiv.style.position = 'absolute';
newdiv.style.left = '-99999px';
//insert the container, fill it with the extended text, and define the new selection
document.body.appendChild(newdiv);
newdiv.innerHTML = copytext;
selection.selectAllChildren(newdiv);
window.setTimeout(function () {
document.body.removeChild(newdiv);
selection.removeAllRanges();
selection.addRange(range);
}, 100);
}
document.addEventListener('copy', addLink);
Improvement for 2018
document.addEventListener('copy', function (e) {
var selection = window.getSelection();
e.clipboardData.setData('text/plain', $('<div/>').html(selection + "").text() + "\n\n" + 'Source: ' + document.location.href);
e.clipboardData.setData('text/html', selection + '<br /><br />Source');
e.preventDefault();
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<p>Example text with <b>bold</b> and <i>italic</i>. Try copying and pasting me into a rich text editor.</p>
Also a little shorter solution:
jQuery( document ).ready( function( $ )
{
function addLink()
{
var sel = window.getSelection();
var pagelink = "<br /><br /> Source: <a href='" + document.location.href + "'>" + document.location.href + "</a><br />© text is here";
var div = $( '<div>', {style: {position: 'absolute', left: '-99999px'}, html: sel + pagelink} );
$( 'body' ).append( div );
sel.selectAllChildren( div[0] );
div.remove();
}
document.oncopy = addLink;
} );
It's a compilation of 2 answers above + compatibility with Microsoft Edge.
I've also added a restore of the original selection at the end, as it is expected by default in any browser.
function addCopyrightInfo() {
//Get the selected text and append the extra info
var selection, selectedNode, html;
if (window.getSelection) {
var selection = window.getSelection();
if (selection.rangeCount) {
selectedNode = selection.getRangeAt(0).startContainer.parentNode;
var container = document.createElement("div");
container.appendChild(selection.getRangeAt(0).cloneContents());
html = container.innerHTML;
}
}
else {
console.debug("The text [selection] not found.")
return;
}
// Save current selection to resore it back later.
var range = selection.getRangeAt(0);
if (!html)
html = '' + selection;
html += "<br/><br/><small><span>Source: </span><a target='_blank' title='" + document.title + "' href='" + document.location.href + "'>" + document.title + "</a></small><br/>";
var newdiv = document.createElement('div');
//hide the newly created container
newdiv.style.position = 'absolute';
newdiv.style.left = '-99999px';
// Insert the container, fill it with the extended text, and define the new selection.
selectedNode.appendChild(newdiv); // *For the Microsoft Edge browser so that the page wouldn't scroll to the bottom.
newdiv.innerHTML = html;
selection.selectAllChildren(newdiv);
window.setTimeout(function () {
selectedNode.removeChild(newdiv);
selection.removeAllRanges();
selection.addRange(range); // Restore original selection.
}, 5); // Timeout is reduced to 10 msc for Microsoft Edge's sake so that it does not blink very noticeably.
}
document.addEventListener('copy', addCopyrightInfo);

execcommand on empty selection

I'm working on a specialized Text/HTML Editor with Javascript and JQuery in a contenteditable div. I implemented the different text styles (bold, italic,...) with execcommand. This seems to work only if the selected text is not empty. What is the best way to solve this problem?
Here an example of what I want to do with Text being the text in the editor, HTML being the corresponding html code and | being the cursor Position:
Text: Hello| World
HTML: <b>Hello| World</b>
By pressing a "bold" button, the execcommand('bold')-command should be executed on the selected position and the caret should be placed inside the modified position.
Text: Hello| World
HTML: <b>Hello</b>|</b> World</b>
This does not work. I found a Workaround by adding an text node containing a blank. This seems to work in Internet Explorer, but not in Firefox. Here a simple example:
HTML:
<div id="textcontent" contenteditable="true" overflow:auto;"><p>Enter text</p></div>
<button type="button" id="setBold">Bold</button>
Javascript:
$('#setBold').click(function () {
if (document.getSelection() != "") {
document.execCommand('bold');
}
else {
var selObj = document.getSelection();
var selRange = selObj.getRangeAt(0);
var newNode = document.createTextNode(' ');
selRange.deleteContents();
selRange.insertNode(newNode);
selObj.removeAllRanges();
selObj.addRange(selRange);
document.execCommand('bold');
selRange.deleteContents();
selObj.removeAllRanges();
selObj.addRange(selRange);
}
});
And the corresponding jsfiddle here: http://jsfiddle.net/andibioticum/3V7pK/
I modified my workaround-solution by inserting a text node containing a letter, calling the execcommand on that node, deleting it afterwards and setting the caret with focus().
$('#setBold').click(function () {
if (document.getSelection() != "") {
document.execCommand('bold');
}
else {
//get selected position
var selObj = document.getSelection();
//get range of selected position
var selRange = selObj.getRangeAt(0);
//Insert node with dummy text 'd'
var newNode = document.createTextNode('d');
selRange.insertNode(newNode);
selObj.removeAllRanges();
selObj.addRange(selRange);
//Execute command on dummy
document.execCommand('bold');
//Delete dummy from range
selRange.setStart(newNode, 0);
selRange.setEnd(newNode, 1);
selRange.deleteContents();
selObj.removeAllRanges();
selObj.addRange(selRange);
//Focus on empty element
$('#textcontent').focus();
}
});
See the fiddle here: http://jsfiddle.net/andibioticum/XJuRf/

getSelection removes links from selection?

I'm using following code to add my copyright when a text get selected in my website. Everything works well, except that if user selects an area which has link, getSelection() method does not return the link. It just returns the plain text.
I want to allow user to copy my website content as usual, without disturbing the style and content. I'm just looking for a way to add a copyright to the end of selection.
Any way?
Regards
<script type="text/javascript">
function addLink() {
var body_element = document.getElementsByTagName('body')[0];
var selection;
selection = window.getSelection();
var pagelink = "<br /><br /> Read more at: <a href='"+document.location.href+"'>"+document.location.href+"</a><br />Copyright © c.bavota"; // change this if you want
var copytext = selection + pagelink;
var newdiv = document.createElement('div');
newdiv.style.position='absolute';
newdiv.style.left='-99999px';
body_element.appendChild(newdiv);
newdiv.innerHTML = copytext;
selection.selectAllChildren(newdiv);
window.setTimeout(function() {
body_element.removeChild(newdiv);
},0);
}
document.oncopy = addLink;
</script>
You need to get the HTML of the selection, instead of the text, then you can append your link.
Have a look at this question.

Javascript "getSelection" only if it's in a certain div

So I'm trying to collect what people are selecting on our site. Currently, it works EVERYWHERE, and I don't want that. I only want it if they are selecting in a certain DIV.
it's basically a simple modification to a script I found.
<script type="text/javascript">
function appendCopyright() {
var theBody = document.getElementsByClassName("sbReview")[0];
var selection;
selection = window.getSelection();
var copyrightLink = '<br /><br /> - Read more at: '+document.location.href+'<br />©2012 <? printf($product. ' & ' .$spOrganization); ?>';
var copytext = selection + copyrightLink;
var extra = document.createElement("div");
extra.style.position="absolute";
extra.style.left="-99999px";
theBody.appendChild(extra);
extra.innerHTML = copytext;
selection.selectAllChildren(extra);
window.setTimeout(function() {
theBody.removeChild(extra);
},0);
}
document.oncopy = appendCopyright;
I tried modifying selection = window.getSelection(); but it just broke it :(
Basically, I want the above code, ONLY to work in a certain div, not the whole body
Probably you shouldn't use document.oncopy, instead try using div.oncopy where div is the div element you are interested in.
var selection = getSelection().toString(); is your solution - getSelection() returns a Selection object and you can get the string just by using .toString() method. More properties and methods of Selection object could be found here: https://developer.mozilla.org/en-US/docs/DOM/Selection
According to the Mozilla JS docs the selection class has a method containsNode. The following should work.
function appendCopyright() {
var theBody = document.getElementsByClassName("sbReview")[0];
var selection;
selection = window.getSelection();
// HERE's THE GOODS
// set aPartlyContained to true if you want to display this
// if any of your node is selected
if(selection.containsNode(aNode, aPartlyContained)){
var copyrightLink = '<br /><br /> - Read more at: '+document.location.href+'<br />©2012 <? printf($product. ' & ' .$spOrganization); ?>';
var copytext = selection + copyrightLink;
var extra = document.createElement("div");
extra.style.position="absolute";
extra.style.left="-99999px";
theBody.appendChild(extra);
extra.innerHTML = copytext;
selection.selectAllChildren(extra);
window.setTimeout(function() {
theBody.removeChild(extra);
},0);
}
}
document.oncopy = appendCopyright;

Categories