Merge branch 'MDL-43856-master' of git://github.com/damyon/moodle

This commit is contained in:
Dan Poltawski 2014-04-07 16:58:46 +08:00
commit 4834cfdf59
34 changed files with 1377 additions and 118 deletions

View file

@ -81,6 +81,8 @@ function atto_equation_params_for_js($elementid, $options, $fpoptions) {
'elements' => get_config('atto_equation', 'librarygroup4'),
));
return array('texfilteractive' => $texfilteractive, 'contextid' => $context->id, 'library' => $library,
'texdocsurl' => get_docs_url('Using_TeX_Notation'));
return array('texfilteractive' => $texfilteractive,
'contextid' => $context->id,
'library' => $library,
'texdocsurl' => get_docs_url('Using_TeX_Notation'));
}

View file

@ -71,9 +71,9 @@ if ($ADMIN->fulltree) {
\neq
';
$setting = new admin_setting_configtextarea('atto_equation/librarygroup1',
$name,
$desc,
$default);
$name,
$desc,
$default);
$settings->add($setting);
// Group 2
@ -96,9 +96,9 @@ if ($ADMIN->fulltree) {
\Leftrightarrow
';
$setting = new admin_setting_configtextarea('atto_equation/librarygroup2',
$name,
$desc,
$default);
$name,
$desc,
$default);
$settings->add($setting);
// Group 3
@ -141,9 +141,9 @@ if ($ADMIN->fulltree) {
\Omega
';
$setting = new admin_setting_configtextarea('atto_equation/librarygroup3',
$name,
$desc,
$default);
$name,
$desc,
$default);
$settings->add($setting);
// Group 4
@ -161,8 +161,9 @@ if ($ADMIN->fulltree) {
\left| \begin{matrix} a_1 & a_2 \\ a_3 & a_4 \end{matrix} \right|
';
$setting = new admin_setting_configtextarea('atto_equation/librarygroup4',
$name,
$desc,
$default);
$name,
$desc,
$default);
$settings->add($setting);
}

View file

@ -48,6 +48,10 @@ var COMPONENTNAME = 'atto_equation',
SUBMIT: '.' + CSS.SUBMIT,
LIBRARY_BUTTON: '.' + CSS.LIBRARY + ' button'
},
DELIMITERS = {
START: '\\(',
END: '\\)'
},
TEMPLATES = {
FORM: '' +
'<form class="atto_form">' +
@ -72,7 +76,7 @@ var COMPONENTNAME = 'atto_equation',
'{{#each library}}' +
'<div id="{{elementid}}_{{../CSS.LIBRARY_GROUP_PREFIX}}{{@key}}">' +
'{{#split "\n" elements}}' +
'<button data-tex="{{this}}" title="{{this}}">$${{this}}$$</button>' +
'<button data-tex="{{this}}" title="{{this}}">{{../../DELIMITERS.START}}{{this}}{{../../DELIMITERS.END}}</button>' +
'{{/split}}' +
'</div>' +
'{{/each}}' +
@ -111,7 +115,17 @@ Y.namespace('M.atto_equation').Button = Y.Base.create('button', Y.M.editor_atto.
*/
_content: null,
/**
* The source equation we are editing in the text.
*
* @property _sourceEquation
* @type String
* @private
*/
_sourceEquation: '',
initializer: function() {
// If there is a tex filter active - enable this button.
if (this.get('texfilteractive')) {
// Add the button to the toolbar.
this.addButton({
@ -127,7 +141,14 @@ Y.namespace('M.atto_equation').Button = Y.Base.create('button', Y.M.editor_atto.
this.unHighlightButtons();
}
}, this);
// We need to convert these to a non dom node based format.
this.editor.all('tex').each(function (texNode) {
var replacement = Y.Node.create('<span>' + DELIMITERS.START + ' ' + texNode.get('text') + ' ' + DELIMITERS.END + '</span>');
texNode.replace(replacement);
});
}
},
/**
@ -160,6 +181,8 @@ Y.namespace('M.atto_equation').Button = Y.Base.create('button', Y.M.editor_atto.
tabview.render();
dialogue.show();
// Trigger any JS filters to reprocess the new nodes.
Y.fire(M.core.event.FILTER_CONTENT_UPDATED, {nodes: (new Y.NodeList(dialogue.get('boundingBox')))});
var equation = this._resolveEquation();
if (equation) {
@ -181,7 +204,8 @@ Y.namespace('M.atto_equation').Button = Y.Base.create('button', Y.M.editor_atto.
// Find the equation in the surrounding text.
var selectedNode = this.get('host').getSelectionParentNode(),
text,
equation;
equation,
patterns = [], i;
// Note this is a document fragment and YUI doesn't like them.
if (!selectedNode) {
@ -190,14 +214,27 @@ Y.namespace('M.atto_equation').Button = Y.Base.create('button', Y.M.editor_atto.
text = Y.one(selectedNode).get('text');
// We use space or not space because . does not match new lines.
pattern = /\$\$[\S\s]*\$\$/;
equation = pattern.exec(text);
if (equation && equation.length) {
equation = equation.pop();
// Replace the equation.
equation = equation.substring(2, equation.length - 2);
return equation;
// $$ blah $$.
patterns.push(/\$\$([\S\s]*)\$\$/);
// E.g. "\( blah \)".
patterns.push(/\\\(([\S\s]*)\\\)/);
// E.g. "\[ blah \]".
patterns.push(/\\\[([\S\s]*)\\\]/);
// E.g. "[tex] blah [/tex]".
patterns.push(/\[tex\]([\S\s]*)\[\/tex\]/);
for (i = 0; i < patterns.length; i++) {
pattern = patterns[i];
equation = pattern.exec(text);
if (equation && equation.length) {
// Remember the inner match so we can replace it later.
this.sourceEquation = equation = equation[1];
return equation;
}
}
this.sourceEquation = '';
return false;
},
@ -212,11 +249,10 @@ Y.namespace('M.atto_equation').Button = Y.Base.create('button', Y.M.editor_atto.
var input,
selectedNode,
text,
pattern,
equation,
value;
value,
host;
var host = this.get('host');
host = this.get('host');
e.preventDefault();
this.getDialogue({
@ -229,18 +265,16 @@ Y.namespace('M.atto_equation').Button = Y.Base.create('button', Y.M.editor_atto.
if (value !== '') {
host.setSelection(this._currentSelection);
value = '$$ ' + value.trim() + ' $$';
selectedNode = Y.one(host.getSelectionParentNode());
text = selectedNode.get('text');
pattern = /\$\$[\S\s]*\$\$/;
equation = pattern.exec(text);
if (equation && equation.length) {
if (this.sourceEquation.length) {
// Replace the equation.
equation = equation.pop();
text = text.replace(equation, '$$' + value + '$$');
selectedNode = Y.one(host.getSelectionParentNode());
text = selectedNode.get('text');
text = text.replace(this.sourceEquation, value);
selectedNode.set('text', text);
} else {
// Insert the new equation.
value = DELIMITERS.START + ' ' + value + ' ' + DELIMITERS.END;
host.insertContentAtFocusPoint(value);
}
@ -249,6 +283,29 @@ Y.namespace('M.atto_equation').Button = Y.Base.create('button', Y.M.editor_atto.
}
},
/**
* Smart throttle, only call a function every delay milli seconds,
* and always run the last call. Y.throttle does not work here,
* because it calls the function immediately, the first time, and then
* ignores repeated calls within X seconds. This does not guarantee
* that the last call will be executed (which is required here).
*
* @param {function} fn
* @param {Number} delay Delay in milliseconds
* @method _throttle
* @private
*/
_throttle: function(fn, delay) {
var timer = null;
return function () {
var context = this, args = arguments;
clearTimeout(timer);
timer = setTimeout(function () {
fn.apply(context, args);
}, delay);
};
},
/**
* Update the preview div to match the current equation.
*
@ -264,8 +321,8 @@ Y.namespace('M.atto_equation').Button = Y.Base.create('button', Y.M.editor_atto.
currentPos = textarea.get('selectionStart'),
prefix = '',
cursorLatex = '\\square ',
isChar;
isChar,
params;
if (e) {
e.preventDefault();
@ -279,25 +336,33 @@ Y.namespace('M.atto_equation').Button = Y.Base.create('button', Y.M.editor_atto.
while (equation.charAt(currentPos) === '\\' && currentPos > 0) {
currentPos -= 1;
}
isChar = /[\w\{\}]/;
isChar = /[a-zA-Z\{\}]/;
while (isChar.test(equation.charAt(currentPos)) && currentPos < equation.length) {
currentPos += 1;
}
// Save the cursor position - for insertion from the library.
this._lastCursorPos = currentPos;
equation = prefix + equation.substring(0, currentPos) + cursorLatex + equation.substring(currentPos);
var previewNode = this._content.one(SELECTORS.EQUATION_PREVIEW);
equation = DELIMITERS.START + ' ' + equation + ' ' + DELIMITERS.END;
// Make an ajax request to the filter.
url = M.cfg.wwwroot + '/lib/editor/atto/plugins/equation/ajax.php';
params = {
sesskey: M.cfg.sesskey,
contextid: this.get('contextid'),
action: 'filtertext',
text: '$$ ' + equation + ' $$'
text: equation
};
preview = Y.io(url, { sync: true,
data: params });
preview = Y.io(url, {
sync: true,
data: params
});
if (preview.status === 200) {
this._content.one(SELECTORS.EQUATION_PREVIEW).setHTML(preview.responseText);
previewNode.setHTML(preview.responseText);
Y.fire(M.core.event.FILTER_CONTENT_UPDATED, {nodes: (new Y.NodeList(previewNode))});
}
},
@ -322,9 +387,9 @@ Y.namespace('M.atto_equation').Button = Y.Base.create('button', Y.M.editor_atto.
}));
this._content.one(SELECTORS.SUBMIT).on('click', this._setEquation, this);
this._content.one(SELECTORS.EQUATION_TEXT).on('valuechange', this._updatePreview, this);
this._content.one(SELECTORS.EQUATION_TEXT).on('mouseup', this._updatePreview, this);
this._content.one(SELECTORS.EQUATION_TEXT).on('keyup', this._updatePreview, this);
this._content.one(SELECTORS.EQUATION_TEXT).on('valuechange', this._throttle(this._updatePreview, 500), this);
this._content.one(SELECTORS.EQUATION_TEXT).on('mouseup', this._throttle(this._updatePreview, 500), this);
this._content.one(SELECTORS.EQUATION_TEXT).on('keyup', this._throttle(this._updatePreview, 500), this);
this._content.delegate('click', this._selectLibraryItem, SELECTORS.LIBRARY_BUTTON, this);
return this._content;
@ -392,7 +457,7 @@ Y.namespace('M.atto_equation').Button = Y.Base.create('button', Y.M.editor_atto.
out = '';
parts = str.trim().split(delimiter);
while (parts.length > 0) {
current = parts.shift();
current = parts.shift().trim();
out += options.fn(current);
}
@ -402,7 +467,8 @@ Y.namespace('M.atto_equation').Button = Y.Base.create('button', Y.M.editor_atto.
elementid: this.get('host').get('elementid'),
component: COMPONENTNAME,
library: library,
CSS: CSS
CSS: CSS,
DELIMITERS: DELIMITERS
});
var url = M.cfg.wwwroot + '/lib/editor/atto/plugins/equation/ajax.php';
@ -435,6 +501,7 @@ Y.namespace('M.atto_equation').Button = Y.Base.create('button', Y.M.editor_atto.
texfilteractive: {
value: false
},
/**
* The contextid to use when generating this preview.
*
@ -464,8 +531,9 @@ Y.namespace('M.atto_equation').Button = Y.Base.create('button', Y.M.editor_atto.
texdocsurl: {
value: null
}
}
});
}, '@VERSION@', {"requires": ["moodle-editor_atto-plugin", "io", "event-valuechange", "tabview"]});
}, '@VERSION@', {"requires": ["moodle-editor_atto-plugin", "moodle-core-event", "io", "event-valuechange", "tabview"]});

View file

@ -48,6 +48,10 @@ var COMPONENTNAME = 'atto_equation',
SUBMIT: '.' + CSS.SUBMIT,
LIBRARY_BUTTON: '.' + CSS.LIBRARY + ' button'
},
DELIMITERS = {
START: '\\(',
END: '\\)'
},
TEMPLATES = {
FORM: '' +
'<form class="atto_form">' +
@ -72,7 +76,7 @@ var COMPONENTNAME = 'atto_equation',
'{{#each library}}' +
'<div id="{{elementid}}_{{../CSS.LIBRARY_GROUP_PREFIX}}{{@key}}">' +
'{{#split "\n" elements}}' +
'<button data-tex="{{this}}" title="{{this}}">$${{this}}$$</button>' +
'<button data-tex="{{this}}" title="{{this}}">{{../../DELIMITERS.START}}{{this}}{{../../DELIMITERS.END}}</button>' +
'{{/split}}' +
'</div>' +
'{{/each}}' +
@ -111,7 +115,17 @@ Y.namespace('M.atto_equation').Button = Y.Base.create('button', Y.M.editor_atto.
*/
_content: null,
/**
* The source equation we are editing in the text.
*
* @property _sourceEquation
* @type String
* @private
*/
_sourceEquation: '',
initializer: function() {
// If there is a tex filter active - enable this button.
if (this.get('texfilteractive')) {
// Add the button to the toolbar.
this.addButton({
@ -127,7 +141,14 @@ Y.namespace('M.atto_equation').Button = Y.Base.create('button', Y.M.editor_atto.
this.unHighlightButtons();
}
}, this);
// We need to convert these to a non dom node based format.
this.editor.all('tex').each(function (texNode) {
var replacement = Y.Node.create('<span>' + DELIMITERS.START + ' ' + texNode.get('text') + ' ' + DELIMITERS.END + '</span>');
texNode.replace(replacement);
});
}
},
/**
@ -160,6 +181,8 @@ Y.namespace('M.atto_equation').Button = Y.Base.create('button', Y.M.editor_atto.
tabview.render();
dialogue.show();
// Trigger any JS filters to reprocess the new nodes.
Y.fire(M.core.event.FILTER_CONTENT_UPDATED, {nodes: (new Y.NodeList(dialogue.get('boundingBox')))});
var equation = this._resolveEquation();
if (equation) {
@ -181,7 +204,8 @@ Y.namespace('M.atto_equation').Button = Y.Base.create('button', Y.M.editor_atto.
// Find the equation in the surrounding text.
var selectedNode = this.get('host').getSelectionParentNode(),
text,
equation;
equation,
patterns = [], i;
// Note this is a document fragment and YUI doesn't like them.
if (!selectedNode) {
@ -190,14 +214,27 @@ Y.namespace('M.atto_equation').Button = Y.Base.create('button', Y.M.editor_atto.
text = Y.one(selectedNode).get('text');
// We use space or not space because . does not match new lines.
pattern = /\$\$[\S\s]*\$\$/;
equation = pattern.exec(text);
if (equation && equation.length) {
equation = equation.pop();
// Replace the equation.
equation = equation.substring(2, equation.length - 2);
return equation;
// $$ blah $$.
patterns.push(/\$\$([\S\s]*)\$\$/);
// E.g. "\( blah \)".
patterns.push(/\\\(([\S\s]*)\\\)/);
// E.g. "\[ blah \]".
patterns.push(/\\\[([\S\s]*)\\\]/);
// E.g. "[tex] blah [/tex]".
patterns.push(/\[tex\]([\S\s]*)\[\/tex\]/);
for (i = 0; i < patterns.length; i++) {
pattern = patterns[i];
equation = pattern.exec(text);
if (equation && equation.length) {
// Remember the inner match so we can replace it later.
this.sourceEquation = equation = equation[1];
return equation;
}
}
this.sourceEquation = '';
return false;
},
@ -212,11 +249,10 @@ Y.namespace('M.atto_equation').Button = Y.Base.create('button', Y.M.editor_atto.
var input,
selectedNode,
text,
pattern,
equation,
value;
value,
host;
var host = this.get('host');
host = this.get('host');
e.preventDefault();
this.getDialogue({
@ -229,18 +265,16 @@ Y.namespace('M.atto_equation').Button = Y.Base.create('button', Y.M.editor_atto.
if (value !== '') {
host.setSelection(this._currentSelection);
value = '$$ ' + value.trim() + ' $$';
selectedNode = Y.one(host.getSelectionParentNode());
text = selectedNode.get('text');
pattern = /\$\$[\S\s]*\$\$/;
equation = pattern.exec(text);
if (equation && equation.length) {
if (this.sourceEquation.length) {
// Replace the equation.
equation = equation.pop();
text = text.replace(equation, '$$' + value + '$$');
selectedNode = Y.one(host.getSelectionParentNode());
text = selectedNode.get('text');
text = text.replace(this.sourceEquation, value);
selectedNode.set('text', text);
} else {
// Insert the new equation.
value = DELIMITERS.START + ' ' + value + ' ' + DELIMITERS.END;
host.insertContentAtFocusPoint(value);
}
@ -249,6 +283,29 @@ Y.namespace('M.atto_equation').Button = Y.Base.create('button', Y.M.editor_atto.
}
},
/**
* Smart throttle, only call a function every delay milli seconds,
* and always run the last call. Y.throttle does not work here,
* because it calls the function immediately, the first time, and then
* ignores repeated calls within X seconds. This does not guarantee
* that the last call will be executed (which is required here).
*
* @param {function} fn
* @param {Number} delay Delay in milliseconds
* @method _throttle
* @private
*/
_throttle: function(fn, delay) {
var timer = null;
return function () {
var context = this, args = arguments;
clearTimeout(timer);
timer = setTimeout(function () {
fn.apply(context, args);
}, delay);
};
},
/**
* Update the preview div to match the current equation.
*
@ -264,8 +321,8 @@ Y.namespace('M.atto_equation').Button = Y.Base.create('button', Y.M.editor_atto.
currentPos = textarea.get('selectionStart'),
prefix = '',
cursorLatex = '\\square ',
isChar;
isChar,
params;
if (e) {
e.preventDefault();
@ -279,25 +336,33 @@ Y.namespace('M.atto_equation').Button = Y.Base.create('button', Y.M.editor_atto.
while (equation.charAt(currentPos) === '\\' && currentPos > 0) {
currentPos -= 1;
}
isChar = /[\w\{\}]/;
isChar = /[a-zA-Z\{\}]/;
while (isChar.test(equation.charAt(currentPos)) && currentPos < equation.length) {
currentPos += 1;
}
// Save the cursor position - for insertion from the library.
this._lastCursorPos = currentPos;
equation = prefix + equation.substring(0, currentPos) + cursorLatex + equation.substring(currentPos);
var previewNode = this._content.one(SELECTORS.EQUATION_PREVIEW);
equation = DELIMITERS.START + ' ' + equation + ' ' + DELIMITERS.END;
// Make an ajax request to the filter.
url = M.cfg.wwwroot + '/lib/editor/atto/plugins/equation/ajax.php';
params = {
sesskey: M.cfg.sesskey,
contextid: this.get('contextid'),
action: 'filtertext',
text: '$$ ' + equation + ' $$'
text: equation
};
preview = Y.io(url, { sync: true,
data: params });
preview = Y.io(url, {
sync: true,
data: params
});
if (preview.status === 200) {
this._content.one(SELECTORS.EQUATION_PREVIEW).setHTML(preview.responseText);
previewNode.setHTML(preview.responseText);
Y.fire(M.core.event.FILTER_CONTENT_UPDATED, {nodes: (new Y.NodeList(previewNode))});
}
},
@ -322,9 +387,9 @@ Y.namespace('M.atto_equation').Button = Y.Base.create('button', Y.M.editor_atto.
}));
this._content.one(SELECTORS.SUBMIT).on('click', this._setEquation, this);
this._content.one(SELECTORS.EQUATION_TEXT).on('valuechange', this._updatePreview, this);
this._content.one(SELECTORS.EQUATION_TEXT).on('mouseup', this._updatePreview, this);
this._content.one(SELECTORS.EQUATION_TEXT).on('keyup', this._updatePreview, this);
this._content.one(SELECTORS.EQUATION_TEXT).on('valuechange', this._throttle(this._updatePreview, 500), this);
this._content.one(SELECTORS.EQUATION_TEXT).on('mouseup', this._throttle(this._updatePreview, 500), this);
this._content.one(SELECTORS.EQUATION_TEXT).on('keyup', this._throttle(this._updatePreview, 500), this);
this._content.delegate('click', this._selectLibraryItem, SELECTORS.LIBRARY_BUTTON, this);
return this._content;
@ -391,7 +456,7 @@ Y.namespace('M.atto_equation').Button = Y.Base.create('button', Y.M.editor_atto.
out = '';
parts = str.trim().split(delimiter);
while (parts.length > 0) {
current = parts.shift();
current = parts.shift().trim();
out += options.fn(current);
}
@ -401,7 +466,8 @@ Y.namespace('M.atto_equation').Button = Y.Base.create('button', Y.M.editor_atto.
elementid: this.get('host').get('elementid'),
component: COMPONENTNAME,
library: library,
CSS: CSS
CSS: CSS,
DELIMITERS: DELIMITERS
});
var url = M.cfg.wwwroot + '/lib/editor/atto/plugins/equation/ajax.php';
@ -434,6 +500,7 @@ Y.namespace('M.atto_equation').Button = Y.Base.create('button', Y.M.editor_atto.
texfilteractive: {
value: false
},
/**
* The contextid to use when generating this preview.
*
@ -463,8 +530,9 @@ Y.namespace('M.atto_equation').Button = Y.Base.create('button', Y.M.editor_atto.
texdocsurl: {
value: null
}
}
});
}, '@VERSION@', {"requires": ["moodle-editor_atto-plugin", "io", "event-valuechange", "tabview"]});
}, '@VERSION@', {"requires": ["moodle-editor_atto-plugin", "moodle-core-event", "io", "event-valuechange", "tabview"]});

View file

@ -46,6 +46,10 @@ var COMPONENTNAME = 'atto_equation',
SUBMIT: '.' + CSS.SUBMIT,
LIBRARY_BUTTON: '.' + CSS.LIBRARY + ' button'
},
DELIMITERS = {
START: '\\(',
END: '\\)'
},
TEMPLATES = {
FORM: '' +
'<form class="atto_form">' +
@ -70,7 +74,7 @@ var COMPONENTNAME = 'atto_equation',
'{{#each library}}' +
'<div id="{{elementid}}_{{../CSS.LIBRARY_GROUP_PREFIX}}{{@key}}">' +
'{{#split "\n" elements}}' +
'<button data-tex="{{this}}" title="{{this}}">$${{this}}$$</button>' +
'<button data-tex="{{this}}" title="{{this}}">{{../../DELIMITERS.START}}{{this}}{{../../DELIMITERS.END}}</button>' +
'{{/split}}' +
'</div>' +
'{{/each}}' +
@ -109,7 +113,17 @@ Y.namespace('M.atto_equation').Button = Y.Base.create('button', Y.M.editor_atto.
*/
_content: null,
/**
* The source equation we are editing in the text.
*
* @property _sourceEquation
* @type String
* @private
*/
_sourceEquation: '',
initializer: function() {
// If there is a tex filter active - enable this button.
if (this.get('texfilteractive')) {
// Add the button to the toolbar.
this.addButton({
@ -125,7 +139,14 @@ Y.namespace('M.atto_equation').Button = Y.Base.create('button', Y.M.editor_atto.
this.unHighlightButtons();
}
}, this);
// We need to convert these to a non dom node based format.
this.editor.all('tex').each(function (texNode) {
var replacement = Y.Node.create('<span>' + DELIMITERS.START + ' ' + texNode.get('text') + ' ' + DELIMITERS.END + '</span>');
texNode.replace(replacement);
});
}
},
/**
@ -158,6 +179,8 @@ Y.namespace('M.atto_equation').Button = Y.Base.create('button', Y.M.editor_atto.
tabview.render();
dialogue.show();
// Trigger any JS filters to reprocess the new nodes.
Y.fire(M.core.event.FILTER_CONTENT_UPDATED, {nodes: (new Y.NodeList(dialogue.get('boundingBox')))});
var equation = this._resolveEquation();
if (equation) {
@ -179,7 +202,8 @@ Y.namespace('M.atto_equation').Button = Y.Base.create('button', Y.M.editor_atto.
// Find the equation in the surrounding text.
var selectedNode = this.get('host').getSelectionParentNode(),
text,
equation;
equation,
patterns = [], i;
// Note this is a document fragment and YUI doesn't like them.
if (!selectedNode) {
@ -188,14 +212,27 @@ Y.namespace('M.atto_equation').Button = Y.Base.create('button', Y.M.editor_atto.
text = Y.one(selectedNode).get('text');
// We use space or not space because . does not match new lines.
pattern = /\$\$[\S\s]*\$\$/;
equation = pattern.exec(text);
if (equation && equation.length) {
equation = equation.pop();
// Replace the equation.
equation = equation.substring(2, equation.length - 2);
return equation;
// $$ blah $$.
patterns.push(/\$\$([\S\s]*)\$\$/);
// E.g. "\( blah \)".
patterns.push(/\\\(([\S\s]*)\\\)/);
// E.g. "\[ blah \]".
patterns.push(/\\\[([\S\s]*)\\\]/);
// E.g. "[tex] blah [/tex]".
patterns.push(/\[tex\]([\S\s]*)\[\/tex\]/);
for (i = 0; i < patterns.length; i++) {
pattern = patterns[i];
equation = pattern.exec(text);
if (equation && equation.length) {
// Remember the inner match so we can replace it later.
this.sourceEquation = equation = equation[1];
return equation;
}
}
this.sourceEquation = '';
return false;
},
@ -210,11 +247,10 @@ Y.namespace('M.atto_equation').Button = Y.Base.create('button', Y.M.editor_atto.
var input,
selectedNode,
text,
pattern,
equation,
value;
value,
host;
var host = this.get('host');
host = this.get('host');
e.preventDefault();
this.getDialogue({
@ -227,18 +263,16 @@ Y.namespace('M.atto_equation').Button = Y.Base.create('button', Y.M.editor_atto.
if (value !== '') {
host.setSelection(this._currentSelection);
value = '$$ ' + value.trim() + ' $$';
selectedNode = Y.one(host.getSelectionParentNode());
text = selectedNode.get('text');
pattern = /\$\$[\S\s]*\$\$/;
equation = pattern.exec(text);
if (equation && equation.length) {
if (this.sourceEquation.length) {
// Replace the equation.
equation = equation.pop();
text = text.replace(equation, '$$' + value + '$$');
selectedNode = Y.one(host.getSelectionParentNode());
text = selectedNode.get('text');
text = text.replace(this.sourceEquation, value);
selectedNode.set('text', text);
} else {
// Insert the new equation.
value = DELIMITERS.START + ' ' + value + ' ' + DELIMITERS.END;
host.insertContentAtFocusPoint(value);
}
@ -247,6 +281,29 @@ Y.namespace('M.atto_equation').Button = Y.Base.create('button', Y.M.editor_atto.
}
},
/**
* Smart throttle, only call a function every delay milli seconds,
* and always run the last call. Y.throttle does not work here,
* because it calls the function immediately, the first time, and then
* ignores repeated calls within X seconds. This does not guarantee
* that the last call will be executed (which is required here).
*
* @param {function} fn
* @param {Number} delay Delay in milliseconds
* @method _throttle
* @private
*/
_throttle: function(fn, delay) {
var timer = null;
return function () {
var context = this, args = arguments;
clearTimeout(timer);
timer = setTimeout(function () {
fn.apply(context, args);
}, delay);
};
},
/**
* Update the preview div to match the current equation.
*
@ -262,8 +319,8 @@ Y.namespace('M.atto_equation').Button = Y.Base.create('button', Y.M.editor_atto.
currentPos = textarea.get('selectionStart'),
prefix = '',
cursorLatex = '\\square ',
isChar;
isChar,
params;
if (e) {
e.preventDefault();
@ -277,25 +334,33 @@ Y.namespace('M.atto_equation').Button = Y.Base.create('button', Y.M.editor_atto.
while (equation.charAt(currentPos) === '\\' && currentPos > 0) {
currentPos -= 1;
}
isChar = /[\w\{\}]/;
isChar = /[a-zA-Z\{\}]/;
while (isChar.test(equation.charAt(currentPos)) && currentPos < equation.length) {
currentPos += 1;
}
// Save the cursor position - for insertion from the library.
this._lastCursorPos = currentPos;
equation = prefix + equation.substring(0, currentPos) + cursorLatex + equation.substring(currentPos);
var previewNode = this._content.one(SELECTORS.EQUATION_PREVIEW);
equation = DELIMITERS.START + ' ' + equation + ' ' + DELIMITERS.END;
// Make an ajax request to the filter.
url = M.cfg.wwwroot + '/lib/editor/atto/plugins/equation/ajax.php';
params = {
sesskey: M.cfg.sesskey,
contextid: this.get('contextid'),
action: 'filtertext',
text: '$$ ' + equation + ' $$'
text: equation
};
preview = Y.io(url, { sync: true,
data: params });
preview = Y.io(url, {
sync: true,
data: params
});
if (preview.status === 200) {
this._content.one(SELECTORS.EQUATION_PREVIEW).setHTML(preview.responseText);
previewNode.setHTML(preview.responseText);
Y.fire(M.core.event.FILTER_CONTENT_UPDATED, {nodes: (new Y.NodeList(previewNode))});
}
},
@ -320,9 +385,9 @@ Y.namespace('M.atto_equation').Button = Y.Base.create('button', Y.M.editor_atto.
}));
this._content.one(SELECTORS.SUBMIT).on('click', this._setEquation, this);
this._content.one(SELECTORS.EQUATION_TEXT).on('valuechange', this._updatePreview, this);
this._content.one(SELECTORS.EQUATION_TEXT).on('mouseup', this._updatePreview, this);
this._content.one(SELECTORS.EQUATION_TEXT).on('keyup', this._updatePreview, this);
this._content.one(SELECTORS.EQUATION_TEXT).on('valuechange', this._throttle(this._updatePreview, 500), this);
this._content.one(SELECTORS.EQUATION_TEXT).on('mouseup', this._throttle(this._updatePreview, 500), this);
this._content.one(SELECTORS.EQUATION_TEXT).on('keyup', this._throttle(this._updatePreview, 500), this);
this._content.delegate('click', this._selectLibraryItem, SELECTORS.LIBRARY_BUTTON, this);
return this._content;
@ -390,7 +455,7 @@ Y.namespace('M.atto_equation').Button = Y.Base.create('button', Y.M.editor_atto.
out = '';
parts = str.trim().split(delimiter);
while (parts.length > 0) {
current = parts.shift();
current = parts.shift().trim();
out += options.fn(current);
}
@ -400,7 +465,8 @@ Y.namespace('M.atto_equation').Button = Y.Base.create('button', Y.M.editor_atto.
elementid: this.get('host').get('elementid'),
component: COMPONENTNAME,
library: library,
CSS: CSS
CSS: CSS,
DELIMITERS: DELIMITERS
});
var url = M.cfg.wwwroot + '/lib/editor/atto/plugins/equation/ajax.php';
@ -433,6 +499,7 @@ Y.namespace('M.atto_equation').Button = Y.Base.create('button', Y.M.editor_atto.
texfilteractive: {
value: false
},
/**
* The contextid to use when generating this preview.
*
@ -462,5 +529,6 @@ Y.namespace('M.atto_equation').Button = Y.Base.create('button', Y.M.editor_atto.
texdocsurl: {
value: null
}
}
});

View file

@ -2,6 +2,7 @@
"moodle-atto_equation-button": {
"requires": [
"moodle-editor_atto-plugin",
"moodle-core-event",
"io",
"event-valuechange",
"tabview"