Right cursor position between MathJax formulas - javascript

I have some trouble with inserting MathJax formulas. I want that user can insert cursor between each formulas and therefore can insert formula between them.
My code:
function get(elem)
{
currentFormul = elem;
var math = MathJax.Hub.getAllJax(elem)[0];
var input = document.getElementById("MathInput");
input.value = math.originalText;
}
function input()
{
sel = window.getSelection();
var range = sel.getRangeAt(0);
var input = document.getElementById("MathInput");
var span = document.createElement("span");
span.contentEditable="false";
span.addEventListener('click', function() { get(this); }, true);
span.innerHTML = "\\("+input.value+"\\)";
range.insertNode(span);
MathJax.Hub.Queue(["Typeset", MathJax.Hub]);
}
</script>
<body contenteditable="true" spellcheck="false">
<span contenteditable="false">
<button onclick="change()">Change!</button>
<input id="MathInput" size="60" />
<button onclick="input()">INPUT!</button>
<button onclick="someChanges()">FONT!</button>
</span>
<p>S</p>
<span onclick="get(this)" contentEditable="false">\(\color{red}{x=ax^2}\)</span>
<span onclick="get(this)" contentEditable="false">\(x=ax^2\)</span>
<span onclick="get(this)" contentEditable="false">\(x=ax^2\)</span>
</body>
</html>
Now i can't insert cursor between formula for new elements, but can for old.
Can someone tell me how do this right?

There are white spaces between the spans in your html. These white spaces are in fact text node that are child of body, which has contenteditable set to true. So when you click on one of these text nodes, you click in a contenteditable node, hence you get a cursor.
When you add spans dynamically, it adds it without white space. There's a text node created, but it's empty so you can't click it.
One easy way to fix this would be to set the text node before your newly inserted spans to " ". It'll then have the same behavior as your old elements. Like this:
function input()
{
sel = window.getSelection();
var range = sel.getRangeAt(0);
var input = document.getElementById("MathInput");
var span = document.createElement("span");
span.contentEditable="false";
span.addEventListener('click', function() { get(this); }, true);
span.innerHTML = "\\("+input.value+"\\)";
range.insertNode(span);
span.previousSibling.textContent = " ";
MathJax.Hub.Queue(["Typeset", MathJax.Hub]);
}
http://jsfiddle.net/ox20a7oj/

Related

How to undo the formatting of a selected text? [duplicate]

I'm trying to make a simple text editor so users can be able to bold/unbold selected text. I want to use Window.getSelection() not Document.execCommand(). It does exactly what I want but when you bold any text, you can't unbold it. I want it in a way that I can bold and unbold any selected text. I tried several things but no success.
function addBold(){
const selection = window.getSelection().getRangeAt(0);
const selectedText = selection.extractContents();
const span = document.createElement("span");
span.classList.toggle("bold-span");
span.appendChild(selectedText);
selection.insertNode(span);
};
.bold-span {font-weight: bold;}
<p contentEditable>Bold anything here and unbold it</p>
<button onclick="addBold()">Bold</button>
This is close to what you want but groups words together so an unselect will remove from whole word. I have not been able to complete this as I have to go, but should be a good starting point.
function addBold(){
const selection = window.getSelection().getRangeAt(0);
let selectedParent = selection.commonAncestorContainer.parentElement;
//console.log(parent.classList.contains("bold-span"))
//console.log(parent)
let mainParent = selectedParent;
if(selectedParent.classList.contains("bold-span"))
{
var text = document.createTextNode(selectedParent.textContent);
mainParent = selectedParent.parentElement;
mainParent.insertBefore(text, selectedParent);
mainParent.removeChild(selectedParent);
mainParent.normalize();
}
else
{
const span = document.createElement("span");
span.classList.toggle("bold-span");
span.appendChild(selection.extractContents());
//selection.surroundContents(span);
selection.insertNode(span);
mainParent.normalize();
}
//selection is set to body after clicking button for some reason
//https://stackoverflow.com/questions/3169786/clear-text-selection-with-javascript
if (window.getSelection) {
if (window.getSelection().empty) { // Chrome
window.getSelection().empty();
} else if (window.getSelection().removeAllRanges) { // Firefox
window.getSelection().removeAllRanges();
}
} else if (document.selection) { // IE?
document.selection.empty();
}
};
.bold-span {font-weight: bold;}
<p contentEditable>Bold anything here and unbold it</p>
<button onclick="addBold()">Bold</button>
var span = '';
jQuery(function($) {
$('.embolden').click(function(){
var highlight = window.getSelection();
if(highlight != ""){
span = '<span class="bold">' + highlight + '</span>';
}else{
highlight = span;
span = $('span.bold').html();
}
var text = $('.textEditor').html();
$('.textEditor').html(text.replace(highlight, span));
});
});
You could define a function like this where the name of your class is "embolden"

How would I find the innerHTML index of text highlighted from textContent?

I am trying to program a simple text editor for fun.
I am stuck on this problem.
I want to add bold or italics to highlighted text on a button click.
I figure the best way to do this is get the index of the selected text and then add the bold tag / italic tag around the tag in the innerHTML.
However, I can not seem to get the position / index of the selected tag to carry over to the innerHTML. Obviosuly, the innerHTML code is offset by the tags.
Is there an easier way to do this?
I though finding the index of the highlighted text was the way to go. Okay. Unforunately, indexOf will only find the first occurance.
var word_document = document.getElementById('word-document');
/* This code is for our bold button */
var bold_button = document.getElementById('bold-button')
bold_button.addEventListener('click', (event) => {
/* Test to see if text is highlighted */
let text = window.getSelection().toString();
console.log("Selected Text: " + text);
if (text.length > 0) {
// Find the position of the highlighted text in the word document.
let position = word_document.innerHTML.indexOf(text); // Not a good way of doing it
console.log("Pos: ", position);
// Replace the highlighted text from the document with the bold text
word_document.innerHTML.replace(text, "<b>" + text + "</b>");
}
/* If text is not highlighted, add a bold tag */
else {
// Add bold tag to bottom of document
word_document.focus();
word_document.innerHTML += "<b></b>";
word_document.selectionEnd = word_document.innerHTML.length - 6;
}
});
/* This code is for our italic button */
var italic_button = document.getElementById('italics-button');
italic_button.addEventListener('click', function() {
let text = window.getSelection().toString();
// Same issue
});
<button id="bold-button">B</button>
<button id="italics-button">I</button>
<textarea id="word-document">Starting Text</textarea>
I suppose a possible way would be to iterate over the textContent and find if any text prior to the selected text matches it, and then set a variable to skip over that many matches. Is there an easier way to do this. Ideally, I would like to create a bold tag, or italic tag and append it to the textarea in a more proper fashion. I support traversing the DOM is probably a better way. Any ideas on how this might be more easily tackled?
Thanks
I use Plain / Vanilla Javascript.
Edit: Fixed code. Adding JsFiddle here
You can try this :
<html>
<header>
</header>
<body>
<button id="bold-button" onClick="makeBold()">B</button>
<div id="word-document" contenteditable>Starting Text</div>
<script>
function makeBold() {
var inputText = document.getElementById("word-document");
var innerHTML = inputText.innerHTML;
text = window.getSelection().toString();
var index = innerHTML.indexOf(text);
if (index >= 0) {
innerHTML = innerHTML.substring(0,index) + "<span style='font-weight: bold;'>" + innerHTML.substring(index,index+text.length) + "</span>" + innerHTML.substring(index + text.length);
inputText.innerHTML = innerHTML;
}
}
</script>
</html>
the idea here is to use a fake textArea: div with content editable.
I hope it helps u,
Good Luck!
simple dummy solution. this don't work for nested tags.
I highly recommended to read this tutorial
function action({tag, classes}, event){
const text = document.getElementById("word-document");
const selection = window.getSelection();
const range = selection.getRangeAt(0);
const before = text.innerHTML.substr(0, range.startOffset);
const after = text.innerHTML.substr(range.endOffset);
const selected = text.innerHTML.substr(range.startOffset,range.endOffset - range.startOffset );
const warpped = `<${tag} ${classes ? "class=" + classes : ""}>${selected}</${tag}>`
text.innerHTML = before + warpped + after;
}
#word-document {
border: 1px solid black;
padding: 10px;
}
.underline{
text-decoration-line: underline;
}
<button onclick="action({tag: 'b'})">B</button>
<button onclick="action({tag: 'i'})">I</button>
<button onclick="action({tag: 'span', classes:'underline'})">Under score</button>
<div id="word-document" contenteditable>Starting Text</div>

Place cursor(caret) in specific position in <pre> contenteditable element

I'm making a web application to test regular expressions. I have an input where I enter the regexp and a contenteditable pre element where I enter the text where the matches are found and highlighted.
Example: asuming the regexp is ab, if the user types abcab in the pre element, both regexp and text are sent to an api I implemented which returns
<span style='background-color: lightgreen'>ab</span>c<span style='background-color: lightgreen'>ab</span>
and this string is set as the innerHTML of the pre element
This operation is made each time the user edites the content of the pre element (keyup event to be exact). The problem I have (and I hope you can solve) is that each time the innterHTML is set, the caret is placed at the beginning, and I want it to be placed right after the last character input by de user. Any suggestions on how to know where the caret is placed and how to place it in a desired position?
Thanks.
UPDATE For better understanding...A clear case:
Regexp is ab and in the contenteditable element we have:
<span style='background-color: lightgreen'>ab</span>c<span style='background-color: lightgreen'>ab</span>
Then I type a c between the first a and the first b, so now we have:
acbc<span style='background-color: lightgreen'>ab</span>
At this moment the caret has returned to the beginning of the contenteditable element, and it should be placed right after the c I typed. That's what I want to achieve, hope now it's more clear.
UPDATE2
function refreshInnerHtml() {
document.getElementById('textInput').innerHTML = "<span style='background-color: lightgreen'>ab</span>c<span style='background-color: lightgreen'>ab</span>";
}
<pre contenteditable onkeyup="refreshInnerHtml()" id="textInput" style="border: 1px solid black;" ></pre>
With some help from these functions from here ->
Add element before/after text selection
I've created something I think your after.
I basically place some temporary tags into the html where the current cursor is. I then render the new HTML, I then replace the tags with the span with a data-cpos attribute. Using this I then re-select the cursor.
var insertHtmlBeforeSelection, insertHtmlAfterSelection;
(function() {
function createInserter(isBefore) {
return function(html) {
var sel, range, node;
if (window.getSelection) {
// IE9 and non-IE
sel = window.getSelection();
if (sel.getRangeAt && sel.rangeCount) {
range = window.getSelection().getRangeAt(0);
range.collapse(isBefore);
// Range.createContextualFragment() would be useful here but is
// non-standard and not supported in all browsers (IE9, for one)
var el = document.createElement("div");
el.innerHTML = html;
var frag = document.createDocumentFragment(), node, lastNode;
while ( (node = el.firstChild) ) {
lastNode = frag.appendChild(node);
}
range.insertNode(frag);
}
} else if (document.selection && document.selection.createRange) {
// IE < 9
range = document.selection.createRange();
range.collapse(isBefore);
range.pasteHTML(html);
}
}
}
insertHtmlBeforeSelection = createInserter(true);
insertHtmlAfterSelection = createInserter(false);
})();
function refreshInnerHtml() {
var
tag_start = '⇷', //lets use some unicode chars unlikely to ever use..
tag_end = '⇸',
sel = document.getSelection(),
input = document.getElementById('textInput');
//remove old data-cpos
[].forEach.call(
input.querySelectorAll('[data-cpos]'),
function(e) { e.remove() });
//insert the tags at current cursor position
insertHtmlBeforeSelection(tag_start);
insertHtmlAfterSelection(tag_end);
//now do our replace
let html = input.innerText.replace(/(ab)/g, '<span style="background-color: lightgreen">$1</span>');
input.innerHTML = html.replace(tag_start,'<span data-cpos>').replace(tag_end,'</span>');
//now put cursor back
var e = input.querySelector('[data-cpos]');
if (e) {
var range = document.createRange();
range.setStart(e, 0);
range.setEnd(e, 0);
sel.removeAllRanges();
sel.addRange(range);
}
}
refreshInnerHtml();
Type some text below, with the letters 'ab' somewhere within it. <br>
<pre contenteditable onkeyup="refreshInnerHtml()" id="textInput" style="border: 1px solid black;" >It's about time.. above and beyond</pre>
<html>
<head>
<script>
function test(inp){
document.getElementById(inp).value = document.getElementById(inp).value;
}
</script>
</head>
<body>
<input id="search" type="text" value="mycurrtext" size="30"
onfocus="test(this.id);" onclick="test(this.id);" name="search"/>
</body>
</html>
I quickly made this and it places the cursor at the end of the string in the input box. The onclick is for when the user manually clicks on the input and the onfocus is for when the user tabs on the input.
<input id="search" type="text" value="mycurrtext" size="30"
onfocus="test(this.id);" onclick="test(this.id);" name="search"/>
function test(inp){
document.getElementById(inp).value = document.getElementById(inp).value;
}
Here to make selection easy, I've added another span tag with nothing in it, and given it a data-end attribute to make it easy to select.
I then simply create a range from this and use window.getSelection addRange to apply it.
Update: modified to place caret after the first ab
var e = document.querySelector('[data-end]');
var range = document.createRange();
range.setStart(e, 0);
range.setEnd(e, 0);
document.querySelector('[contenteditable]').focus();
var sel = window.getSelection();
sel.removeAllRanges();
sel.addRange(range);
<div contenteditable="true">
<span style='background-color: lightgreen'>ab</span><span data-end></span>c<span style='background-color: lightgreen'>ab</span>
</div>

Add html tags around highligted text in contenteditable div

I have a contenteditable div and i would like to add some html tags around highlighted text, after user select the text and click the button..
Here is the sample code. It has some javascript codes but i couldnt make it work as desired. And i played with a lot actually.
https://codepen.io/anon/pen/ybzzXZ
P.S. I'm going to add , or like html tags after when we solve the how to add html tags around it.
Some of that js codes which i found in stackoverflow.
function getSelectionText() {
var text = "";
if (window.getSelection) {
text = window.getSelection().text;
} else if (document.selection && document.selection.type != "Control") {
text = document.selection.createRange().text;
}
return text;
}
and the other one is
function replaceSelectionWithHtml(html) {
var range;
if (window.getSelection && window.getSelection().getRangeAt) {
range = window.getSelection().getRangeAt(0);
range.deleteContents();
var div = document.createElement("div");
div.innerHTML = html;
var frag = document.createDocumentFragment(), child;
while ( (child = div.firstChild) ) {
frag.appendChild(child);
}
range.insertNode(frag);
} else if (document.selection && document.selection.createRange) {
range = document.selection.createRange();
range.pasteHTML(html);
}
}
There are several challenges with the problem you present.
First off you need to gather the selected text value. You have posted some examples of that - that is fairly well documented elsewhere so I will leave that up to you to isolate that issue.
Next you need to highlight the selected text. Often to highlight something in HTML we wrap that text that we wish to highlight in a simple element such as a span, then give that span some class - for example often this is used to give a background color to some text. <span style='background-color:yellow'>some text</span> - not so difficult to understand that portion.
The challenge with this then is to combine your "discovered text" with the highlight. Pretty easy to wrap that text as in the span example provided earlier. One issue however is that if that text is previously within some other HTML elements, we need to ensure that the text choice in the discovery is for example not contained within another element AND if so, handle that issue. Let's illustrate that with this span: Howdy <span style='background-color:yellow'>some text</span> Some more.
Now for this example suppose we wish to highlight the text "Howdy some" - a portion of that text is previously within a span with our desired markup, thus we must first extract that, remove that "highlight" and henceforth highlight the new text "choice" of "Howdy some".
To provide an illustration of that. Type the words "This I want" into the text box and see how it gets highlighted.
This is not exactly your problem however it provides the "highlight" which you could potentially combine with your selector. I have NOT fully vetted this for bugs such as typing in HTML in to "highlight".
/* highlight words */
function findStringLimit(searchChar, searchCharIndex, searchedString) {
return searchedString.substring(0, searchedString.lastIndexOf(searchChar, searchCharIndex));
};
function highlightWords(wordsy, text) { /* eliminate a bug with parenthesis */
wordsy = wordsy.replace("(", "");
wordsy = wordsy.replace(")", ""); /* escape other characters for bug */
text = text.replace(";", "");
text = text.replace("'", "'");
text = text.replace("<", "<");
text = text.replace(">", ">");
text = text.replace("<span", "<span");
text = text.replace('autoCompleteWord">', 'autoCompleteWord">');
text = text.replace("</span", "</span");
text = text.replace('span>', 'span>');
var re = '(' + wordsy + ')(?![^<]*(?:<\/span class=\"autoCompleteWord\"|>))';
var regExp = new RegExp(re, 'ig');
var sTag = '<span class="autoCompleteWord">';
var eTag = "</span>";
return text.replace(regExp, sTag + '$&' + eTag);
};
function parseAndHighlight(wordstring, htmlString) {
var htmlStringUn = htmlString;
var found = htmlStringUn.toLowerCase().indexOf(wordstring.toLowerCase(), 0);
if (found >= 0) {
htmlStringUn = highlightWords(wordstring, htmlStringUn);
}
else {
//split and parse the beast
var words = wordstring.split(/\W+/);
var allPhrases = [];
allPhrases.push(wordstring);
var i = 0;
i = words.length;
while (i--) {
allPhrases.push(findStringLimit(" ", allPhrases[(words.length - i) - 1].length, allPhrases[(words.length - i) - 1]));
};
i = allPhrases.length;
while (i--) {
if (allPhrases[i] != "") words = words.concat(allPhrases[i]);
};
i = words.length;
while (i--) {
htmlStringUn = highlightWords(words[i], htmlStringUn);
};
};
return htmlStringUn;
}
$(document).on('change', '#myInput', function() {
var myValue = $('#myInput').val(); //get what was typed
$('#found').text(myValue);
myValue = myValue.replace(/^\s+|\s+$/g, ""); //strip whitespace on ends
$('#found').text(myValue + ':stripped:');
var showText = $('#origshower').text();
var newShowString = parseAndHighlight(myValue, showText); //my original highlighter
$('#shower').html(newShowString);
});
#holder{border:red solid 2px; padding: 5px;}
#myInput{width:200px; background-color: aqua;}
span.autoCompleteWord /* this is the word(s) found */
{
font-weight: bold;
background-color: yellow;
}
#shower{border:lime 2px solid;}
<script
src="https://code.jquery.com/jquery-1.12.4.min.js"
integrity="sha256-ZosEbRLbNQzLpnKIkEdrPv7lOy9C27hHQ+Xp8a4MxAQ="
crossorigin="anonymous"></script>
<div id='holder'>
<input id='myInput' type='text' cols='60' rows='2' />Enter Text to match
</div>
<div id='origshower'>This is the span thistle with the whistle that I want matched is this neat</div>
<div id='shower'>none</div>
<div id='found'>enter</div>
You can just call executeCommand with formatBlock. You can find more information here:
https://developer.mozilla.org/en-US/docs/Web/API/Document/execCommand

JS - focus to a b tag

I want to set the focus to a b tag (<b>[focus should be here]</b>).
My expected result was that the b tag into the div has the focus and if I would write, that the characters are bold.
Is this impossible? How can I do this?
Idea was from here:
focus an element created on the fly
HTML:
<div id="editor" class="editor" contentEditable="true">Hallo</div>
JS onDomready:
var input = document.createElement("b"); //create it
document.getElementById('editor').appendChild(input); //append it
input.focus(); //focus it
My Solution thanks to A1rPun:
add: 'input.tabIndex = 1;' and listen for the follow keys.
HTML:
<h1>You can start typing</h1>
<div id="editor" class="editor" contentEditable="true">Hallo</div>
JS
window.onload = function() {
var input = document.createElement("b"); //create it
document.getElementById('editor').appendChild(input); //append it
input.tabIndex = 1;
input.focus();
var addKeyEvent = function(e) {
//console.log('add Key');
var key = e.which || e.keyCode;
this.innerHTML += String.fromCharCode(key);
};
var addLeaveEvent = function(e) {
//console.log('blur');
// remove the 'addKeyEvent' handler
e.target.removeEventListener('keydown', addKeyEvent);
// remove this handler
e.target.removeEventListener(e.type, arguments.callee);
};
input.addEventListener('keypress', addKeyEvent);
input.addEventListener('blur', addLeaveEvent);
};
You can add a tabIndex property to allow the element to be focused.
input.tabIndex = 1;
input.focus();//now you can set the focus
jsfiddle
Edit:
I think the best way to solve your problem is to style an input tag with font-weight: bold.
I had to cheat a little by adding an empty space inside the bold area because I couldn't get it to work on the empty element.
This works by moving the selector inside the last element in the contentEditable since the bold element is the last one added.
It can be edited to work on putting the focus on any element.
http://jsfiddle.net/dnzajx21/3/
function appendB(){
var bold = document.createElement("b");
bold.innerHTML = " ";
//create it
document.getElementById('editor').appendChild(bold); //append it
setFocus();
}
function setFocus() {
var el = document.getElementById("editor");
var range = document.createRange();
var sel = window.getSelection();
range.setStartAfter(el.lastChild);
range.collapse(true);
sel.removeAllRanges();
sel.addRange(range);
el.focus();
}
The SetFocus function I took was from this question: How to set caret(cursor) position in contenteditable element (div)?

Categories

Resources