Here's my code:
function moremagic()
{
var txt = '';
if (window.getSelection)
{
txt = window.getSelection();
}
else if (document.getSelection)
{
txt = document.getSelection();
}
else if (document.selection)
{
txt = document.selection.createRange().text;
}
else return;
if(txt=="" || txt==" "){
alert("No Text Selected");
return;}
var start = txt.anchorOffset;
var countstring = txt.toString();
alert(txt.anchorNode);
var end = txt.anchorOffset+countstring.length;
var type = prompt("Annotation Type: ");
if(type=="lp-token"){
var description = prompt("Lisp Statement: ");}
else if(type=="section-head-annotation"){
var description = "Section Head";}
else if(type=="list-item-annotation"){
var description = "list-element";}
else if(type=="sentence-annotation"){}
else {var description = prompt("Description: ");}
Arraystring = Arraystring+"#"+type+"#"+description+"#"+start+"#"+end;
alert(Arraystring);
var custom = document.getElementById("custom");
custom.value=Arraystring;
}
It generates a textnode object from text highlighted by the cursor but this function is called many different times and for each different highlight the anchorNode changes. I need the anchorNode to be a constant for all of the created textnode objects. Is there any way that the anchorNode of a textobject can be changed? Thank you!
Not totally sure what you are trying to achieve, but in reading this http://help.dottoro.com/ljkstboe.php I would have to say "No, there is no way to make the anchorNode constant" since it is based off of the selection. Now, if you always want the reference to the first character in the selected string for a particular selection (assuming the selection is only textNode), then you might be able to do this (replacing code where your var start is):
var firstNode, start;
if(txt.anchorOffset < txt.focusOffset) {
firstNode = txt.anchorNode;
start = txt.anchorOffset;
}
else {
firstNode = txt.focusNode;
start = txt.focusOffset;
}
var countstring = txt.toString();
etc...
This is because if the selection is made from right-to-left rather than left-to-right, most browsers place the anchorNode at the last character and the focusNode at the first character of the selection, because the anchorNode indicates where the user started their selection.
Related
First Time I am asking something here I hope I will be precise enough.
I am trying to make a simple extension for chrome that highlight the selected text when I press "H" on my keyboard, but I have some issue :
Use Case
The user select with his mouse a piece of text.
The user press H on his keyboard
Bibbidi-Bobbidi-Boo the selected text is highlighted.
Code so far
To detect when user press H and to get the piece of text he selected :
window.addEventListener('keydown', e => {
if(e.code == "KeyH")
{
var selected = window.getSelection()
//SimpleHighLight(selected);
ComplexHighLight(selected);
}
});
I have coded coded a simple way to do what I want like this :
function SimpleHighLight(selected){
var selectedText = selected.toString();
if(selectedText.length != 0)
{
var range = selected.getRangeAt(0);
var element = selected.anchorNode.parentNode;
var highlited = "<span style='background: rgb(255,255,0)'>" + selectedText + "</span>";
var reg = new RegExp(selectedText,"g");
var text = element.innerHTML.replace(reg, highlited);
element.innerHTML = text;
}
}
It work fine for piece of text in an unique DOM element and when there is no other occurrence of the selected text but I want it to always work, like in a case of my selected text comes from 2 different paragraphs.
So I did this :
function ComplexHighLight(selected){
var selectedText = selected.toString();
if(selectedText.length != 0)
{
console.log(" Selection : " + selectedText);
var range = selected.getRangeAt(0);
if(!range.collapsed)
{
var startNode = range.startContainer;
var startOffset = range.startOffset;
var endNode = range.endContainer;
var endOffset = range.endOffset;
if(startNode == endNode) //Means that its in the same node element
{
var highlited = "<span style='background: rgb(255,255,0)'>" + selectedText + "</span>";
startNode.replaceData(startOffset, endOffset-startOffset, highlited);
startNode.parentNode.innerHTML = startNode.nodeValue;
}
}
}
}
That's only a part of the problem where I handle when a piece of text is in the same element (I am already too much in trouble to handle when the selected text comes from multiples elements :( ).
Issue
On paper, it should work, but the main issue is that when I do :
startNode.parentNode.innerHTML = startNode.nodeValue;
the <span> division is given to innerHTML as a string and not some HTML stuff.
I have worked around this for about the whole evening but I can't fix it, does anyone have an idea of how I should do that ?
This is my editor content:
<h1>Heading 1<h1>
<p>Paragraph</p>
<h2>Heading 2</h2>
Now if i select text in the editor, is there a chance to get a list of all the elements involved in this selection? For example if i select a portion of Heading 1 and Paragraph i would like to get an array (h1, p) or at least an object where i can see which elements are in the selection.
Ive already tried most of the functions described here http://docs.ckeditor.com/#!/api/CKEDITOR.dom.selection but most of the time i only get the first element of the selection.
i have adapted a function i had for the same
$("textarea").select(function() {
var textComponent = $(this)[0]; //element identifier
var selectedText;
// IE version
if (document.selection !== undefined)
{
textComponent.focus();
var sel = document.selection.createRange();
selectedText = sel.text;
}
// Mozilla version
else if (textComponent.selectionStart !== undefined)
{
var startPos = textComponent.selectionStart;
var endPos = textComponent.selectionEnd;
selectedText = textComponent.value.substring(startPos, endPos);
}
$("p").html("You selected: " + selectedText);
});
check here: https://jsfiddle.net/ees8bupq/1/
I'm creating a markdown editor and I need to check if neighbor characters are specific characters, then remove them, else append them.
For e.g I want to check selected-text, tow neighbor characters are **, then remove them, else append them around selected text.
I can get selected text using this approach:
function getSelection(elem) {
var selectedText;
if (document.selection != undefined) { // IE
elem.focus();
var sel = document.selection.createRange();
selectedText = sel.text;
} else if (elem.selectionStart != undefined) { // Firefox
var startPos = elem.selectionStart;
var endPos = elem.selectionEnd;
selectedText = elem.value.substring(startPos, endPos)
}
return selectedText;
}
$(document).on('mousedown', 'button', function(e) {
var selection = getSelection( $('#txtarea').get(0) );
alert(selection);
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<textarea id="txtarea">this is a test</textarea>
<button>Bold (toggle)</button>
Now I need when user clicks on that button, it checks if selected text is between ** like this **selectedtext**, then remove them like this selected text else append them like this **selectedtext**. How can I do that?
Before anything I would like to refer to all the markdown editors out there: https://www.google.de/webhp?sourceid=chrome-instant&ion=1&espv=2&ie=UTF-8#q=javascript%20markdown%20library
So: do not try to reinvent the the wheel, and so on.
But for the sake of learning, my approach would look like this:
function toggleMarker(marker, el) {
var markerLength = marker.length;
var startPos, endPos, selection, range;
if (document.selection != undefined) { // IE
el.focus();
range = document.selection.createRange();
selection = range.text;
} else if (el.selectionStart != undefined) { // Firefox
startPos = el.selectionStart;
endPos = el.selectionEnd;
selection = el.value.substring(startPos, endPos);
}
if (!selection.length){
return;
}
if (el.value.substring(startPos-markerLength,startPos) === marker
&& el.value.substring(endPos,endPos+markerLength) === marker
){
el.value = el.value.substring(0,startPos-markerLength) +
selection +
el.value.substring(endPos+markerLength);
}
else{
el.value = el.value.substring(0,startPos) + marker +
selection + marker + el.value.substring(endPos);
}
}
$(document).on('mousedown', 'button', function(e) {
toggleMarker( $(this).data('marker'), $('#txtarea').get(0) ).text;
});
See it in action: https://jsfiddle.net/t4ro53v8/4/
The solution takes a very generic approach: the marker to toggle is set as a custom data attribute to make it easy to reuse the code.
The functionality is only implemented for the non-IE case. You will have to check, how to determine startPos and endPos for a range in IE.
In all other browsers:
the selection is identified
nothing is done if nothing is selected
sourroundings of the selection are checked against the given marker
if both markers are present, they get deleted
otherwise the markers are inserted
As a proof of concept this example works like a charm.
But there are some shortcomings:
How to distinguish between bold text(**) and italics(*)?
How to handle markers that just appear just on one side of the selection
What to do, if a marker is selected?
But that is for you to solve now ...
You could use regex to find the occurance of a ** ** pattern.This regex will help you find the pattern similar to what you have.
[*][*][a-z]*[*][*] .
Using the exec() method, will help you extract that particular text.
Check the length of this using .length, if it is 4, then there is nothing in between, and you can replace it with the new text surrounded by **,
"**"+ newtext+"**"
For removing the **, you can use the replace() method, where you replace ** with whitespace or so.
In Google docs, this function changes the selected text to black
function selectedFontColorBlack() {
// DocumentApp.getUi().alert('selectedFontColorBlack');
var sel = DocumentApp.getActiveDocument().getSelection();
var elements = sel.getRangeElements();
for (var i = 0; i < elements.length; i++) {
var element = elements[i];
// Only modify elements that can be edited as text; skip images and other non-text elements.
if(element.getElement().editAsText) {
var text = element.getElement().editAsText();
// Bold the selected part of the element, or the full element if it's completely selected.
if (element.isPartial()) {
text.setForegroundColor(element.getStartOffset(), element.getEndOffsetInclusive(), "#000000");
} else {
text.setForegroundColor("#000000");
}
}
}
}
This function changes the entire paragraph in which the cursor (or selection) exists to uppercase:
function uppercaseSelected() {
// DocumentApp.getUi().alert('uppercaseSelected');
var sel = DocumentApp.getActiveDocument().getSelection();
var elements = sel.getRangeElements();
for (var i = 0; i < elements.length; i++) {
var element = elements[i];
// Only modify elements that can be edited as text; skip images and other non-text elements.
if(element.getElement().editAsText) {
var text = element.getElement().editAsText();
text.setText(text.getText().toUpperCase());
}
}
}
I don't see any corresponding setText function that works on the selection's "offset", as does the setForegroundColor(Integer,Integer,String). (Both of these functions are in class Text.)
How can I change the actually selected text to uppercase, and not the entire paragraph in which the selection exists?
Thank you.
Try using the setAttributes(startOffset, endOffsetInclusive, attributes) method. Check out the documentation
[EDIT: my bad, i don't think that'll do it. I'll look a bit longer tho]
The gem hidden in the post that #Mogsdad is referring to is this: var selectedText = elementText.substring(startOffset,endOffset+1);. to be little more verbose on how this is used: you can use the string method substring on objects such as DocumentApp.getActiveDocument().getSelection().getSelectedElements()[i].getElement().editAsText().getText()
so, essentially, grab that substring, convert it to uppercase, delete the text in the range (selectedElement.getstartOffset,selectedElement.endOffsetInclusive) and insert the bolded text at selectedElement.getstartOffset
Tada! check it out:
function uppercaseSelected() {
// Try to get the current selection in the document. If this fails (e.g.,
// because nothing is selected), show an alert and exit the function.
var selection = DocumentApp.getActiveDocument().getSelection();
if (!selection) {
DocumentApp.getUi().alert('Cannot find a selection in the document.');
return;
}
var selectedElements = selection.getSelectedElements();
for (var i = 0; i < selectedElements.length; ++i) {
var selectedElement = selectedElements[i];
// Only modify elements that can be edited as text; skip images and other
// non-text elements.
var text = selectedElement.getElement().editAsText();
// Change the background color of the selected part of the element, or the
// full element if it's completely selected.
if (selectedElement.isPartial()) {
var bitoftext = text.getText().substring(selectedElement.getStartOffset(), selectedElement.getEndOffsetInclusive() + 1);
text.deleteText(selectedElement.getStartOffset(), selectedElement.getEndOffsetInclusive());
text.insertText(selectedElement.getStartOffset(), bitoftext.toUpperCase());
} else {
text.setText(text.getText().toUpperCase());
}
}
}
Started with the code from Google App script Document App get selected lines or words?, and made this almost a year ago. I'm happy if it helps you.
The "trick" is that you need to delete the original text and insert the converted text.
This script produces a menu with options for UPPER, lower and Title Case. Because of the delete / insert, handling more than one paragraph needs special attention. I've left that to you!
function onOpen() {
DocumentApp.getUi().createMenu('Change Case')
.addItem("UPPER CASE", 'toUpperCase' )
.addItem("lower case", 'toLowerCase' )
.addItem("Title Case", 'toTitleCase' )
.addToUi();
}
function toUpperCase() {
_changeCase(_toUpperCase);
}
function toLowerCase() {
_changeCase(_toLowerCase);
}
function toTitleCase() {
_changeCase(_toTitleCase);
}
function _changeCase(newCase) {
var doc = DocumentApp.getActiveDocument();
var selection = doc.getSelection();
var ui = DocumentApp.getUi();
var report = ""; // Assume success
if (!selection) {
report = "Select text to be modified.";
}
else {
var elements = selection.getSelectedElements();
if (elements.length > 1) {
report = "Select text in one paragraph only.";
}
else {
var element = elements[0].getElement();
var startOffset = elements[0].getStartOffset(); // -1 if whole element
var endOffset = elements[0].getEndOffsetInclusive(); // -1 if whole element
var elementText = element.asText().getText(); // All text from element
// Is only part of the element selected?
if (elements[0].isPartial())
var selectedText = elementText.substring(startOffset,endOffset+1);
else
selectedText = elementText;
// Google Doc UI "word selection" (double click)
// selects trailing spaces - trim them
selectedText = selectedText.trim();
endOffset = startOffset + selectedText.length - 1;
// Convert case of selected text.
var convertedText = newCase(selectedText);
element.deleteText(startOffset, endOffset);
element.insertText(startOffset, convertedText);
}
}
if (report !== '') ui.alert( report );
}
function _toUpperCase(str) {
return str.toUpperCase();
}
function _toLowerCase(str) {
return str.toLowerCase();
}
// https://stackoverflow.com/a/196991/1677912
function _toTitleCase(str)
{
return str.replace(/\w\S*/g, function(txt){return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();});
}
I have found a code snippet (can't remember where), and it's working fine - almost :-)
The problem is, that it copies the selection no matter where the selection is made on the entire website, and it must only copy the selection if it is in a specific div - but how is that done?
function getHTMLOfSelection () {
var range;
if (document.selection && document.selection.createRange) {
range = document.selection.createRange();
return range.htmlText;
}
else if (window.getSelection) {
var selection = window.getSelection();
if (selection.rangeCount > 0) {
range = selection.getRangeAt(0);
var clonedSelection = range.cloneContents();
var div = document.createElement('div');
div.appendChild(clonedSelection);
return div.innerHTML;
} else {
return '';
}
} else {
return '';
}
}
$(document).ready(function() {
$("#test").click(function() {
var kopitekst = document.getElementById("replytekst");
var kopitjek=getHTMLOfSelection(kopitekst);
if (kopitjek=='')
{
alert("Please select some content");
}
else
{
alert(kopitjek);
}
});
});
I have made a Jsfiddle
This is my first post here. Hopefully I done it right :-)
That's because it checks the entire document with:
if (document.selection && document.selection.createRange) {
range = document.selection.createRange();
return range.htmlText;
}
Not a specific section. If you want to check specific sections for selected text, you need to identify that you are searching for them in the search selection, something that nails your range down to a particular div:
range = $('#replytekst');
Specify a particular DOM element instead of using document object.
var oDiv = document.getElementById( 'selDiv' );
then use
if ( oDiv.selection && oDiv.selection.createRange ) {
range = oDiv.selection.createRange();
return range.htmlText;
}
You need to check if the section contains the selection. This is separate from getting the selection. There is a method for doing this in this answer: How to know if selected text is inside a specific div
I've updated your fiddle
Basically you need to check the id of the parent/ascendant of the selected text node.
selection.baseNode.parentElement.id or selection.baseNode.parentElement.parentElement.id will give you that.
Edit: I've thought of another, somewhat hack-y, way of doing it.
If
kopitekst.innerHTML.indexOf(kopitjek) !== -1
gives true, you've selected the right text.
DEMO1
DEMO2
(these work in Chrome and Firefox, but you might want to restructure the getHTMLOfSelection function a little)
If it possible for you I recommend to use rangy framework. Then your code might look like this:
// get the selection
var sel = rangy.getSelection();
var ranges = sel.getAllRanges();
if (!sel.toString() || !sel.toString().length)
return;
// create range for element, where selection is allowed
var cutRange = rangy.createRange();
cutRange.selectNode(document.getElementById("replytekst"));
// make an array of intersections of current selection ranges and the cutRange
var goodRanges = [];
$.each(ranges, function(j, tr) {
var rr = cutRange.intersection(tr);
if (rr)
goodRanges.push(rr);
});
sel.setRanges(goodRanges);
// do what you want with corrected selection
alert(sel.toString());
// release
sel.detach();
In this code if text was selected in your specific div then it will be kept, if there was selection where other elements take part too, these selection ranges will be cut off.