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

@ -1012,7 +1012,7 @@ class core_plugin_manager {
'filter' => array(
'activitynames', 'algebra', 'censor', 'emailprotect',
'emoticon', 'mediaplugin', 'multilang', 'tex', 'tidy',
'emoticon', 'mathjaxloader', 'mediaplugin', 'multilang', 'tex', 'tidy',
'urltolink', 'data', 'glossary'
),

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"

View file

@ -0,0 +1,72 @@
YUI.add('moodle-core-event', function (Y, NAME) {
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* @module moodle-core-event
*/
var LOGNAME = 'moodle-core-event';
/**
* List of published global JS events in Moodle. This is a collection
* of global events that can be subscribed to, or fired from any plugin.
*
* @namespace M.core
* @class event
*/
M.core = M.core || {};
M.core.event = {
/**
* This event is triggered when a page has added dynamic nodes to a page
* that should be processed by the filter system. An example is loading
* user text that could have equations in it. MathJax can typeset the equations
* but only if it is notified that there are new nodes in the page that need processing.
* To trigger this event use M.core.Event.fire(M.core.Event.FILTER_CONTENT_UPDATED, {nodes: list});
*
* @event "filter-content-updated"
* @param nodes {Y.NodeList} List of nodes added to the DOM.
*/
FILTER_CONTENT_UPDATED: "filter-content-updated"
};
var eventDefaultConfig = {
emitFacade: true,
defaultFn: function(e) {
Y.log('Event fired: ' + e.type, 'debug', LOGNAME);
},
preventedFn: function(e) {
Y.log('Event prevented: ' + e.type, 'debug', LOGNAME);
},
stoppedFn: function(e) {
Y.log('Event stopped: ' + e.type, 'debug', LOGNAME);
}
};
// Publish all the events with a standard config.
var key;
for (key in M.core.event) {
if (M.core.event.hasOwnProperty(key)) {
Y.publish(M.core.event[key], eventDefaultConfig);
}
}
// Publish events with a custom config here.
}, '@VERSION@', {"requires": ["event-custom"]});

View file

@ -0,0 +1 @@
YUI.add("moodle-core-event",function(e,t){var n="moodle-core-event";M.core=M.core||{},M.core.event={FILTER_CONTENT_UPDATED:"filter-content-updated"};var r={emitFacade:!0,defaultFn:function(e){},preventedFn:function(e){},stoppedFn:function(e){}},i;for(i in M.core.event)M.core.event.hasOwnProperty(i)&&e.publish(M.core.event[i],r)},"@VERSION@",{requires:["event-custom"]});

View file

@ -0,0 +1,69 @@
YUI.add('moodle-core-event', function (Y, NAME) {
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* @module moodle-core-event
*/
var LOGNAME = 'moodle-core-event';
/**
* List of published global JS events in Moodle. This is a collection
* of global events that can be subscribed to, or fired from any plugin.
*
* @namespace M.core
* @class event
*/
M.core = M.core || {};
M.core.event = {
/**
* This event is triggered when a page has added dynamic nodes to a page
* that should be processed by the filter system. An example is loading
* user text that could have equations in it. MathJax can typeset the equations
* but only if it is notified that there are new nodes in the page that need processing.
* To trigger this event use M.core.Event.fire(M.core.Event.FILTER_CONTENT_UPDATED, {nodes: list});
*
* @event "filter-content-updated"
* @param nodes {Y.NodeList} List of nodes added to the DOM.
*/
FILTER_CONTENT_UPDATED: "filter-content-updated"
};
var eventDefaultConfig = {
emitFacade: true,
defaultFn: function(e) {
},
preventedFn: function(e) {
},
stoppedFn: function(e) {
}
};
// Publish all the events with a standard config.
var key;
for (key in M.core.event) {
if (M.core.event.hasOwnProperty(key)) {
Y.publish(M.core.event[key], eventDefaultConfig);
}
}
// Publish events with a custom config here.
}, '@VERSION@', {"requires": ["event-custom"]});

View file

@ -0,0 +1,10 @@
{
"name": "moodle-core-event",
"builds": {
"moodle-core-event": {
"jsfiles": [
"event.js"
]
}
}
}

67
lib/yui/src/event/js/event.js vendored Normal file
View file

@ -0,0 +1,67 @@
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* @module moodle-core-event
*/
var LOGNAME = 'moodle-core-event';
/**
* List of published global JS events in Moodle. This is a collection
* of global events that can be subscribed to, or fired from any plugin.
*
* @namespace M.core
* @class event
*/
M.core = M.core || {};
M.core.event = {
/**
* This event is triggered when a page has added dynamic nodes to a page
* that should be processed by the filter system. An example is loading
* user text that could have equations in it. MathJax can typeset the equations
* but only if it is notified that there are new nodes in the page that need processing.
* To trigger this event use M.core.Event.fire(M.core.Event.FILTER_CONTENT_UPDATED, {nodes: list});
*
* @event "filter-content-updated"
* @param nodes {Y.NodeList} List of nodes added to the DOM.
*/
FILTER_CONTENT_UPDATED: "filter-content-updated"
};
var eventDefaultConfig = {
emitFacade: true,
defaultFn: function(e) {
Y.log('Event fired: ' + e.type, 'debug', LOGNAME);
},
preventedFn: function(e) {
Y.log('Event prevented: ' + e.type, 'debug', LOGNAME);
},
stoppedFn: function(e) {
Y.log('Event stopped: ' + e.type, 'debug', LOGNAME);
}
};
// Publish all the events with a standard config.
var key;
for (key in M.core.event) {
if (M.core.event.hasOwnProperty(key)) {
Y.publish(M.core.event[key], eventDefaultConfig);
}
}
// Publish events with a custom config here.

View file

@ -0,0 +1,7 @@
{
"moodle-core-event": {
"requires": [
"event-custom"
]
}
}