Update highlight of sentences in TinyMCE on the fly - javascript

Okay. I've used Markjs.io and TinyMCE to create a little tool highlighting long sentences (above n-amount of words).
I currently have the text highlighted correctly on load.
I then tried to add som .on('keyup',function()) and .on('change', function()) to run the function and recalculate the highlights / marks as I type.
However. This approach apparently keeps returning the original text. And I can't add any new text to the field.
What I wanna do:
So what I wanna do is figure out how my function should run to keep recalculating and highlight long sentences. But in a way, so that it actually also add in the new text I'm typing or current edits I do in the TinyMCE editor.
Available on CodePen here: http://codepen.io/MarkBuskbjerg/pen/rWWRbX
HTML:
<div id="myTextArea" contenteditable="true">
Any text will do. Above 16 words in a single sentence - from dot to dot - will be highlightet for all the world to see.
</div>
JavaScript (jQuery):
tinymce.init({
selector: '#myTextArea',
height: 300,
setup: function(ed) {
ed.on('change', myCustomInitInstance);
ed.on('keyup', myCustomInitInstance);
ed.on('paste', myCustomInitInstance);
ed.on('cut', myCustomInitInstance);
},
init_instance_callback: "myCustomInitInstance",
});
function myCustomInitInstance(inst) {
var rawText = tinyMCE.get('myTextArea').getContent({
format: 'text'
});
var sentenceArray = rawText.split(".");
var matchWarning = [];
var longSentence = 16;
var words;
var wordCounter;
var output;
for (var i in sentenceArray) {
words = sentenceArray[i].split(" ");
wordCounter = words.length;
if (wordCounter > longSentence) {
matchWarning.push(sentenceArray[i]);
}
}
var $clone = $("#myTextArea").clone();
$clone.mark(matchWarning, {
"separateWordSearch": false,
});
tinyMCE.activeEditor.setContent($clone.html());
}):

The issue is the line:
var $clone = $("#myTextArea").clone();
Which just gets the original value from the textarea each time which is why it isn't updating.
Using makejs on the the body element of the WYSIWYG iframe instead should work:
function myCustomInitInstance(inst) {
var rawText = tinyMCE.get('myTextArea').getContent({
format: 'text'
});
var sentenceArray = rawText.split(".");
var matchWarning = [];
var longSentence = 16;
var words;
var wordCounter;
var output;
for (var i in sentenceArray) {
words = sentenceArray[i].split(" ");
wordCounter = words.length;
if (wordCounter > longSentence) {
matchWarning.push(sentenceArray[i]);
}
}
var editor = tinyMCE.activeEditor;
// Store the selection
var bookmark = editor.selection.getBookmark();
// Remove previous marks and add new ones
$(editor.getBody()).unmark().mark(matchWarning, {
acrossElements: true,
"separateWordSearch": false,
});
// Restore the selection
editor.selection.moveToBookmark(bookmark);
}

Related

Trouble with markjs.io and TinyMCE Bookmark when caret is active on text

Okay. I'm using TinyMCE and MarkJS to highlight long words in the text. If words are longer than 6 letters they are marked.
I've put together a demo here: http://codepen.io/MarkBuskbjerg/pen/RomqKO
The problem:
At first glance everything is mighty fine. The long words are highligthed perfectly.
But...
If you place the caret inside the word. The highlight disappears on keydown.
Example: Place the caret at the end of the text and arrow key your way throught the text the highlight is gone when the caret is on a highlighted word.
I suspect that the problem is due to TinyMCE triggering some sort of event when the caret is on a word.
Possible solution
I've tried to see if the caret is somehow splitting the word and thereby making it seem shorter. Which doesn't seem to be the case (see the console.log(words) on line 24.
** EDIT 30-12-2016:**
Seems like it ought to be TinyMCE triggering the error when the caret is inside of a word. Maybe because it execute some other event?
My question
I somehow need to find a way to keep the word highlighted while the caret is active on the highlighted span.
Any ideas on how to achieve this is very much appreciated :)
The HTML markup is:
<div class="container">
<div id="myTextArea" contenteditable="true">
Lorem ipsum dolores sit amet.
</div>
</div>
The JavaScript goes like this:
tinymce.init({
selector: '#myTextArea',
height: 200,
setup: function(ed) {
ed.on('keyup', myCustomInitInstance);
ed.on('paste', myCustomInitInstance);
ed.on('cut', myCustomInitInstance);
},
init_instance_callback: "myCustomInitInstance",
});
function myCustomInitInstance(inst) {
var rawText = tinyMCE.get('myTextArea').getContent({
format: 'text'
});
rawText = rawText.replace(/<([^>]+)>|[\.\+\?,\(\)"'\-\!\;\:]/ig, "");
var words = rawText.split(" ");
var matchWarning = [];
var longWord = 6;
var wordCounter;
var output;
for (var i = 0, len = words.length; i < len; i++) {
if (words[i].length > longWord) {
matchWarning.push(words[i]);
}
}
var editor = tinyMCE.activeEditor;
// Store the selection
var bookmark = editor.selection.getBookmark();
console.log(bookmark);
// Remove previous marks and add new ones
var $ctx = $(editor.getBody());
$ctx.unmark({
done: function() {
$ctx.mark(matchWarning, {
acrossElements: true,
//debug: true,
separateWordSearch: false
});
}
});
console.log(bookmark);
// Restore the selection
editor.selection.moveToBookmark(bookmark);
}
Your problem is that you're listening to the keyup event, which is also fired if you're navigating with the arrow keys. And in the callback function of this event listener you've removing the highlighted terms using .unmark(), which is why the highlighting disappears.
To solve this, you need to ignore events from arrow keys. I've done this for you. Also I've refactored your example to simplify the situation, removed unnecessary variables and used TinyMCE v4 methods (which you're using).
Example
tinymce.init({
selector: "#myTextArea",
height: 200,
setup: function(editor) {
editor.on("init keyup", function(e) {
var code = (e.keyCode || e.which || 0);
// do nothing if it's an arrow key
if (code == 37 || code == 38 || code == 39 || code == 40) {
return;
}
myCustomInitInstance();
});
}
});
function myCustomInitInstance() {
var editor = tinymce.get("myTextArea"),
rawText = editor.getContent({
format: "text"
}).replace(/<([^>]+)>|[\.\+\?,\(\)"'\-\!\;\:]/ig, ""),
words = rawText.split(" "),
matchWarning = [],
longWord = 6;
for (var i = 0, len = words.length; i < len; i++) {
if (words[i].length > longWord) {
matchWarning.push(words[i]);
}
}
console.log(matchWarning);
var bookmark = editor.selection.getBookmark();
var $ctx = $(editor.getBody());
$ctx.unmark({
done: function() {
$ctx.mark(matchWarning, {
acrossElements: true,
separateWordSearch: false,
done: function() {
editor.selection.moveToBookmark(bookmark);
}
});
}
});
}

Get the count of words in tinymce

I have a word count div outside the tinymce which shows the word count but not using the wordCount plugin but using regex to count the words.
But this count is not showing correct value when i add bullet or apply bold to the already typed text[It is showing count as 3 while i have entered only one word at the time of using bullet and increases the count by 2 while highlighting the already typed text]
Can any one can suggest what to do in the regex to get correct count when using the bold or,italic,underline or bullets or using wordCount plugin to use it's output outside the stauts bar[In this case in my word count div]
Here is the code :
tinymceConfig = {
mode:"exact",
elements:"essay",
menubar: false,
statusbar: false,
plugins: "autoresize",
content_css : '../../theme/css/Language/editor.css',
toolbar : "bold italic underline bullist",
resize:"height",
autoresize_max_height: 325,
setup : function(editor) {
if ($('#essay').prop('readonly')) {
editor.settings.readonly = true;
}
editor.on('keydown', function (evt) {
var wordCount = 0;
var valid_keys = [8, 46];
text = editor.getContent().replace(/(< ([^>]+)<)/g, '').replace(/\s+/g, ' ');
text = text.replace(/^\s\s*/, '').replace(/\s\s*$/, '');
wordCount = text.split(' ').length-1;
if(wordCount >= Helpers.constants.MAX_WORDS && valid_keys.indexOf(evt.keyCode) == -1)
{
evt.preventDefault();
Helpers.prompt('You have reached the maximum word limit.');
//evt.stopPropagation();
return false;
}
});
editor.on('keyup', function (evt) {
var text = '';
clearTimeout(saveEssayIntervalId);
saveEssayIntervalId = setTimeout(function() {
saveEssay('silent');
}, 3000);
text = editor.getContent().replace(/(< ([^>]+)<)/g, '').replace(/\s+/g, ' ');
text = text.replace(/^\s\s*/, '').replace(/\s\s*$/, '');
var wordCount = text.split(' ').length;
$("#essayContainer .textAreaAfter").html("[ Words entered: "+wordCount+" ]");
});
} };
tinyMCE.init(tinymceConfig);
You can get the current word count from TinyMCE's WordCount plugin - you should not need to calculate this yourself.
theEditor = tinymce.activeEditor;
wordCount = theEditor.plugins.wordcount.getCount();
If you have an old tinyMCE version, you may not have the getCount() function, in this case you can write, for the active editor (else pass on the editor's object):
var editor = tinyMCE.activeEditor,
words = editor.plugins.wordcount._getCount(editor);

Function does not work on second occurence?

I am trying to change the color of multiple texts to a certain color by using this code:
var search = "bar";
$("div:contains('"+search+"')").each(function () {
var regex = new RegExp(search,'gi');
$(this).html($(this).text().replace(regex, "<span class='red'>"+search+"</span>"));
});
However, the code does not work a second time, and I am not sure why--it only changes the newest occurrence of the code.
Here is a JSFiddle using it twice where it is only changing the 2nd occurrence: http://jsfiddle.net/PELkt/189/
Could someone explain why it does not work on the 2nd occurrence?
Could someone explain why it does not work on the 2nd occurrence?
By calling .text() you are removing all the HTML markup, including the <span>s you just inserted.
This is the markup after the first replacement:
<div id="foo">this is a new <span class='red'>bar</span></div>
$(this).text() will return the string "this is a new bar", in which replace "new" with a <span> ("this is a <span class='red'>new</span> bar") and set it as new content of the element.
In order to do this right, you'd have to iterate over all text node descendants of the element instead, and process them individually. See Highlight a word with jQuery for an implementation.
It was easy to fix your jsfiddle. Simply replace both .text() with .html() & you'll see that it highlights new & both bars in red.
jQuery's .text() method will strip all markup each time that it's used, but what you want to do is use .html() to simply change the markup which is already in the DOM.
$(document).ready(function () {
var search = "bar";
$("div:contains('"+search+"')").each(function () {
var regex = new RegExp(search,'gi');
$(this).html($(this).html().replace(regex, "<span class='red'>"+search+"</span>"));
});
search = "new";
$("div:contains('"+search+"')").each(function () {
var regex = new RegExp(search,'gi');
$(this).html($(this).html().replace(regex, "<span class='red'>"+search+"</span>"));
});
});
Here is another way of doing it that will allow you to continue using text if you wish
function formatWord(content, term, className){
return content.replace(new RegExp(term, 'g'), '<span class="'+className+'">'+term+'</span>');
}
$(document).ready(function () {
var content = $('#foo').text();
var change1 = formatWord(content, 'bar', 'red'),
change2 = formatWord(change1, 'foo', 'red');
alert(change2);
$('body').html(change2);
});
http://codepen.io/nicholasabrams/pen/wGgzbR?editors=1010
Use $(this).html() instead of $(this).text(), as $.fn.text() strips off all the html tags, so are the <span class="red">foo</span> stripped off to foo.
But let's say that you apply same highlight multiple times for foo, then I would suggest that you should create a class similar to this to do highlighting
var Highlighter = function ($el, initialArray) {
this._array = initialArray || [];
this.$el = $el;
this.highlight = function (word) {
if (this.array.indexOf(word) < 0) {
this.array.push(word);
}
highlightArray();
}
function highlightArray() {
var search;
// first remove all highlighting
this.$el.find("span[data-highlight]").each(function () {
var html = this.innerHTML;
this.outerHTML = html;
});
// highlight all here
for (var i = 0; i < this._array.length; i += 1) {
search = this._array[i];
this.$el.find("div:contains('"+search+"')").each(function () {
var regex = new RegExp(search,'gi');
$(this).html($(this).html().replace(regex, "<span data-highlight='"+search+"' class='red'>"+search+"</span>"));
});
}
}
}
var highlighter = new HighLighter();
highlighter.highlight("foo");

Split div content at cursor position

I need to split an element after user clicks on it and the attr 'contenteditable' becomes 'true'. This fiddle works for the first paragraph but not second because the latter is in a p tag. Similary in this fiddle you will see that when the element has html tags in it, the counter loses accuracy and hence the text before and after the cursor is not what you'd expect.
The assumption here is that the users will split the data in a way that the help tags will stay intact. As pointed out by dandavis here, e.g. the div has <i>Hello</i> <b>Wo*rld</b>, the user will only need to split the div into two divs, first will have <i>Hello</i> and the second div will have <b>Wo*rld</b> in it.
Html:
<div><mark>{DATE}</mark><i>via email: </i><mark><i>{EMAIL- BROKER OR TENANT}</i></mark></div>
JS:
var $splitbut = $('<p class="split-but">Split</p>');
$(this).attr('contenteditable', 'true').addClass('editing').append($splitbut);
var userSelection;
if (window.getSelection) {
userSelection = window.getSelection();
}
var start = userSelection.anchorOffset;
var end = userSelection.focusOffset;
var before = $(this).html().substr(0, start);
var after = $(this).html().substr(start, $(this).html().length);
The "Split" button is not working as generating the html is not an issue once I get proper "after" and "before" text. Any ideas as to what I am doing wrong here?
Something like this could work for the specific case you describe
$('div, textarea').on('click', function(e) {
var userSelection;
if (window.getSelection) {
userSelection = window.getSelection();
}
var start = userSelection.anchorOffset,
end = userSelection.focusOffset,
node = userSelection.anchorNode,
allText = $(this).text(),
nodeText = $(node).text();
// before and after inside node
var nodeBefore = nodeText.substr(0, start);
var nodeAfter = nodeText.substr(start, nodeText.length);
// before and after for whole of text
var allExceptNode = allText.split(nodeText),
before = allExceptNode[0] + nodeBefore,
after = nodeAfter + allExceptNode[1];
console.log('Before: ', before);
console.log('------');
console.log('After: ', after);
});
Updated demo at https://jsfiddle.net/gaby/vaLz55fv/10/
It might exhibit issues if there are tags whose content is repeated in the whole text. (problem due to splitting)

Any way to prevent losing focus when clicking an input text out of tinymce container?

I've made this tinymce fiddle to show what I say.
Highlight text in the editor, then click on the input text, highlight in tinyMCE is lost (obviously).
Now, I know it's not easy since both, the inline editor and the input text are in the same document, thus, the focus is only one. But is there any tinymce way to get like an "unfocused" highlight (gray color) whenever I click in an input text?
I'm saying this because I have a customized color picker, this color picker has an input where you can type in the HEX value, when clicking OK it would execCommand a color change on the selected text, but it looks ugly because the highlight is lost.
I don't want to use an iframe, I know that by using the non-inline editor (iframe) is one of the solutions, but for a few reasons, i can't use an iframe text editor.
Any suggestion here? Thanks.
P.S: Out of topic, does any of you guys know why I can't access to tinymce object in the tinyMCE Fiddle ? looks like the tinyMCE global var was overwritten by the tinymce select dom element of the page itself. I can't execute a tinyMCE command lol.
Another solution:
http://fiddle.tinymce.com/sBeaab/5
P.S: Out of topic, does any of you guys know why I can't access to
tinymce object in the tinyMCE Fiddle ? looks like the tinyMCE global
var was overwritten by the tinymce select dom element of the page
itself. I can't execute a tinyMCE command lol.
Well, you can access the tinyMCE variable and even execute commands.
this line is wrong
var colorHex = document.getElementById("colorHex")
colorHex contains input element, not value.
var colorHex = document.getElementById("colorHex").value
now it works ( neolist couldn't load, so I removed it )
http://fiddle.tinymce.com/DBeaab/1
I had to do something similar recently.
First off, you can't really have two different elements "selected" simultaneously. So in order to accomplish this you're going to need to mimic the browser's built-in 'selected text highlight'. To do this, you're going to have to insert spans into the text to simulate highlighting, and then capture the mousedown and mouseup events.
Here's a fiddle from StackOverflow user "fullpipe" which illustrates the technique I used.
http://jsfiddle.net/fullpipe/DpP7w/light/
$(document).ready(function() {
var keylist = "abcdefghijklmnopqrstuvwxyz123456789";
function randWord(length) {
var temp = '';
for (var i=0; i < length; i++)
temp += keylist.charAt(Math.floor(Math.random()*keylist.length));
return temp;
}
for(var i = 0; i < 500; i++) {
var len = Math.round(Math.random() * 5 + 3);
document.body.innerHTML += '<span id="'+ i +'">' + randWord(len) + '</span> ';
}
var start = null;
var end = null;
$('body').on('mousedown', function(event) {
start = null;
end = null;
$('span.s').removeClass('s');
start = $(event.target);
start.addClass('s');
});
$('body').on('mouseup', function(event) {
end = $(event.target);
end.addClass('s');
if(start && end) {
var between = getAllBetween(start,end);
for(var i=0, len=between.length; i<len;i++)
between[i].addClass('s');
alert('You select ' + (len) + ' words');
}
});
});
function getAllBetween(firstEl,lastEl) {
var firstIdx = $('span').index($(firstEl));
var lastIdx = $('span').index($(lastEl));
if(lastIdx == firstIdx)
return [$(firstEl)];
if(lastIdx > firstIdx) {
var firstElement = $(firstEl);
var lastElement = $(lastEl);
} else {
var lastElement = $(firstEl);
var firstElement = $(lastEl);
}
var collection = new Array();
collection.push(firstElement);
firstElement.nextAll().each(function(){
var siblingID = $(this).attr("id");
if (siblingID != $(lastElement).attr("id")) {
collection.push($(this));
} else {
return false;
}
});
collection.push(lastElement);
return collection;
}
As you can see in the fiddle, the gibberish text in the right pane stays highlighted regardless of focus elsewhere on the page.
At that point, you're going to have to apply your color changes to all matching spans.

Categories

Resources