Skip to content

Instantly share code, notes, and snippets.

@garyfeng
Last active August 30, 2016 17:17
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save garyfeng/f63021954cdb67ac7cb5d40425345131 to your computer and use it in GitHub Desktop.
Save garyfeng/f63021954cdb67ac7cb5d40425345131 to your computer and use it in GitHub Desktop.
// sample code for calculating the caret cursor position in CKEditor
// including functions to deal with cross-browser inconsistencies for end-of-the-line
// A hack to get text from HTML
// http://stackoverflow.com/questions/5002111/javascript-how-to-strip-html-tags-from-string
var html2text = function (html) {
// testing the idea of using .toString() with a replacement trick
// knowing that .textContent doesn't preserve the newlines, let's replace </p> with 2 newline chars
// This is ignoring all other HTML/CSS markups, such as <br>, tables, etc.
// console.log(html);
// but first, we have to remove the dangling '<p></p>' at the end of a selection.
// other wise moving the cursor to the begiining of a new line will give you incorrect pos reading
// it's basically 2+, because the empty </p> is turned into \n\n.
// This is only applicable to "<p></p>" inserted by CKEditor; user created newlines (e.g.
// when the user hits enter keys) are like "<p><br/></p>", and textContent ignores <br>s. We
// are ok there.
html = html.replace(/<p><\/p>/gi, "");
// now we can do our trick
html = html.replace(/<\/p>/gi, "\n\n<\/p>");
// create the div if it's not there
var el = document.getElementById("divHTML2Text");
if (el === null) {
el = document.createElement("div");
el.setAttribute("id", "divHTML2Text");
el.setAttribute("style", "{display:hidden; white-space: pre-line;}");
}
// clear the element
el.innerHTML = "";
try {
// in the case html is not valid
el.innerHTML = html;
} catch (e) {
console.log("Error in html2text(): invalid HTML input");
return "";
}
// can't use el.textContent because it doesn't preserve newline as \n
// but innerText trims spaces ;< We solve this problem by
// replacing spaces with non-breaking spaces in the incoming HTML.
// @@@@@ NOTE: Firefox doesn't support innerText @@@@@@@@
// see this for a great summary of the state of affair as of 2015
// http://perfectionkills.com/the-poor-misunderstood-innerText/
// and we probably don't want to polyfill, like
// https://github.com/duckinator/innerText-polyfill/blob/master/innertext.js
// So we will use .textContent instead, with the newline replacement trick above.
//return el.innerText;
return el.textContent;
}
// A hack to get a docment fragment to HTML
var frag2html = function (frag) {
// create the div if it's not there
var el = document.getElementById("divHTML2Text");
if (el === null) {
el = document.createElement("div");
el.setAttribute("id", "divHTML2Text");
el.setAttribute("style", "{display:hidden}");
}
// clear the element
el.innerHTML = "";
try {
// in the case html is not valid
el.appendChild(frag.cloneNode(true));
} catch (e) {
console.log("Error in frag2html(): invalid document fragment");
return "";
}
return el.innerHTML;
}
var selection2string = function (editor) {
// avoid using DOM manipulations
// return editor.getSelection().getNative().toString()
return editor.getSelection().getSelectedText();
};
var getSelectionInfo = function (editor) {
// get the current selection, return a selInfo object
// RonMorrill: Initialize this for cases when we need to return an empty selection
var selInfo = {
textSelected: "",
startPos: 0,
endPos: 0
},
textSelected = "";
// get current selection in DOM
// var editor = CKEDITOR.instances.editor1
var sel = editor.getSelection().getNative();
// jgrant. check for no selection.
// RonMorrill: improve check and return something that won't cause calling code to crash
if (sel == null || sel.rangeCount === 0) {
return selInfo;
}
// get range
var ran = sel.getRangeAt(0);
// get html
// make sure we replace white spaces with non-break spaces
var html = frag2html(ran.cloneContents()).replace(/ /g, '&nbsp;');
// Calculate the text position of the two ends.
// note that html2text() returns "\n\n" for line breaks,
// but range.toString() reutrns "\n" only.
// So we must go through html2text() for all range contents for consistency.
// first select the beginning of the text
var firstnode = editor.document.$.body.childNodes[0];
var firstOffset = 0;
// Count characters before to the left of the selection.
var leftrange = editor.document.$.createRange();
leftrange.setStart(firstnode, firstOffset);
leftrange.setEnd(ran.startContainer, ran.startOffset);
textSelected = html2text(frag2html(leftrange.cloneContents()).replace(/ /g, '&nbsp;'));
selInfo.startPos = textSelected.length;
// if (logConfig.logKeyVerbose) selInfo.leftText = textSelected;
// this includes "\n\n" that we need to take out
if (ran.startOffset !== 0 && textSelected.endsWith("\n\n")) {
selInfo.startPos -= 2;
}
// Count characters before to the right of the selection.
var rightrange = editor.document.$.createRange();
rightrange.setStart(firstnode, firstOffset);
rightrange.setEnd(ran.endContainer, ran.endOffset);
textSelected = html2text(frag2html(rightrange.cloneContents()).replace(/ /g, '&nbsp;'));
// if (logConfig.logKeyVerbose) selInfo.rightText = textSelected;
selInfo.endPos = textSelected.length;
// this includes "\n\n" that we need to take out
if (ran.endOffset !== 0 && textSelected.endsWith("\n\n")) {
selInfo.endPos -= 2;
}
selInfo.lenSelected = selInfo.endPos - selInfo.startPos;
// selected contents
// if (logConfig.logKeyVerbose) selInfo.htmlSelected = html;
// textSelected turned out to be more complex. In the following example
// "startPos":13,"endPos":20,"htmlSelected":"<p>t</p><p>Se</p>","textSelected":"t\n\nSe\n\n"
// we see that the lenSelected = 7, but the text has an extra "\n\n" because html2text automatically
// closes off the </p>, which gets translated as "\n\n". We have to take it out.
textSelected = html2text(html);
// if endOffset is 0, then delete the last 2 characters, which are "\n\n"
selInfo.textSelected = ran.endOffset != 0 && textSelected.endsWith("\n\n") ? textSelected.substring(0, textSelected.length - 2) : textSelected;
//selInfo.textSelected = textSelected;
selInfo.isCollapsed = sel.isCollapsed;
return selInfo;
};
// example code ... may not be valid
var editor = CKEDITOR.instances.editor1
var sel = getSelectionInfo(editor);
var extendedInfo = {};
extendedInfo.selectedText = sel.textSelected || "";
extendedInfo.startPos = sel.startPos;
extendedInfo.endPos = sel.endPos;
// when nothing is selected, startPos == endPos
@garyfeng
Copy link
Author

removed references to config.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment