CodeMirror with spell checker - javascript

I would like to use the functionality of CodeMirror (such as linenumbering, wrapping, search, etc.) for plain text, without particular need of code highlightening but instead with Google Chrome spell checker or some other natural language (especially English) spell checking activated (I do not need to have it work on other browsers). How can I do this? Is it possible to write a plain text mode add-on that enables spell checking?

I actually integrated typo.js with CodeMirror while coding for NoTex.ch; you can have a look at it here CodeMirror.rest.js; I needed a way to get the reStructuredText markup spell checked, and since I use CodeMirror's excellent syntax highlighting capabilities, it was quite straight forward to do.
You can check the code at the provided link, but I'll summarize, what I've done:
Initialize the typo.js library; see also the author's blog/documentation:
var typo = new Typo ("en_US", AFF_DATA, DIC_DATA, {
platform: 'any'
});
Define a regular expression for your word separators:
var rx_word = "!\"#$%&()*+,-./:;<=>?#[\\\\\\]^_`{|}~";
Define an overlay mode for CodeMirror:
CodeMirror.defineMode ("myoverlay", function (config, parserConfig) {
var overlay = {
token: function (stream, state) {
if (stream.match (rx_word) &&
typo && !typo.check (stream.current ()))
return "spell-error"; //CSS class: cm-spell-error
while (stream.next () != null) {
if (stream.match (rx_word, false)) return null;
}
return null;
}
};
var mode = CodeMirror.getMode (
config, parserConfig.backdrop || "text/x-myoverlay"
);
return CodeMirror.overlayMode (mode, overlay);
});
Use the overlay with CodeMirror; see the user manual to figure out how exactly you do this. I've done it in my code so you could check it out there too, but I recommend the user manual.
Define CSS class:
.CodeMirror .cm-spell-error {
background: url(images/red-wavy-underline.gif) bottom repeat-x;
}
This approach works great for German, English and Spanish. With the French dictionary typo.js seems to have some (accent) problems, and languages like Hebrew, Hungarian, and Italian - where the number of affixes is long or the dictionary is quite extensive - it does not work really, since typo.js at its current implementation uses too much memory and is too slow.
With German (and Spanish) typo.js can block the JavaScript VM for a few hundred milliseconds (but only during initialization!), so you might want to consider background threads with HTML5 web workers (see CodeMirror.typo.worker.js for an example). Further typo.js does not seem to support Unicode (due to JavaScript restrictions): At least, I did not manage to get it to work with non-Latin languages like Russian, Greek, Hindi etc.
I've not refactored the described solution into a nice separate project apart from (now quite big) NoTex.ch, but I might do it quite soon; till then you've to patch your own solution based on the above description or hinted code. I hope this helps.

In CodeMirror 5.18.0 and above, you can set inputStyle: 'contenteditable' with spellcheck: true to be able to use your web browser's spellcheck features. For example:
var myTextArea = document.getElementById('my-text-area');
var editor = CodeMirror.fromTextArea(myTextArea, {
inputStyle: 'contenteditable',
spellcheck: true,
});
The relevant commits that made this solution possible are:
Make inputStyle an option, move relevant logic into TextareaInput (CodeMirror 5.0.0, February 2015)
Add experimental spellcheck option (CodeMirror 5.18.0, August 2016)

This is a working version of hsk81's answer. It uses CodeMirror's overlay mode, and looks for any word inside quotes, html tags, etc. It has a sample typo.check that should be replaced with something like Typo.js. It underlines unknown words with a red squiggly line.
This was tested using an IPython's %%html cell.
<style>
.CodeMirror .cm-spell-error {
background: url("https://raw.githubusercontent.com/jwulf/typojs-project/master/public/images/red-wavy-underline.gif") bottom repeat-x;
}
</style>
<h2>Overlay Parser Demo</h2>
<form><textarea id="code" name="code">
</textarea></form>
<script>
var typo = { check: function(current) {
var dictionary = {"apple": 1, "banana":1, "can't":1, "this":1, "that":1, "the":1};
return current.toLowerCase() in dictionary;
}
}
CodeMirror.defineMode("spell-check", function(config, parserConfig) {
var rx_word = new RegExp("[^\!\"\#\$\%\&\(\)\*\+\,\-\.\/\:\;\<\=\>\?\#\[\\\]\^\_\`\{\|\}\~\ ]");
var spellOverlay = {
token: function (stream, state) {
var ch;
if (stream.match(rx_word)) {
while ((ch = stream.peek()) != null) {
if (!ch.match(rx_word)) {
break;
}
stream.next();
}
if (!typo.check(stream.current()))
return "spell-error";
return null;
}
while (stream.next() != null && !stream.match(rx_word, false)) {}
return null;
}
};
return CodeMirror.overlayMode(CodeMirror.getMode(config, parserConfig.backdrop || "text/html"), spellOverlay);
});
var editor = CodeMirror.fromTextArea(document.getElementById("code"), {mode: "spell-check"});
</script>

CodeMirror is not based on an HTML textarea, so you can't use the built-in spell check
You could implement your own spell check for CodeMirror with something like typo.js
I don't believe anyone has done this yet.

I wrote a squiggly underline type spell checker a while ago. It needs a rewrite to be honest I was very new to JavaScript then. But the principles are all there.
https://github.com/jameswestgate/SpellAsYouType

I created a spellchecker with typos suggestions/corrections:
https://gist.github.com/kofifus/4b2f79cadc871a29439d919692099406
demo: https://plnkr.co/edit/0y1wCHXx3k3mZaHFOpHT
Below are relevant parts of the code:
First I make a promise to get load the dictionaries. I use typo.js for the dictionary, loading can take a while if they are not hosted locally, so it is better to start the loading as soon as your starts before login/CM initializing etc:
function loadTypo() {
// hosting the dicts on your local domain will give much faster results
const affDict='https://rawgit.com/ropensci/hunspell/master/inst/dict/en_US.aff';
const dicDict='https://rawgit.com/ropensci/hunspell/master/inst/dict/en_US.dic';
return new Promise(function(resolve, reject) {
var xhr_aff = new XMLHttpRequest();
xhr_aff.open('GET', affDict, true);
xhr_aff.onload = function() {
if (xhr_aff.readyState === 4 && xhr_aff.status === 200) {
//console.log('aff loaded');
var xhr_dic = new XMLHttpRequest();
xhr_dic.open('GET', dicDict, true);
xhr_dic.onload = function() {
if (xhr_dic.readyState === 4 && xhr_dic.status === 200) {
//console.log('dic loaded');
resolve(new Typo('en_US', xhr_aff.responseText, xhr_dic.responseText, { platform: 'any' }));
} else {
console.log('failed loading aff');
reject();
}
};
//console.log('loading dic');
xhr_dic.send(null);
} else {
console.log('failed loading aff');
reject();
}
};
//console.log('loading aff');
xhr_aff.send(null);
});
}
Second I add an overlay to detect and mark typos like this:
cm.spellcheckOverlay={
token: function(stream) {
var ch = stream.peek();
var word = "";
if (rx_word.includes(ch) || ch==='\uE000' || ch==='\uE001') {
stream.next();
return null;
}
while ((ch = stream.peek()) && !rx_word.includes(ch)) {
word += ch;
stream.next();
}
if (! /[a-z]/i.test(word)) return null; // no letters
if (startSpellCheck.ignoreDict[word]) return null;
if (!typo.check(word)) return "spell-error"; // CSS class: cm-spell-error
}
}
cm.addOverlay(cm.spellcheckOverlay);
Third I use a list box to show suggestions and fix typos:
function getSuggestionBox(typo) {
function sboxShow(cm, sbox, items, x, y) {
let selwidget=sbox.children[0];
let options='';
if (items==='hourglass') {
options='<option>⌛</option>'; // hourglass
} else {
items.forEach(s => options += '<option value="' + s + '">' + s + '</option>');
options+='<option value="##ignoreall##">ignore all</option>';
}
selwidget.innerHTML=options;
selwidget.disabled=(items==='hourglass');
selwidget.size = selwidget.length;
selwidget.value=-1;
// position widget inside cm
let cmrect=cm.getWrapperElement().getBoundingClientRect();
sbox.style.left=x+'px';
sbox.style.top=(y-sbox.offsetHeight/2)+'px';
let widgetRect = sbox.getBoundingClientRect();
if (widgetRect.top<cmrect.top) sbox.style.top=(cmrect.top+2)+'px';
if (widgetRect.right>cmrect.right) sbox.style.left=(cmrect.right-widgetRect.width-2)+'px';
if (widgetRect.bottom>cmrect.bottom) sbox.style.top=(cmrect.bottom-widgetRect.height-2)+'px';
}
function sboxHide(sbox) {
sbox.style.top=sbox.style.left='-1000px';
}
// create suggestions widget
let sbox=document.getElementById('suggestBox');
if (!sbox) {
sbox=document.createElement('div');
sbox.style.zIndex=100000;
sbox.id='suggestBox';
sbox.style.position='fixed';
sboxHide(sbox);
let selwidget=document.createElement('select');
selwidget.multiple='yes';
sbox.appendChild(selwidget);
sbox.suggest=((cm, e) => { // e is the event from cm contextmenu event
if (!e.target.classList.contains('cm-spell-error')) return false; // not on typo
let token=e.target.innerText;
if (!token) return false; // sanity
// save cm instance, token, token coordinates in sbox
sbox.codeMirror=cm;
sbox.token=token;
let tokenRect = e.target.getBoundingClientRect();
let start=cm.coordsChar({left: tokenRect.left+1, top: tokenRect.top+1});
let end=cm.coordsChar({left: tokenRect.right-1, top: tokenRect.top+1});
sbox.cmpos={ line: start.line, start: start.ch, end: end.ch};
// show hourglass
sboxShow(cm, sbox, 'hourglass', e.pageX, e.pageY);
// let the ui refresh with the hourglass & show suggestions
setTimeout(() => {
sboxShow(cm, sbox, typo.suggest(token), e.pageX, e.pageY); // typo.suggest takes a while
}, 100);
e.preventDefault();
return false;
});
sbox.onmouseleave=(e => {
sboxHide(sbox)
});
selwidget.onchange=(e => {
sboxHide(sbox)
let cm=sbox.codeMirror, correction=e.target.value;
if (correction=='##ignoreall##') {
startSpellCheck.ignoreDict[sbox.token]=true;
cm.setOption('maxHighlightLength', (--cm.options.maxHighlightLength) +1); // ugly hack to rerun overlays
} else {
cm.replaceRange(correction, { line: sbox.cmpos.line, ch: sbox.cmpos.start}, { line: sbox.cmpos.line, ch: sbox.cmpos.end});
cm.focus();
cm.setCursor({line: sbox.cmpos.line, ch: sbox.cmpos.start+correction.length});
}
});
document.body.appendChild(sbox);
}
return sbox;
}
Hope this helps !

Related

ACE Editor - Beautify for CSS

I'm currently implementing ace as an editor for several programming languages. I really want to implement a beautify functionality and currently I use this approach:
var beautify = ace.require("ace/ext/beautify"); // get reference to extension
var editor = ace.edit("editor"); // get reference to editor
beautify.beautify(editor.session);
More info
Sadly this does not format CSS correctly. Example:
.class1 .subClass { color: red; }
beautified via the described code this changes to
.class1.subClass{
color:red;
}
as you can see all spaces in the selector were removed and this changes the target of the rule.
Is my code wrong? Is there a alternative beautifier for CSS in ace?
As a fallback I would remove the functionality which isn't the ideal solution.
TL;DR
Is there a plugin/extension for ace that can beautify CSS correctly? Am I doing something wrong?
Well I found a nice css beautifier which i added to my solution.
Here's the magic:
container.querySelector('.editor_beautify').addEventListener('click', function () {
var currentMode = editor.session.$modeId.substr(editor.session.$modeId.lastIndexOf('/') + 1);
switch (currentMode) {
case modes.css:
util.formatCss(editor, configuration.scriptBase);
break;
default:
util.ensureScript(configuration.scriptBase + 'ext-beautify.js', function () {
var beautify = ace.require("ace/ext/beautify").beautify(editor.session);
});
}
});
formatCss: function (editorAce, scriptBase) {
var unformatted = editorAce.getValue();
if (unformatted.trim().length > 0) {
util.ensureScript(scriptBase + 'cssbeautify.js', function () {
editorAce.getSession().setUseWrapMode(false);
var formatted = cssbeautify(unformatted, {
indent: ' ',
openbrace: 'end-of-line',
autosemicolon: true
});
editorAce.setValue(formatted);
editorAce.getSession().setUseWrapMode(true);
});
}
}

Using Javascript to change website language

I'm working on a GUI website that can use several languages. The original HTML-files I got to work with were totally static. So if translation was needed I had to parse through alle files, note where some words or terms were, collect them all hand them to the translation department and enter those translations in the new language files.
Since those files were totally static it meant having to translate whole sections several times. Not very effictient.
So now I am working on some kind of dictionary in Javascript, to just exchange the terms in those websites. Mostly it works this way:
var dicEnglish = {
term 1: "This is the English text"
Ref: "Another English text"
}
var dicFrench = {
term 1: "This is the French text"
Ref: "Another French text"
}
Which contains all the possible content that needs to be changed. Every candidate in the HTML-code gets a class="dicRef" id="l_dicTag_#"as identifier, which I slice down to the dictionary tag and exchange with the following code:
var imgSrc = "en";
var ActiveDic;
var langSel;
if(window.name){
langSel=window.name;
}
else{langSel="English";
}
function LangChange(){
langClass = document.getElementsByClassName("dicRef");
var i = langClass.length;
var Start, Stop, idSrc, idDic;
var navText;
switch(langSel){
case "French":
langSel="French";
imgSrc = "en";
navText="Anglais";
break;
case "English":
case "Anglais":
default:
langSel="English";
imgSrc = "fr";
navText="French";
break;
}
ActiveDic="dic"+langSel;
window.name=langSel;
while(i--){
idSrc = langClass[i].id;
Start=idSrc.indexOf("_")+1;
Stop=idSrc.lastIndexOf("_");
idDic=idSrc.slice(Start,Stop);
if(window[ActiveDic][idDic]){
document.getElementById(idSrc).innerHTML=window[ActiveDic][idDic];}
else{
document.getElementById(idSrc).innerHTML="N/A";
}
}
if(document.getElementById("imgSel")){
document.getElementById("imgSel").src="../../img/"+imgSrc+".gif";
}
if (document.getElementById("l_SelLang1_1")){
document.getElementById("l_SelLang1_1").innerHTML=navText;
}
}
The problem lies in the uniqueness of the id-tag. Since some terms can occur more than once and some are generated the counter is needed. I'd prefer to ommit the counter, but can't find any other identifier to sort out all target terms and change their content.
Since I want to be safe for the future I'd prefer a solution that makes it possible to handle a possible third language. Working with the inner HTML would need to tag the same term several times, once for each language.
So is there any way to target all terms to be exchanged more efficently and easily, or a better way to do it? I can only work with client-side solutions, so no PHP and so on.
No offense to the other answerers but storing the text in JavaScript or in data attributes is not good for search engines or disabled site visitors and offers no benefits while added unnecessarily complicated code. The best and most simple solution in my opinion is to make use of HTML lang attribute and use JavaScript to show and hide the desired language. This solution also gracefully degrades so if a site visitor has their JavaScript disabled it will still display the content. Here is my solution:
HTML
<button id="switch-lang">Switch Language</button>
<h1><span lang="en">Hello</span> <span lang="es">Hola</span></h1>
<p lang="en">I really enjoy coding.</p>
<p lang="es">Me gusta mucho la codificación.</p>
jQuery
$('[lang="es"]').hide();
$('#switch-lang').click(function() {
$('[lang="es"]').toggle();
$('[lang="en"]').toggle();
});
Then I would recommend adding HTML5 Geolocation to determine which language to show initially based on the users location in the world. I would also use Fontawesome language icon to show users they can switch languages in a way that is understandable by anyone: http://fontawesome.io/icon/language/
Here is the working code example at CodePen: https://codepen.io/codepajamas/pen/ZejaQz?editors=1010
Here is an additional example on code pen using a select menu to change between 3
(or more) languages: https://codepen.io/codepajamas/pen/NjGOMV
Updated Full Example with Geolocation and Cookies
I kept working on this and created an updated example switching between two languages Chinese and English (if you need more than two languages you would have to hide all languages and show only the one selected instead of using toggle the way I am). This code also detects if an existing cookie is already set for the language using jQuery Cookie. It also checks their geolocation if their browser supports it automatically setting the language to Chinese if they are in either Taiwan or China and defaults to English in all other countries. The code below is commented so you can see what each step is doing and hopefully be able to modify it to suit your needs. Here it is:
HTML
<button id="switch-lang">Switch Language Icon Here</button>
<h1><span lang="en">Hello</span> <span lang="zh">你好</span></h1>
<p lang="en">I really enjoy coding.</p>
<p lang="zh">我真的很喜歡編碼。</p>
jQuery
Note: this requires linking to not only jQuery but also jQuery Cookie
$(function () {
///// Language Switching (2 languages: English and Chinese). /////
// Initially disable language switching button.
$('#switch-lang').css({'pointer-events':'none',
'cursor':'default'}).attr('disabled','disabled');
function langButtonListen() {
$('#switch-lang').click(function (event) {
event.preventDefault();
$('[lang="zh"]').toggle();
$('[lang="en"]').toggle();
// Switch cookie stored language.
if ($.cookie('lang') === 'en') {
$.cookie('lang', 'zh', { expires: 7 });
} else {
$.cookie('lang', 'en', { expires: 7 });
}
});
// Enable lang switching button.
$('#switch-lang').css({'pointer-events':'auto',
'cursor':'pointer'}).removeAttr('disabled');
}
// Check if language cookie already exists.
if ($.cookie('lang')) {
var lang = $.cookie('lang');
if (lang === 'en') {
$('[lang="zh"]').hide();
langButtonListen();
} else {
$('[lang="en"]').hide();
langButtonListen();
}
} else {
// no cookie set, so detect language based on location.
if ("geolocation" in navigator) {
// geolocation is available
navigator.geolocation.getCurrentPosition(function (position) {
// accepted geolocation so figure out which country
var lat = position.coords.latitude,
lng = position.coords.longitude;
$.getJSON('http://maps.googleapis.com/maps/api/geocode/json?latlng='+lat+','+lng+'&sensor=true', null, function (response) {
var country = response.results[response.results.length-1].formatted_address;
if (country === 'Taiwan' || country === 'China') {
$('[lang="en"]').hide();
$.cookie('lang', 'zh', { expires: 7 });
langButtonListen();
} else {
$('[lang="zh"]').hide();
$.cookie('lang', 'en', { expires: 7 });
langButtonListen();
}
}).fail(function (err) {
console.log('error: '+err);
$('[lang="zh"]').hide();
$.cookie('lang', 'en', { expires: 7 });
langButtonListen();
});
},
function (error) {
if (error.code == error.PERMISSION_DENIED) {
// denied geolocation
$('[lang="zh"]').hide();
$.cookie('lang', 'en', { expires: 7 });
langButtonListen();
} else {
console.log('Unknown error. Defaulting to English!');
$('[lang="zh"]').hide();
$.cookie('lang', 'en', { expires: 7 });
langButtonListen();
}
});
} else {
// geolocation IS NOT available
$('[lang="zh"]').hide();
$.cookie('lang', 'en', { expires: 7 });
langButtonListen());
}
}
});
You can use data attributes: the fact that "HTML5 attributes are not supported in IE6 and IE7" means that you don't get the getAttribute() method or the dataset property for retrieving/accessing them. But you can still retrieve them as explained in this post.
<div id="geoff" data-geoff="geoff">
var geoff = document.getElementById("geoff");
alert(geoff.getAttribute("data-geoff"));
Even better, you can use jQuery .data() to support previous versions of IE.
Something along these lines should work:
<div data-translate="translation_key"></div>
$("[data-translate]").each(function(){
var key = $(this).data('translate');
$(this).html(dictionary[key][current_lang] || "N/A");
});
Working example: https://jsfiddle.net/x93oLad8/4/
One of the ways around this might be to use some sort of client-side templating system for your interface. That way you don't need to unnecessarily load your HTML with a bunch of data attributes detailing the language requirements, but just describe it once in the JavaScript and use a couple of functions to assist with the translation. I've coded up quick example below to show you what I mean.
Here's the dictionary object. It contains all the translations by country code. This means you don't need separate dictionaries for each country. This is important because it means we can use this single object structure very easily in out translation function as you'll see in a moment. It also means you can add as many languages and translations as you like.
var dict = {
en: {
'Hallo': 'Hallo',
'Goodbye': 'Goodbye',
'castle': 'castle'
},
fr: {
'Hallo': 'Bonjour',
'Goodbye': 'Au revoir',
'castle': 'chateau'
},
de: {
'Hallo': 'Hallo',
'Goodbye': 'Auf Wiedersehen',
'castle': 'schloss'
}
}
This is our country code and it relates directly to the country code key in our dictionary object:
var lang = 'fr';
The first of our two functions. This takes a template and a language and performs the translation, returning whatever's left (usually some sort of HTML as in our example).
function applyTemplate(tmpl, lang) {
// find all words within {{word}} a double set of curly braces
// (this format is similar to the handlebars templating engine)
var regex = /\{\{([a-zA-Z])\w+\}\}/g
// for each found word perform the translation and
// remove the curly braces
return tmpl.replace(regex, function (word) {
return translate(dict, lang, word.replace(/[\{\}]/g, ''));
});
}
The translate function takes the dictionary, the language, and a word and returns the translated word. Note that this is much easier with one object containing all the country translations.
function translate(dict, lang, word) {
return dict[lang][word];
}
Some HTML. Here is our template (display: none) and the output element. Note the words in the curly braces are the ones to be translated.
<div class="template"><div>{{Goodbye}}, {{castle}}</div></div>
<div id="translation"></div>
Finally, putting it all together:
// grab the template
var tmpl = document.querySelector('.template').textContent;
var translation = document.querySelector('#translation');
// grab our translated html and add it to the output element
var html = applyTemplate(tmpl, lang);
translation.insertAdjacentHTML('afterbegin', html);
DEMO
Now, obviously you don't have to use this method (there are dozens of JS templating engines out there), but templating is particularly useful for sites that need to use multiple languages. Many do this on the back end but, as you can see, it can be easily done client-side too.
Hope this was useful and given you a couple of different ideas on how you might approach your solution.
<script type="text/javascript">
// Load the Google Transliteration API
google.load("elements", "1", {
packages: "transliteration"
});
var transliterationControl;
function onLoad() {
var options = {
sourceLanguage: 'en',
destinationLanguage: ['hi','or','bn','ta','te'],
transliterationEnabled: true,
shortcutKey: 'ctrl+g'
};
// Create an instance on TransliterationControl with the required
// options.
transliterationControl =
new google.elements.transliteration.TransliterationControl(options);
// Enable transliteration in the textfields with the given ids.
var ids = [ "transl1", "transl2" ];
transliterationControl.makeTransliteratable(ids);
// Add the STATE_CHANGED event handler to correcly maintain the state
// of the checkbox.
transliterationControl.addEventListener(
google.elements.transliteration.TransliterationControl.EventType.STATE_CHANGED,
transliterateStateChangeHandler);
// Add the SERVER_UNREACHABLE event handler to display an error message
// if unable to reach the server.
transliterationControl.addEventListener(
google.elements.transliteration.TransliterationControl.EventType.SERVER_UNREACHABLE,
serverUnreachableHandler);
// Add the SERVER_REACHABLE event handler to remove the error message
// once the server becomes reachable.
transliterationControl.addEventListener(
google.elements.transliteration.TransliterationControl.EventType.SERVER_REACHABLE,
serverReachableHandler);
// Set the checkbox to the correct state.
document.getElementById('checkboxId').checked =
transliterationControl.isTransliterationEnabled();
// Populate the language dropdown
var destinationLanguage =
transliterationControl.getLanguagePair().destinationLanguage;
var languageSelect = document.getElementById('languageDropDown');
var supportedDestinationLanguages =
google.elements.transliteration.getDestinationLanguages(
google.elements.transliteration.LanguageCode.ENGLISH);
for (var lang in supportedDestinationLanguages) {
var opt = document.createElement('option');
opt.text = lang;
if (lang=="TAMIL" || lang=="TELUGU" || lang=="HINDI" || lang=="ORIYA" || lang=="BENGALI"){
opt.value = supportedDestinationLanguages[lang];
if (destinationLanguage == opt.value) {
opt.selected = true;
}
try {
languageSelect.add(opt, null);
} catch (ex) {
languageSelect.add(opt);
}
}//End of if
}
}
// Handler for STATE_CHANGED event which makes sure checkbox status
// reflects the transliteration enabled or disabled status.
function transliterateStateChangeHandler(e) {
document.getElementById('checkboxId').checked = e.transliterationEnabled;
}
// Handler for checkbox's click event. Calls toggleTransliteration to toggle
// the transliteration state.
function checkboxClickHandler() {
transliterationControl.toggleTransliteration();
}
// Handler for dropdown option change event. Calls setLanguagePair to
// set the new language.
function languageChangeHandler() {
var dropdown = document.getElementById('languageDropDown');
transliterationControl.setLanguagePair(
google.elements.transliteration.LanguageCode.ENGLISH,
dropdown.options[dropdown.selectedIndex].value);
}
// SERVER_UNREACHABLE event handler which displays the error message.
function serverUnreachableHandler(e) {
document.getElementById("errorDiv").innerHTML =
"Transliteration Server unreachable";
}
// SERVER_UNREACHABLE event handler which clears the error message.
function serverReachableHandler(e) {
document.getElementById("errorDiv").innerHTML = "";
}
google.setOnLoadCallback(onLoad);

Add event to Series.Toggle in Rickshaw

I want to add functionality when clicking on items in the legend in Rickshaw. I use the standard code to add toggle functionality, which I want to extend to run my own function:
shelving = new Rickshaw.Graph.Behavior.Series.Toggle( {
graph: graph,
legend: legend
}
Is there a way add a function of my own here? I also tried looking through and editing the code in Rickshaw.Graph.Behavior.Series.Toggle.js, but I couldn't get anything to run upon clicking items in the legend. (Might it be that the imported js file is cached so that my edits don't take effect?)
As you explicitly asked about what source code to modify, I highlighted it below.
If you modify the source, upgrading to a new version becomes much harder, extending/creating own version would be the preferred way to go.
In Rickshaw.Graph.Behavior.Series.Toggle.js
'Rickshaw.Graph.Behavior.Series.Toggle.js' change the following
Rickshaw.Graph.Behavior.Series.Toggle = function(args) {
...
this._addBehavior = function() {
this.graph.series.forEach( function(s) {
s.disable = function() {
if (self.graph.series.length <= 1) {
throw('only one series left');
}
s.disabled = true;
alert('disabling ' + s.name); //HERE
self.graph.update();
};
s.enable = function() {
s.disabled = false;
alert('enabling ' + s.name); //HERE
self.graph.update();
};
} );
};
...
};

CKEditor - remove script tag with data processor

I am quite new with CKEditor (starting to use it 2 days ago) and I am still fighting with some configuration like removing the tag from editor.
So for example, if a user type in source mode the following:
<script type="text/javascript">alert('hello');</script>
I would like to remove it.
Looking the documentation, I found that this can be done using an HTML filter. I so defined it but it does not work.
var editor = ev.editor;
var dataProcessor = editor.dataProcessor;
var htmlFilter = dataProcessor && dataProcessor.htmlFilter;
htmlFilter.addRules(
{
elements :
{
script : function(element)
{
alert('Found script :' + element.name);
element.remove();
},
img : function( element )
{
alert('Found script :' + element.name);
if ( !element.attributes.alt )
element.attributes.alt = 'Cookingfactory';
}
}
});
The img part is working well but not the script one. I guess I missed something. It even does not display the alert message for script.
Any help would be more than welcome :o)
You can use this :
CKEDITOR.replace('editor1', {
on: {
pluginsLoaded: function(event) {
event.editor.dataProcessor.dataFilter.addRules({
elements: {
script: function(element) {
return false;
}
}
});
}
}
});
If you are using CKEditor 4.1 or above, you may use Advanced Content Filter to allow the content you want.
If you are using CKEditor 4.4 or above, there is an easier way. You can use Disallowed Content to filter content you don't like .
config.disallowedContent = 'script';
As I'm having CKEditor 4, I did the next
CKEDITOR.instances.editor1.config.protectedSource.push( /{.*\".*}/g );
It will ignore quotes in smarty curly brackets

CodeMirror2: How to format a pasted content?

Is it possible to format the inserted content after an event like "onPaste" in CodeMirror2? - I want to indent the content from the clipboard after pasting. I already know with JavaScript it's not possible to get access to the clipboard.
So I think there is also no possiblity to create a context menu with cut/copy/paste functions? - Could I create my own JS-clipboard or is there an existing solution?
Thanks!
leX
Took me a little while to work this out, so in case it helps anyone, here's how I'm intercepting pastes and replacing each tab with 2 spaces:
editor.on("beforeChange", (cm, change) => {
if(change.origin === "paste") {
const newText = change.text.map(line => line.replace(/\t/g, " "));
change.update(null, null, newText);
}
});
A CodeMirror has native "inputRead" event so you can do this:
editor.on('inputRead', function(cm, event) {
/* event -> object{
origin: string, can be '+input', '+move' or 'paste'
doc for origins >> http://codemirror.net/doc/manual.html#selection_origin
from: object {line, ch},
to: object {line, ch},
removed: array of removed strings
text: array of pasted strings
} */
if (event.origin == 'paste') {
console.log(event.text);
var text = event.text[0]; // pasted string
var new_text = '['+text+']'; // any operations here
cm.refresh();
// my first idea was
// note: for multiline strings may need more complex calculations
cm.replaceRange(new_text, event.from, {line: event.from.line, ch: event.from.ch + text.length});
// first solution did'nt work (before i guess to call refresh) so i tried that way, works too
/* cm.execCommand('undo');
cm.setCursor(event.from);
cm.replaceSelection(new_text); */
}
});
And there are also other events "cut", "copy" and "paste" (http://codemirror.net/doc/manual.html#events) so this will work:
editor.on('paste', function(cm, event) { ... } );
Andavtage is you can cancel event by calling event.preventDefault(); but retrieving pasted text is a problem.
Currently i am working on similar thing - hook copy/paste events and make some replacements. I found a solution to programmatically copy text to clipboard. This is clipboard.js
discussed here Does codemirror provide Cut, Copy and Paste API?. The great thing is you can trigger click event programmatically (that was a problem when i used ZeroClipboard with cross-browser flash shim) and it will work!
new Clipboard('.btn-copy', {
text: function(trigger) {
var text = editor.getValue(); // or editor.getSelection();
return text.replace(/\s+/g,' ');
}
});
editor.on('copy', function(cm, event) {
$('.btn-copy').click();
event.preventDefault();
});
The only solution I found was intercepting the even using inputRead and then manually changing the pasted contents. I made a generic funcion you could reuse:
class CodeMirrorExt {
// This function returns a callback to be used with codemirror's inputRead event.
// The intent is to intercept a pasted text, transform the pasted contents (each line) with the function
// you provide, and then have that transformed content be what ends up in the editor.
//
// Usage example that cuts each pasted line to 10 chars:
//
// this.codemirror.on("inputRead", CodeMirrorExt.replaceTextOnPasteFunc((line) => {
// return line.slice(0, 10);
// }));
static replaceTextOnPasteFunc(transformFunc) {
return ((doc, event) => {
if (event.origin !== "paste") {
return;
}
const firstText = event.text[0];
const lineNum = event.from.line;
const chNum = event.from.ch;
const newTexts = event.text.map(transformFunc);
newTexts.forEach((text, i) => {
if (i == 0) {
doc.replaceRange(text, {line: lineNum, ch: chNum}, {line: lineNum, ch: chNum + firstText.length});
}
else {
doc.replaceRange(text, {line: lineNum + i, ch: 0}, {line: lineNum + i, ch: event.text[i].length});
}
})
});
}
}
export default CodeMirrorExt;
Using js-beautify you can beautify the editors value during a paste event on the .CodeMirror like so:
$(document).on('paste', '.CodeMirror', function(e) {
var content = $(this).closest('.content');
var editor = content[0].editor;
// beautify the code
editor.setValue( js_beautify( editor.getValue(), { indent_size: 2 } ) );
});
Be aware that when inserting text into the editor, it listens to the first indentation, so if your first line is indented, all other lines will be indented accordingly.
ex:
function something() { // if the start is indented like so,
var blah = something; // next lines will follow
}

Categories

Resources