I am using Medium.js editor script which helps with contenteditable divs.
But I also want to make adding images better and more like Medium.js itself does.
Currently, I am using this function to insert a node at the current cursor point:
function insertImageAtCursor(text) {
var sel, range, html;
sel = window.getSelection();
range = sel.getRangeAt(0);
range.deleteContents();
var descriptionNode = document.createElement("img");
descriptionNode.className = "img";
descriptionNode.src = text;
range.insertNode(descriptionNode);
range.setStartAfter(descriptionNode);
sel.removeAllRanges();
sel.addRange(range);
}
This works, however I am facing styling issues where I am currently in a paragraph and typing, then insert an image I will end up with content like:
<p>
this is a paragraph and I have inserted a image
<img src="insertimage.png"/>
</p>
Really, I should be using the figure element and it would insert the image outside of the current paragraph. This way when I add an image, it wouldn't be added in the current p element, but instead would be added after and as a figure element. I cannot simply append the image, as I want users to be able to be editing a blog post and be able to insert an image at any point.
Any help on this? There is a plugin already:
But the dependencies required are more than I wish to use and I already have image upload etc working. Just need to get the images being inserted better.
you can split the <p> in two <p> tags and add the <img> between this <p> tags.
Here is a sample code. It's not bug free :).
http://codepen.io/anon/pen/RaOpbr
function insertImageAtCursor(text) {
var sel, range, html;
sel = window.getSelection();
range = sel.getRangeAt(0);
var para = getParagraph(range.startContainer);
range.deleteContents();
range.setEndAfter(para);
var newPara = document.createElement("p");
newPara.innerHTML = range.toString();
range.deleteContents();
para.parentElement.appendChild(newPara);
para.parentElement.insertBefore(para, newPara);
var descriptionNode = document.createElement("img");
descriptionNode.className = "img";
descriptionNode.src = text;
para.parentElement.insertBefore(descriptionNode, newPara);
range.setStart(newPara, 0);
sel.removeAllRanges();
sel.addRange(range);
}
function getParagraph(node) {
var para = node;
while (para.parentElement !== null && (para.tagName == null || para.tagName.toLowerCase() !== 'p')) {
para = para.parentElement;
}
return para;
}
Related
My current solution is:
Get selected html (include text and html tag), namely: selText
highlightText = <span>selText</span>
Find selText in innerHTML of the body or document (or the element which the mouse dragged in)
Replace with highlightText
But if the document is: a a a a a a and user selects the last a. My function will highlight the first or all a.
Any suggestion?
Thank you.
i think your question is duplicated, anyway i just searched the internet and found this article.
Below the final code to achieve what you ask
function highlightSelection() {
var selection;
//Get the selected stuff
if(window.getSelection)
selection = window.getSelection();
else if(typeof document.selection!="undefined")
selection = document.selection;
//Get a the selected content, in a range object
var range = selection.getRangeAt(0);
//If the range spans some text, and inside a tag, set its css class.
if(range && !selection.isCollapsed)
{
if(selection.anchorNode.parentNode == selection.focusNode.parentNode)
{
var span = document.createElement('span');
span.className = 'highlight-green';
range.surroundContents(span);
}
}
}
I also found this library rangy that is an helper you can use to select text but only works with jquery so i prefer the first vanilla-js solution.
var el = $("<span></span>");
el.text(rangy.getSelection().getRangeAt(0).toString());
rangy.getSelection().getRangeAt(0).deleteContents();
rangy.getSelection().getRangeAt(0).insertNode(el.get(0));
rangy.getSelection().getRangeAt(0).getSelection().setSingleRange(range);
On Range and User Selection
You have to select range using Document.createRange that return a Range object before you can use Range.surroundContents(), you could create a range this way.
var range = document.createRange();
range.setStart(startNode, startOffset);
range.setEnd(endNode, endOffset);
In practice you follow this guide to understand range and selection tecniques.
The most important point is contained in this code
var userSelection;
if (window.getSelection) {
userSelection = window.getSelection();
}
else if (document.selection) { // should come last; Opera!
userSelection = document.selection.createRange();
}
After this you can use
userSelection.surroundContents()
I have this function I am calling from rich text editor to add the content in the selection.
The problem is, it is not working properly, if I pass with html tags like <center>some</center>
Instead of making the content center, it outputs the HTML also text
function addTextAtCursorPostion(text) {
var sel, range, html;
if (window.getSelection) {
sel = window.getSelection();
if (sel.getRangeAt && sel.rangeCount) {
range = sel.getRangeAt(0);
range.deleteContents();
range.insertNode(document.createTextNode(text));
}
}
This document.createTextNode takes only text. Is there a way i can process text and html?
or do we have any dummy/empty node that can place html inside?
Sample Content of my Rich Text Editor innerHTML taken using Firebug
<div contenteditable="true" id="editor">
<font face="Comic Sans MS">Go ahead</font>…
<a href="www.stackoverflow.com"><b><i>Welcome User</i>
**ADD HTML FORMATTED STRING HERE OR POSITION AT CURSOR**
</b></a><br>
</div>
If i wrap my htmlText with span/div/p i may get UI alignment issue
I do not think you can render HTML using a text node. You'll need to create an element and use innerHTML to insert the code.
var el = document.createElement('span');
el.innerHTML = text;
Not tested, but I'm thinking you could insert the HTML string into a custom HTML Element (which bring no inherited styles, other than those added globally) and then insert that into the range.
function addTextAtCursorPostion(text) {
var sel, range, html;
if (window.getSelection) {
sel = window.getSelection();
if (sel.getRangeAt && sel.rangeCount) {
range = sel.getRangeAt(0);
range.deleteContents();
range.insertNode(document.createElement("range").innerHTML=text);
}
}
I am using contenteditable=true trying make a text editor. I have successfully used getSelection() to wrap the selection inside HTML tags.
A problem is though, that if the user selects text outside the editor and the operation button is clicked, that text will be wrapped inside tags as well.
How would I do it with getSelection(), check if the selection is inside a div with the class of editor?
Edit:
Currently using this code:
var sel = window.getSelection ? window.getSelection() : document.selection.createRange();
if(sel.getRangeAt){
var range = sel.getRangeAt(0);
var newNode = document.createElement("p");
newNode.setAttribute('class', operationClass);
range.surroundContents(newNode);
} else {
sel.pasteHTML('<p class="' + operationClass +'">'+sel.htmlText+'</p>');
}
Once you have var range = sel.getRangeAt(0); you can determine if range.commonAncestorContainer or one of its ancestors is the editor container, using code like this:
var ancestor = range.commonAncestorContainer;
while (ancestor.id != "editor" // Check id, class or otherwise
&& ancestor.parentElement != null) {
ancestor = ancestor.parentElement;
}
if (ancestor.id == "editor") {
// Selection is within the editor.
}
I'm trying to create a fairly simple text editor (bold, italic, indent) and need to be able to toggle the class associated with the button on click. I have this code:
var selected = function ()
{
var text = '';
if (window.getSelection) {
text = window.getSelection();
}
return text;
}
$('textarea').select(function(eventObject)
{
console.log(selected().toString());
var selectedtext = selected().toString();
$('#bold-button').click(function () {
$(selectedtext).addClass('bold-text');
});
});
And I can get the selected text to print, but can't get the class added. I've seen other solutions that add the class on click to the entire textarea, but I dont need that. Any help?
You could use surroundContents() like below. Before demo here http://jsfiddle.net/jwRG8/3/
function surroundSelection() {
var span = document.createElement("span");
span.style.fontWeight = "bold";
span.style.color = "green";
if (window.getSelection) {
var sel = window.getSelection();
if (sel.rangeCount) {
var range = sel.getRangeAt(0).cloneRange();
range.surroundContents(span);
sel.removeAllRanges();
sel.addRange(range);
}
}
}
But this is not supported less than IE9. And I worked on text selections before and I found them in consistent. Tim Down is very much experienced on selections and most of the answers in SO related to Selections are given my him. He has written a plugin called rangy. You mat try it at https://code.google.com/p/rangy/
Because you are selecting text directly, there is no element to add the class on. textNodes cannot have classes. Instead, try wrapping the text in an element:
$('textarea').select(function(eventObject) {
console.log(selected().toString());
var selectedtext = selected().toString();
$(selectedtext).wrap('<span />').parent().addClass('bold-text');
})
Or you could just wrap it in a b tag, without the class:
$(selectedtext).wrap('<b/>');
I have a string with a word five times.if i selected forth hello it should return 4
<div id="content">hello hai hello hello hello</div>
getting selected text script
<script>
if(window.getSelection){
t = window.getSelection();
}else if(document.getSelection){
t = document.getSelection();
}else if(document.selection){
t =document.selection.createRange().text;
}
</script>
If am selecting hai it should return 1.
If am selecting hello hai it should return 1. please help.
Assuming that the contents of the <div> are guaranteed to be a single text node, this is not too hard. The following does not work in IE < 9, which does not support Selection and Range APIs. If you need support for these browsers, I can provide code for this particular case, or you could use my Rangy library.
Live demo: http://jsfiddle.net/timdown/VxTfu/
Code:
if (window.getSelection) {
var sel = window.getSelection();
var div = document.getElementById("content");
if (sel.rangeCount) {
// Get the selected range
var range = sel.getRangeAt(0);
// Check that the selection is wholly contained within the div text
if (range.commonAncestorContainer == div.firstChild) {
// Create a range that spans the content from the start of the div
// to the start of the selection
var precedingRange = document.createRange();
precedingRange.setStartBefore(div.firstChild);
precedingRange.setEnd(range.startContainer, range.startOffset);
// Get the text preceding the selection and do a crude estimate
// of the number of words by splitting on white space
var textPrecedingSelection = precedingRange.toString();
var wordIndex = textPrecedingSelection.split(/\s+/).length;
alert("Word index: " + wordIndex);
}
}
}
You'll need to use the Range capabilities of the DOM. Here is how to get the currently selected range:
var currentRange = window.getSelection().getRangeAt(0);
From there currentRage.startOffset will tell you the position of your selection within the file. So you'll need to compare that with the start range of your element:
var myContent = document.getElementById('content');
var divRange = document.createRange ();
divRange.selectNodeContents (myContent);
Now you can compare the divRange.startOffset with your selection startOffset and determine which one you're on.