MDL-45207 atto_equation: Handle multiple equations on the same line

This commit is contained in:
Andrew Nicols 2014-04-23 11:22:44 +08:00
parent 98bb800209
commit ea382deaaf
5 changed files with 318 additions and 92 deletions

View File

@ -128,10 +128,10 @@ Y.namespace('M.atto_equation').Button = Y.Base.create('button', Y.M.editor_atto.
* The source equation we are editing in the text.
*
* @property _sourceEquation
* @type String
* @type Object
* @private
*/
_sourceEquation: '',
_sourceEquation: null,
/**
* A reference to the tab focus set on each group.
@ -144,6 +144,25 @@ Y.namespace('M.atto_equation').Button = Y.Base.create('button', Y.M.editor_atto.
*/
_groupFocus: null,
/**
* Regular Expression patterns used to pick out the equations in a String.
*
* @property _equationPatterns
* @type Array
* @private
*/
_equationPatterns: [
// We use space or not space because . does not match new lines.
// $$ blah $$.
/\$\$([\S\s]+?)\$\$/,
// E.g. "\( blah \)".
/\\\(([\S\s]+?)\\\)/,
// E.g. "\[ blah \]".
/\\\[([\S\s]+?)\\\]/,
// E.g. "[tex] blah [/tex]".
/\[tex\]([\S\s]+?)\[\/tex\]/
],
initializer: function() {
this._groupFocus = {};
@ -225,39 +244,87 @@ 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(),
selection = this.get('host').getSelection(),
text,
equation,
patterns = [], i;
returnValue = false;
this.sourceEquation = null;
// Note this is a document fragment and YUI doesn't like them.
if (!selectedNode) {
return false;
}
text = Y.one(selectedNode).get('text');
// We use space or not space because . does not match new lines.
// $$ 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;
}
// We don't yet have a cursor selection somehow so we can't possible be resolving an equation that has selection.
if (!selection || selection.length === 0) {
return false;
}
selection = selection[0];
this.sourceEquation = '';
return false;
text = Y.one(selectedNode).get('text');
// For each of these patterns we have a RegExp which captures the inner component of the equation but also includes the delimiters.
// We first run the RegExp adding the global flag ("g"). This ignores the capture, instead matching the entire
// equation including delimiters and returning one entry per match of the whole equation.
// We have to deal with multiple occurences of the same equation in a String so must be able to loop on the
// match results.
Y.Array.find(this._equationPatterns, function(pattern) {
// For each pattern in turn, find all whole matches (including the delimiters).
var patternMatches = text.match(new RegExp(pattern.source, "g"));
if (patternMatches && patternMatches.length) {
// This pattern matches at least once. See if this pattern matches our current position.
// Note: We return here to break the Y.Array.find loop - any truthy return will stop any subsequent
// searches which is the required behaviour of this function.
return Y.Array.find(patternMatches, function(match) {
// Check each occurrence of this match.
var startIndex = 0;
while(text.indexOf(match, startIndex) !== -1) {
// Determine whether the cursor is in the current occurrence of this string.
// Note: We do not support a selection exceeding the bounds of an equation.
var startOuter = text.indexOf(match, startIndex),
endOuter = startOuter + match.length,
startMatch = (selection.startOffset >= startOuter && selection.startOffset < endOuter),
endMatch = (selection.endOffset <= endOuter && selection.endOffset > startOuter);
if (startMatch && endMatch) {
// This match is in our current position - fetch the innerMatch data.
var innerMatch = match.match(pattern);
if (innerMatch && innerMatch.length) {
// We need the start and end of the inner match for later.
var startInner = text.indexOf(innerMatch[1], startOuter),
endInner = startInner + innerMatch[1].length;
// We'll be returning the inner match for use in the editor itself.
returnValue = innerMatch[1];
// Save all data for later.
this.sourceEquation = {
// Outer match data.
startOuterPosition: startOuter,
endOuterPosition: endOuter,
outerMatch: match,
// Inner match data.
startInnerPosition: startInner,
endInnerPosition: endInner,
innerMatch: innerMatch
};
// This breaks out of both Y.Array.find functions.
return true;
}
}
// Update the startIndex to match the end of the current match so that we can continue hunting
// for further matches.
startIndex = endOuter;
}
}, this);
}
}, this);
return returnValue;
},
/**
@ -287,13 +354,15 @@ Y.namespace('M.atto_equation').Button = Y.Base.create('button', Y.M.editor_atto.
if (value !== '') {
host.setSelection(this._currentSelection);
if (this.sourceEquation.length) {
if (this.sourceEquation) {
// Replace the equation.
selectedNode = Y.one(host.getSelectionParentNode());
text = selectedNode.get('text');
newText = text.slice(0, this.sourceEquation.startInnerPosition) +
value +
text.slice(this.sourceEquation.endInnerPosition);
text = text.replace(this.sourceEquation, value);
selectedNode.set('text', text);
selectedNode.set('text', newText);
} else {
// Insert the new equation.
value = DELIMITERS.START + ' ' + value + ' ' + DELIMITERS.END;
@ -627,4 +696,13 @@ Y.namespace('M.atto_equation').Button = Y.Base.create('button', Y.M.editor_atto.
});
}, '@VERSION@', {"requires": ["moodle-editor_atto-plugin", "moodle-core-event", "io", "event-valuechange", "tabview"]});
}, '@VERSION@', {
"requires": [
"moodle-editor_atto-plugin",
"moodle-core-event",
"io",
"event-valuechange",
"tabview",
"array-extras"
]
});

View File

@ -128,10 +128,10 @@ Y.namespace('M.atto_equation').Button = Y.Base.create('button', Y.M.editor_atto.
* The source equation we are editing in the text.
*
* @property _sourceEquation
* @type String
* @type Object
* @private
*/
_sourceEquation: '',
_sourceEquation: null,
/**
* A reference to the tab focus set on each group.
@ -144,6 +144,25 @@ Y.namespace('M.atto_equation').Button = Y.Base.create('button', Y.M.editor_atto.
*/
_groupFocus: null,
/**
* Regular Expression patterns used to pick out the equations in a String.
*
* @property _equationPatterns
* @type Array
* @private
*/
_equationPatterns: [
// We use space or not space because . does not match new lines.
// $$ blah $$.
/\$\$([\S\s]+?)\$\$/,
// E.g. "\( blah \)".
/\\\(([\S\s]+?)\\\)/,
// E.g. "\[ blah \]".
/\\\[([\S\s]+?)\\\]/,
// E.g. "[tex] blah [/tex]".
/\[tex\]([\S\s]+?)\[\/tex\]/
],
initializer: function() {
this._groupFocus = {};
@ -225,39 +244,87 @@ 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(),
selection = this.get('host').getSelection(),
text,
equation,
patterns = [], i;
returnValue = false;
this.sourceEquation = null;
// Note this is a document fragment and YUI doesn't like them.
if (!selectedNode) {
return false;
}
text = Y.one(selectedNode).get('text');
// We use space or not space because . does not match new lines.
// $$ 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;
}
// We don't yet have a cursor selection somehow so we can't possible be resolving an equation that has selection.
if (!selection || selection.length === 0) {
return false;
}
selection = selection[0];
this.sourceEquation = '';
return false;
text = Y.one(selectedNode).get('text');
// For each of these patterns we have a RegExp which captures the inner component of the equation but also includes the delimiters.
// We first run the RegExp adding the global flag ("g"). This ignores the capture, instead matching the entire
// equation including delimiters and returning one entry per match of the whole equation.
// We have to deal with multiple occurences of the same equation in a String so must be able to loop on the
// match results.
Y.Array.find(this._equationPatterns, function(pattern) {
// For each pattern in turn, find all whole matches (including the delimiters).
var patternMatches = text.match(new RegExp(pattern.source, "g"));
if (patternMatches && patternMatches.length) {
// This pattern matches at least once. See if this pattern matches our current position.
// Note: We return here to break the Y.Array.find loop - any truthy return will stop any subsequent
// searches which is the required behaviour of this function.
return Y.Array.find(patternMatches, function(match) {
// Check each occurrence of this match.
var startIndex = 0;
while(text.indexOf(match, startIndex) !== -1) {
// Determine whether the cursor is in the current occurrence of this string.
// Note: We do not support a selection exceeding the bounds of an equation.
var startOuter = text.indexOf(match, startIndex),
endOuter = startOuter + match.length,
startMatch = (selection.startOffset >= startOuter && selection.startOffset < endOuter),
endMatch = (selection.endOffset <= endOuter && selection.endOffset > startOuter);
if (startMatch && endMatch) {
// This match is in our current position - fetch the innerMatch data.
var innerMatch = match.match(pattern);
if (innerMatch && innerMatch.length) {
// We need the start and end of the inner match for later.
var startInner = text.indexOf(innerMatch[1], startOuter),
endInner = startInner + innerMatch[1].length;
// We'll be returning the inner match for use in the editor itself.
returnValue = innerMatch[1];
// Save all data for later.
this.sourceEquation = {
// Outer match data.
startOuterPosition: startOuter,
endOuterPosition: endOuter,
outerMatch: match,
// Inner match data.
startInnerPosition: startInner,
endInnerPosition: endInner,
innerMatch: innerMatch
};
// This breaks out of both Y.Array.find functions.
return true;
}
}
// Update the startIndex to match the end of the current match so that we can continue hunting
// for further matches.
startIndex = endOuter;
}
}, this);
}
}, this);
return returnValue;
},
/**
@ -287,13 +354,15 @@ Y.namespace('M.atto_equation').Button = Y.Base.create('button', Y.M.editor_atto.
if (value !== '') {
host.setSelection(this._currentSelection);
if (this.sourceEquation.length) {
if (this.sourceEquation) {
// Replace the equation.
selectedNode = Y.one(host.getSelectionParentNode());
text = selectedNode.get('text');
newText = text.slice(0, this.sourceEquation.startInnerPosition) +
value +
text.slice(this.sourceEquation.endInnerPosition);
text = text.replace(this.sourceEquation, value);
selectedNode.set('text', text);
selectedNode.set('text', newText);
} else {
// Insert the new equation.
value = DELIMITERS.START + ' ' + value + ' ' + DELIMITERS.END;
@ -625,4 +694,13 @@ Y.namespace('M.atto_equation').Button = Y.Base.create('button', Y.M.editor_atto.
});
}, '@VERSION@', {"requires": ["moodle-editor_atto-plugin", "moodle-core-event", "io", "event-valuechange", "tabview"]});
}, '@VERSION@', {
"requires": [
"moodle-editor_atto-plugin",
"moodle-core-event",
"io",
"event-valuechange",
"tabview",
"array-extras"
]
});

View File

@ -126,10 +126,10 @@ Y.namespace('M.atto_equation').Button = Y.Base.create('button', Y.M.editor_atto.
* The source equation we are editing in the text.
*
* @property _sourceEquation
* @type String
* @type Object
* @private
*/
_sourceEquation: '',
_sourceEquation: null,
/**
* A reference to the tab focus set on each group.
@ -142,6 +142,25 @@ Y.namespace('M.atto_equation').Button = Y.Base.create('button', Y.M.editor_atto.
*/
_groupFocus: null,
/**
* Regular Expression patterns used to pick out the equations in a String.
*
* @property _equationPatterns
* @type Array
* @private
*/
_equationPatterns: [
// We use space or not space because . does not match new lines.
// $$ blah $$.
/\$\$([\S\s]+?)\$\$/,
// E.g. "\( blah \)".
/\\\(([\S\s]+?)\\\)/,
// E.g. "\[ blah \]".
/\\\[([\S\s]+?)\\\]/,
// E.g. "[tex] blah [/tex]".
/\[tex\]([\S\s]+?)\[\/tex\]/
],
initializer: function() {
this._groupFocus = {};
@ -223,39 +242,87 @@ 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(),
selection = this.get('host').getSelection(),
text,
equation,
patterns = [], i;
returnValue = false;
this.sourceEquation = null;
// Note this is a document fragment and YUI doesn't like them.
if (!selectedNode) {
return false;
}
text = Y.one(selectedNode).get('text');
// We use space or not space because . does not match new lines.
// $$ 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;
}
// We don't yet have a cursor selection somehow so we can't possible be resolving an equation that has selection.
if (!selection || selection.length === 0) {
return false;
}
selection = selection[0];
this.sourceEquation = '';
return false;
text = Y.one(selectedNode).get('text');
// For each of these patterns we have a RegExp which captures the inner component of the equation but also includes the delimiters.
// We first run the RegExp adding the global flag ("g"). This ignores the capture, instead matching the entire
// equation including delimiters and returning one entry per match of the whole equation.
// We have to deal with multiple occurences of the same equation in a String so must be able to loop on the
// match results.
Y.Array.find(this._equationPatterns, function(pattern) {
// For each pattern in turn, find all whole matches (including the delimiters).
var patternMatches = text.match(new RegExp(pattern.source, "g"));
if (patternMatches && patternMatches.length) {
// This pattern matches at least once. See if this pattern matches our current position.
// Note: We return here to break the Y.Array.find loop - any truthy return will stop any subsequent
// searches which is the required behaviour of this function.
return Y.Array.find(patternMatches, function(match) {
// Check each occurrence of this match.
var startIndex = 0;
while(text.indexOf(match, startIndex) !== -1) {
// Determine whether the cursor is in the current occurrence of this string.
// Note: We do not support a selection exceeding the bounds of an equation.
var startOuter = text.indexOf(match, startIndex),
endOuter = startOuter + match.length,
startMatch = (selection.startOffset >= startOuter && selection.startOffset < endOuter),
endMatch = (selection.endOffset <= endOuter && selection.endOffset > startOuter);
if (startMatch && endMatch) {
// This match is in our current position - fetch the innerMatch data.
var innerMatch = match.match(pattern);
if (innerMatch && innerMatch.length) {
// We need the start and end of the inner match for later.
var startInner = text.indexOf(innerMatch[1], startOuter),
endInner = startInner + innerMatch[1].length;
// We'll be returning the inner match for use in the editor itself.
returnValue = innerMatch[1];
// Save all data for later.
this.sourceEquation = {
// Outer match data.
startOuterPosition: startOuter,
endOuterPosition: endOuter,
outerMatch: match,
// Inner match data.
startInnerPosition: startInner,
endInnerPosition: endInner,
innerMatch: innerMatch
};
// This breaks out of both Y.Array.find functions.
return true;
}
}
// Update the startIndex to match the end of the current match so that we can continue hunting
// for further matches.
startIndex = endOuter;
}
}, this);
}
}, this);
return returnValue;
},
/**
@ -285,13 +352,15 @@ Y.namespace('M.atto_equation').Button = Y.Base.create('button', Y.M.editor_atto.
if (value !== '') {
host.setSelection(this._currentSelection);
if (this.sourceEquation.length) {
if (this.sourceEquation) {
// Replace the equation.
selectedNode = Y.one(host.getSelectionParentNode());
text = selectedNode.get('text');
newText = text.slice(0, this.sourceEquation.startInnerPosition) +
value +
text.slice(this.sourceEquation.endInnerPosition);
text = text.replace(this.sourceEquation, value);
selectedNode.set('text', text);
selectedNode.set('text', newText);
} else {
// Insert the new equation.
value = DELIMITERS.START + ' ' + value + ' ' + DELIMITERS.END;

View File

@ -5,7 +5,8 @@
"moodle-core-event",
"io",
"event-valuechange",
"tabview"
"tabview",
"array-extras"
]
}
}