You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
202 lines
6.0 KiB
202 lines
6.0 KiB
define([
|
|
'require',
|
|
'jquery',
|
|
'base/js/events',
|
|
'base/js/namespace',
|
|
'notebook/js/textcell',
|
|
'codemirror/lib/codemirror',
|
|
'./typo/typo'
|
|
], function (
|
|
requirejs,
|
|
$,
|
|
events,
|
|
Jupyter,
|
|
textcell,
|
|
CodeMirror,
|
|
Typo
|
|
) {
|
|
'use strict';
|
|
|
|
// parameters (potentially) stored in config. This object gets updated on config load.
|
|
var params = {
|
|
enable_on_load : true,
|
|
add_toolbar_button : true,
|
|
lang_code : 'en_US',
|
|
dic_url : 'https://cdn.jsdelivr.net/codemirror.spell-checker/latest/en_US.dic',
|
|
aff_url : 'https://cdn.jsdelivr.net/codemirror.spell-checker/latest/en_US.aff',
|
|
};
|
|
|
|
// Initialize data globally to reduce memory consumption
|
|
var log_prefix = '[spellchecker]';
|
|
var dict_load_promise;
|
|
var typo_dict;
|
|
|
|
/**
|
|
* Load the dictionaries from the param-specified urls
|
|
*
|
|
* @return {Promise} - a promise which fulfils when the dictionaries have
|
|
* been ajax-loaded
|
|
*/
|
|
function load_dictionary () {
|
|
if (dict_load_promise === undefined) {
|
|
dict_load_promise = Promise.all([
|
|
params.aff_url ? $.ajax({
|
|
url: requirejs.toUrl(params.aff_url),
|
|
dataType: 'text'
|
|
}) : Promise.resolve(''),
|
|
params.dic_url ? $.ajax({
|
|
url: requirejs.toUrl(params.dic_url),
|
|
dataType: 'text'
|
|
}) : Promise.resolve('')
|
|
]).then(function (values) {
|
|
if (typo_dict === undefined) {
|
|
typo_dict = new Typo(params.lang_code, values[0], values[1], {
|
|
platform: 'any'
|
|
});
|
|
}
|
|
return typo_dict;
|
|
});
|
|
}
|
|
return dict_load_promise;
|
|
}
|
|
|
|
/**
|
|
* rx_word_char defines characters in words,
|
|
* rx_non_word_char defines the opposite.
|
|
* Defining both allows a simplified mode token function.
|
|
* The single quote can be in words, as an apostrophe for contractions like
|
|
* "isn't", so it's treated as a word character, then stripped from the
|
|
* start & finish before checking the word against the dictionary.
|
|
*/
|
|
var rx_word_char = /[^-\[\]{}():\/!;&@$£%§<>"*+=?.,~\\^|_`#±\s\t]/;
|
|
var rx_non_word_char = /[-\[\]{}():\/!;&@$£%§<>"*+=?.,~\\^|_`#±\s\t]/;
|
|
|
|
function define_mode (original_mode_spec) {
|
|
if (original_mode_spec.indexOf('spellcheck_') === 0) {
|
|
return original_mode_spec;
|
|
}
|
|
|
|
var new_mode_spec = 'spellcheck_' + original_mode_spec;
|
|
CodeMirror.defineMode(new_mode_spec, function (config) {
|
|
var spellchecker_overlay = {
|
|
name: new_mode_spec,
|
|
token: function (stream, state) {
|
|
if (stream.eatWhile(rx_word_char)) {
|
|
// strip leading and trailing single quotes
|
|
var word = stream.current().replace(/(^')|('$)/g, '');
|
|
// we don't consider a set of digits as a word to spellcheck
|
|
if (!word.match(/^\d+$/) && (typo_dict !== undefined) && !typo_dict.check(word)) {
|
|
return 'spell-error';
|
|
}
|
|
}
|
|
stream.eatWhile(rx_non_word_char);
|
|
return null;
|
|
}
|
|
};
|
|
return CodeMirror.overlayMode(
|
|
CodeMirror.getMode(config, original_mode_spec), spellchecker_overlay, true);
|
|
});
|
|
return new_mode_spec;
|
|
}
|
|
|
|
/**
|
|
* Given a codemirror mode specification string, return the corresponding
|
|
* string with spellcheck enabled/disabled by adding/removing 'spellcheck_'
|
|
* from the beginning where necessary
|
|
*
|
|
* @param {String} mode - a CodeMirror mode specification string
|
|
* @param {Boolean} spellcheck_on - whether a spellcheck mode should be returned
|
|
* @return {String} - the appropriate CodeMirror mode string
|
|
*/
|
|
function toggle_mode (mode, spellcheck_on) {
|
|
var new_mode = mode.substr(Boolean(mode.match('^spellcheck_')) ? 11 : 0);
|
|
if (spellcheck_on) {
|
|
return define_mode(new_mode);
|
|
}
|
|
else {
|
|
return new_mode;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Toggle spelling checking overlay usage for all text cells
|
|
*
|
|
* @param {Boolean} set_on - whether spellcheck mode should be toggled on.
|
|
* If undefined, it's just toggled from current state
|
|
* @return {Boolean} - whether the mode was set on
|
|
*/
|
|
function toggle_spellcheck (set_on) {
|
|
set_on = (set_on !== undefined) ? set_on : (params.enable_on_load = !params.enable_on_load);
|
|
// Change defaults for new cells:
|
|
textcell.MarkdownCell.options_default.cm_config.mode = toggle_mode(
|
|
textcell.MarkdownCell.options_default.cm_config.mode, set_on
|
|
);
|
|
// And change any existing cells:
|
|
Jupyter.notebook.get_cells().forEach(function (cell, idx, array) {
|
|
if (cell instanceof textcell.TextCell) {
|
|
var new_mode = toggle_mode(cell.code_mirror.getOption('mode'), set_on);
|
|
cell.code_mirror.setOption('mode', new_mode);
|
|
}
|
|
});
|
|
// update button class
|
|
$('#spellchecker_btn').toggleClass('active', set_on);
|
|
console.log(log_prefix, 'toggled ' + (set_on ? 'on' : 'off'));
|
|
return set_on;
|
|
}
|
|
|
|
/**
|
|
* Add a button to the jupyter toolbar for toggling spellcheck overlay
|
|
*/
|
|
function add_toolbar_buttons () {
|
|
return $(Jupyter.toolbar.add_buttons_group([
|
|
Jupyter.keyboard_manager.actions.register ({
|
|
help : 'Toggle spell checking on markdown cells',
|
|
icon : 'fa-check',
|
|
handler: function (evt) {
|
|
toggle_spellcheck();
|
|
setTimeout(function () {
|
|
evt.currentTarget.blur();
|
|
}, 100);
|
|
}
|
|
}, 'toggle-spellchecking', 'spellchecker')
|
|
])).find('.btn').attr('id', 'spellchecker_btn');
|
|
}
|
|
|
|
/**
|
|
* Add a <link> for a css file to the document head
|
|
*
|
|
* @param {String} url - the url of the css file, which will be passed
|
|
* through requirejs.toUrl, to enable relative urls
|
|
* @return {jQuery} - a jQuery object containing the link which was added
|
|
*/
|
|
function add_css (url) {
|
|
return $('<link/>').attr({
|
|
type : 'text/css',
|
|
rel : 'stylesheet',
|
|
href : requirejs.toUrl(url)
|
|
}).appendTo('head');
|
|
}
|
|
|
|
/**
|
|
* Initializes the extension
|
|
*/
|
|
function load_jupyter_extension () {
|
|
add_css('./main.css');
|
|
|
|
return Jupyter.notebook.config.loaded
|
|
.then(function () {
|
|
$.extend(true, params, Jupyter.notebook.config.data.spellchecker); // update params
|
|
if (params.add_toolbar_button) {
|
|
add_toolbar_buttons();
|
|
}
|
|
toggle_spellcheck(params.enable_on_load);
|
|
})
|
|
.then(load_dictionary);
|
|
}
|
|
|
|
return {
|
|
load_jupyter_extension : load_jupyter_extension,
|
|
load_ipython_extension : load_jupyter_extension,
|
|
};
|
|
});
|