diff --git a/lib/editor/atto/plugins/equation/ajax.php b/lib/editor/atto/plugins/equation/ajax.php new file mode 100644 index 00000000000..03817755271 --- /dev/null +++ b/lib/editor/atto/plugins/equation/ajax.php @@ -0,0 +1,50 @@ +. + +/** + * Renders text with the active filters and returns it. Used to create previews of equations + * using whatever tex filters are enabled. + * + * @package atto_equation + * @copyright 2014 Damyon Wiese + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +define('AJAX_SCRIPT', true); + +require_once(dirname(__FILE__) . '/../../../../../config.php'); + +$contextid = required_param('contextid', PARAM_INT); +$context = context::instance_by_id($contextid, MUST_EXIST); +$PAGE->set_url('/lib/editor/atto/plugins/equation/ajax.php'); +$PAGE->set_context($context); + +require_login(); +require_sesskey(); + +$action = required_param('action', PARAM_ALPHA); + +if ($action === 'filtertext') { + $text = required_param('text', PARAM_RAW); + + $result = filter_manager::instance()->filter_text($text, $context); + echo $OUTPUT->header(); + echo $result; + + die(); +} + +print_error('invalidarguments'); diff --git a/lib/editor/atto/plugins/equation/lang/en/atto_equation.php b/lib/editor/atto/plugins/equation/lang/en/atto_equation.php new file mode 100644 index 00000000000..b0ec620bf1f --- /dev/null +++ b/lib/editor/atto/plugins/equation/lang/en/atto_equation.php @@ -0,0 +1,39 @@ +. + +/** + * Strings for component 'atto_equation', language 'en'. + * + * @package atto_equation + * @copyright 2013 Damyon Wiese + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +$string['editequation'] = 'Edit equation'; +$string['editequation_desc'] = 'Equations are written in TeX.'; +$string['librarygroup1'] = 'Operators'; +$string['librarygroup1_desc'] = 'List of tex commands to list on the operators tab.'; +$string['librarygroup2'] = 'Arrows'; +$string['librarygroup2_desc'] = 'List of tex commands to list on the arrows tab.'; +$string['librarygroup3'] = 'Greek Symbols'; +$string['librarygroup3_desc'] = 'List of tex commands to list on the greek symbols tab.'; +$string['librarygroup4'] = 'Advanced'; +$string['librarygroup4_desc'] = 'List of tex commands to list on the advanced tab.'; +$string['pluginname'] = 'Equation editor'; +$string['preview'] = 'Equation preview (cursor position is indicated with a box)'; +$string['saveequation'] = 'Save equation'; +$string['settings'] = 'Equation editor settings'; +$string['update'] = 'Update'; diff --git a/lib/editor/atto/plugins/equation/lib.php b/lib/editor/atto/plugins/equation/lib.php new file mode 100644 index 00000000000..224f85bb384 --- /dev/null +++ b/lib/editor/atto/plugins/equation/lib.php @@ -0,0 +1,73 @@ +. + +/** + * Atto text editor integration version file. + * + * @package atto_equation + * @copyright 2013 Damyon Wiese + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die(); + +/** + * Get the list of strings for this plugin. + * @param string $elementid + */ +function atto_equation_strings_for_js() { + global $PAGE; + + $PAGE->requires->strings_for_js(array('saveequation', + 'editequation', + 'preview', + 'editequation_desc', + 'update', + 'librarygroup1', + 'librarygroup2', + 'librarygroup3', + 'librarygroup4'), + 'atto_equation'); +} + +/** + * Set params for this plugin. + * + * @param string $elementid + * @param stdClass $options - the options for the editor, including the context. + * @param stdClass $fpoptions - unused. + */ +function atto_equation_params_for_js($elementid, $options, $fpoptions) { + $texexample = '$$\pi$$'; + + // Format a string with the active filter set. + // If it is modified - we assume that some sort of text filter is working in this context. + $result = format_text($texexample, true, $options); + + $texfilteractive = ($texexample !== $result); + $context = $options['context']; + if (!$context) { + $context = context_system::instance(); + } + + // Tex example librarys. + $library = array('group1' => get_config('atto_equation', 'librarygroup1'), + 'group2' => get_config('atto_equation', 'librarygroup2'), + 'group3' => get_config('atto_equation', 'librarygroup3'), + 'group4' => get_config('atto_equation', 'librarygroup4')); + + return array('texfilteractive' => $texfilteractive, 'contextid'=>$context->id, 'library'=>$library); +} diff --git a/lib/editor/atto/plugins/equation/settings.php b/lib/editor/atto/plugins/equation/settings.php new file mode 100644 index 00000000000..9aec16a2f7c --- /dev/null +++ b/lib/editor/atto/plugins/equation/settings.php @@ -0,0 +1,168 @@ +. + +/** + * Settings that allow configuration of the list of tex examples in the equation editor. + * + * @package atto_equation + * @copyright 2013 Damyon Wiese + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die(); + +$ADMIN->add('editoratto', new admin_category('atto_equation', new lang_string('pluginname', 'atto_equation'))); + +$settings = new admin_settingpage('atto_equation_settings', new lang_string('settings', 'atto_equation')); +if ($ADMIN->fulltree) { + // Group 1 + $name = new lang_string('librarygroup1', 'atto_equation'); + $desc = new lang_string('librarygroup1_desc', 'atto_equation'); + $default = ' +\cdot +\times +\ast +\div +\diamond +\pm +\mp +\oplus +\ominus +\otimes +\oslash +\odot +\circ +\bullet +\asymp +\equiv +\subseteq +\supseteq +\leq +\geq +\preceq +\succeq +\sim +\simeq +\approx +\subset +\supset +\ll +\gg +\prec +\succ +\infty +\in +\ni +\forall +\exists +\neq +'; + $setting = new admin_setting_configtextarea('atto_equation/librarygroup1', + $name, + $desc, + $default); + $settings->add($setting); + + // Group 2 + $name = new lang_string('librarygroup2', 'atto_equation'); + $desc = new lang_string('librarygroup2_desc', 'atto_equation'); + $default = ' +\leftarrow +\rightarrow +\uparrow +\downarrow +\leftrightarrow +\nearrow +\searrow +\swarrow +\nwarrow +\Leftarrow +\Rightarrow +\Uparrow +\Downarrow +\Leftrightarrow +'; + $setting = new admin_setting_configtextarea('atto_equation/librarygroup2', + $name, + $desc, + $default); + $settings->add($setting); + + // Group 3 + $name = new lang_string('librarygroup3', 'atto_equation'); + $desc = new lang_string('librarygroup3_desc', 'atto_equation'); + $default = ' +\alpha +\beta +\gamma +\delta +\epsilon +\zeta +\eta +\theta +\iota +\kappa +\lambda +\mu +\nu +\xi +\pi +\rho +\sigma +\tau +\upsilon +\phi +\chi +\psi +\omega +\Gamma +\Delta +\Theta +\Lambda +\Xi +\Pi +\Sigma +\Upsilon +\Phi +\Psi +\Omega +'; + $setting = new admin_setting_configtextarea('atto_equation/librarygroup3', + $name, + $desc, + $default); + $settings->add($setting); + + // Group 4 + $name = new lang_string('librarygroup4', 'atto_equation'); + $desc = new lang_string('librarygroup4_desc', 'atto_equation'); + $default = ' +\sum{a,b} +\int_{a}^{b}{c} +\iint_{a}^{b}{c} +\iiint_{a}^{b}{c} +\oint{a} +(a) +[a] +\lbrace{a}\rbrace +\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); + $settings->add($setting); +} diff --git a/lib/editor/atto/plugins/equation/styles.css b/lib/editor/atto/plugins/equation/styles.css new file mode 100644 index 00000000000..2339c2e74b9 --- /dev/null +++ b/lib/editor/atto/plugins/equation/styles.css @@ -0,0 +1,21 @@ +#atto_equation_library .yui3-tabview-list { + border: none; +} + +#atto_equation_library .yui3-tab-selected .yui3-tab-label, .yui3-skin-sam #atto_equation_library .yui3-tab-selected .yui3-tab-label:focus, .yui3-skin-sam #atto_equation_library .yui3-tab-selected .yui3-tab-label:hover { + background: none; + color: black; + border-top-left-radius: 4px; + border-top-right-radius: 4px; +} + +#atto_equation_library button { + margin: 4px; +} + +#page-admin-setting-atto_equation_settings .form-defaultinfo { + max-height: 10em; + overflow: auto; + padding: 5px; + min-width: 206px; +} diff --git a/lib/editor/atto/plugins/equation/version.php b/lib/editor/atto/plugins/equation/version.php new file mode 100644 index 00000000000..c7eda326fb6 --- /dev/null +++ b/lib/editor/atto/plugins/equation/version.php @@ -0,0 +1,29 @@ +. + +/** + * Atto text editor integration version file. + * + * @package atto_equation + * @copyright 2013 Damyon Wiese + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die(); + +$plugin->version = 2014012800; // The current plugin version (Date: YYYYMMDDXX). +$plugin->requires = 2013110500; // Requires this Moodle version. +$plugin->component = 'atto_equation'; // Full name of the plugin (used for diagnostics). diff --git a/lib/editor/atto/plugins/equation/yui/build/moodle-atto_equation-button/moodle-atto_equation-button-debug.js b/lib/editor/atto/plugins/equation/yui/build/moodle-atto_equation-button/moodle-atto_equation-button-debug.js new file mode 100644 index 00000000000..0667bacc269 --- /dev/null +++ b/lib/editor/atto/plugins/equation/yui/build/moodle-atto_equation-button/moodle-atto_equation-button-debug.js @@ -0,0 +1,365 @@ +YUI.add('moodle-atto_equation-button', 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 . + +/** + * Atto text editor equation plugin. + * + * @package editor-atto + * @copyright 2013 Damyon Wiese + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +M.atto_equation = M.atto_equation || { + /** + * The window used to get the equation details. + * + * @property dialogue + * @type M.core.dialogue + * @default null + */ + dialogue : null, + + /** + * The selection object returned by the browser. + * + * @property selection + * @type Range + * @default null + */ + selection : null, + + /** + * A mapping of elementids to contextids. + * + * @property contextids + * @type Object + * @default {} + */ + contextids : {}, + + /** + * A nested object containing a the configured list of tex examples. + * + * @property library + * @type Object + * @default {} + */ + library : {}, + + /** + * The last cursor index in the source. + * + * @property lastcursor + * @type Integer + * @default 0 + */ + lastcursor : 0, + + /** + * Display the chooser dialogue. + * + * @method display_chooser + * @param Event e + * @param string elementid + */ + display_chooser : function(e, elementid) { + e.preventDefault(); + if (!M.editor_atto.is_active(elementid)) { + M.editor_atto.focus(elementid); + } + M.atto_equation.selection = M.editor_atto.get_selection(); + if (M.atto_equation.selection !== false && (!M.atto_equation.selection.collapsed)) { + var dialogue; + if (!M.atto_equation.dialogue) { + dialogue = new M.core.dialogue({ + visible: false, + modal: true, + close: true, + draggable: true, + width: '800px' + }); + } else { + dialogue = M.atto_equation.dialogue; + } + + dialogue.render(); + dialogue.set('bodyContent', M.atto_equation.get_form_content(elementid)); + dialogue.set('headerContent', M.util.get_string('pluginname', 'atto_equation')); + + var tabview = new Y.TabView({ + srcNode: '#atto_equation_library' + }); + + tabview.render(); + dialogue.show(); + M.atto_equation.resolve_equation(); + M.atto_equation.update_preview(false, elementid); + M.atto_equation.dialogue = dialogue; + } + }, + + /** + * Add this button to the form. + * + * @method init + * @param {Object} params + */ + init : function(params) { + var iconurl = M.util.image_url('e/math', 'core'); + + if (params.texfilteractive) { + // Save the elementid/contextid mapping. + this.contextids[params.elementid] = params.contextid; + // Save the button library. + this.library = params.library; + + // Add the button to the toolbar. + M.editor_atto.add_toolbar_button(params.elementid, 'equation', iconurl, params.group, this.display_chooser); + } + }, + + /** + * If there is selected text and it is part of an equation, + * extract the equation (and set it in the form). + * + * @method resolve_equation + */ + resolve_equation : function() { + // Find the equation in the surrounding text. + var selectednode = M.editor_atto.get_selection_parent_node(), + text, + equation; + + // Note this is a document fragment and YUI doesn't like them. + if (!selectednode) { + return; + } + + 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); + Y.one('#atto_equation_equation').set('text', equation); + } + }, + + /** + * The OK button has been pressed - make the changes to the source. + * + * @method set_equation + * @param {Y.Event} e + * @param {String} elementid + */ + set_equation : function(e, elementid) { + var input, + selectednode, + text, + pattern, + equation, + value; + + e.preventDefault(); + M.atto_equation.dialogue.hide(); + M.editor_atto.set_selection(M.atto_equation.selection); + + input = e.currentTarget.ancestor('.atto_form').one('textarea'); + + value = input.get('value'); + if (value !== '') { + value = '$$ ' + value.trim() + ' $$'; + selectednode = Y.one(M.editor_atto.get_selection_parent_node()), + text = selectednode.get('text'); + pattern = /\$\$[\S\s]*\$\$/; + equation = pattern.exec(text); + if (equation && equation.length) { + // Replace the equation. + equation = equation.pop(); + text = text.replace(equation, '$$' + value + '$$'); + selectednode.set('text', text); + } else { + // Insert the new equation. + if (document.selection && document.selection.createRange().pasteHTML) { + document.selection.createRange().pasteHTML(value); + } else { + document.execCommand('insertHTML', false, value); + } + + } + + // Clean the YUI ids from the HTML. + M.editor_atto.text_updated(elementid); + } + }, + + /** + * Update the preview div to match the current equation. + * + * @param Event e - unused + * @param String elementid - The editor elementid. + * @method update_preview + */ + update_preview : function(e, elementid) { + var textarea = Y.one('#atto_equation_equation'); + var equation = textarea.get('value'), url, preview; + var prefix = ''; + var cursorlatex = '\\square ' ; + + var currentpos = textarea.get('selectionStart'); + if (!currentpos) { + currentpos = 0; + } + // Move the cursor so it does not break expressions. + // + while (equation.charAt(currentpos) === '\\' && currentpos > 0) { + currentpos -= 1; + } + var ischar = /[\w\{\}]/; + 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); + if (e) { + e.preventDefault(); + } + url = M.cfg.wwwroot + '/lib/editor/atto/plugins/equation/ajax.php'; + params = { + sesskey: M.cfg.sesskey, + contextid: this.contextids[elementid], + action : 'filtertext', + text : '$$ ' + equation + ' $$' + }; + + + preview = Y.io(url, { sync: true, + data: params }); + if (preview.status === 200) { + Y.one('#atto_equation_preview').setHTML(preview.responseText); + } + }, + + /** + * Return the HTML of the form to show in the dialogue. + * + * @method get_form_content + * @param string elementid + * @return string + */ + get_form_content : function(elementid) { + var content = Y.Node.create('
' + + this.get_library_html(elementid) + + '' + + '
' + + '

' + M.util.get_string('editequation_desc', 'atto_equation') + '

' + + '' + + '
' + + '
' + + '
' + + '' + + '
' + + '
'); + + content.one('#atto_equation_submit').on('click', M.atto_equation.set_equation, this, elementid); + content.one('#atto_equation_equation').on('valuechange', M.atto_equation.update_preview, this, elementid); + content.one('#atto_equation_equation').on('keyup', M.atto_equation.update_preview, this, elementid); + content.one('#atto_equation_equation').on('mouseup', M.atto_equation.update_preview, this, elementid); + + content.delegate('click', M.atto_equation.select_library_item, '#atto_equation_library button', this, elementid); + + return content; + }, + + /** + * Reponse to button presses in the tex library panels. + * + * @method select_library_item + * @param Event event + * @param string elementid + * @return string + */ + select_library_item : function(event, elementid) { + var tex = event.currentTarget.getAttribute('data-tex'); + + event.preventDefault(); + + input = event.currentTarget.ancestor('.atto_form').one('textarea'); + + value = input.get('value'); + + value = value.substring(0, this.lastcursorpos) + tex + value.substring(this.lastcursorpos, value.length); + + input.set('value', value); + M.atto_equation.update_preview(false, elementid); + input.focus(); + }, + + /** + * Return the HTML for rendering the library of predefined buttons. + * + * @method get_library_html + * @param string elementid + * @return string + */ + get_library_html : function(elementid) { + var content = '
', i = 0, group = 1; + content += ''; + content += '
'; + for (group = 1; group < 5; group++) { + content += '
'; + var examples = this.library['group' + group].split("\n"); + for (i = 0; i < examples.length; i++) { + if (examples[i]) { + examples[i] = Y.Escape.html(examples[i]); + content += ''; + } + } + content += '
'; + } + content += '
'; + content += '
'; + + var url = M.cfg.wwwroot + '/lib/editor/atto/plugins/equation/ajax.php'; + var params = { + sesskey: M.cfg.sesskey, + contextid: this.contextids[elementid], + action : 'filtertext', + text : content + }; + + preview = Y.io(url, { sync: true, data: params, method: 'POST'}); + + if (preview.status === 200) { + content = preview.responseText; + } + return content; + } +}; + + +}, '@VERSION@', {"requires": ["node", "escape", "io", "event-valuechange", "tabview"]}); diff --git a/lib/editor/atto/plugins/equation/yui/build/moodle-atto_equation-button/moodle-atto_equation-button-min.js b/lib/editor/atto/plugins/equation/yui/build/moodle-atto_equation-button/moodle-atto_equation-button-min.js new file mode 100644 index 00000000000..ba9c3cacf3e --- /dev/null +++ b/lib/editor/atto/plugins/equation/yui/build/moodle-atto_equation-button/moodle-atto_equation-button-min.js @@ -0,0 +1 @@ +YUI.add("moodle-atto_equation-button",function(e,t){M.atto_equation=M.atto_equation||{dialogue:null,selection:null,contextids:{},library:{},lastcursor:0,display_chooser:function(t,n){t.preventDefault(),M.editor_atto.is_active(n)||M.editor_atto.focus(n),M.atto_equation.selection=M.editor_atto.get_selection();if(M.atto_equation.selection!==!1&&!M.atto_equation.selection.collapsed){var r;M.atto_equation.dialogue?r=M.atto_equation.dialogue:r=new M.core.dialogue({visible:!1,modal:!0,close:!0,draggable:!0,width:"800px"}),r.render(),r.set("bodyContent",M.atto_equation.get_form_content(n)),r.set("headerContent",M.util.get_string("pluginname","atto_equation"));var i=new e.TabView({srcNode:"#atto_equation_library"});i.render(),r.show(),M.atto_equation.resolve_equation(),M.atto_equation.update_preview(!1,n),M.atto_equation.dialogue=r}},init:function(e){var t=M.util.image_url("e/math","core");e.texfilteractive&&(this.contextids[e.elementid]=e.contextid,this.library=e.library,M.editor_atto.add_toolbar_button(e.elementid,"equation",t,e.group,this.display_chooser))},resolve_equation:function(){var t=M.editor_atto.get_selection_parent_node(),n,r;if(!t)return;n=e.one(t).get("text"),pattern=/\$\$[\S\s]*\$\$/,r=pattern.exec(n),r&&r.length&&(r=r.pop(),r=r.substring(2,r.length-2),e.one("#atto_equation_equation").set("text",r))},set_equation:function(t,n){var r,i,s,o,u,a;t.preventDefault(),M.atto_equation.dialogue.hide(),M.editor_atto.set_selection(M.atto_equation.selection),r=t.currentTarget.ancestor(".atto_form").one("textarea"),a=r.get("value"),a!==""&&(a="$$ "+a.trim()+" $$",i=e.one(M.editor_atto.get_selection_parent_node()),s=i.get("text"),o=/\$\$[\S\s]*\$\$/,u=o.exec(s),u&&u.length?(u=u.pop(),s=s.replace(u,"$$"+a+"$$"),i.set("text",s)):document.selection&&document.selection.createRange().pasteHTML?document.selection.createRange().pasteHTML(a):document.execCommand("insertHTML",!1,a),M.editor_atto.text_updated(n))},update_preview:function(t,n){var r=e.one("#atto_equation_equation"),i=r.get("value"),s,o,u="",a="\\square ",f=r.get("selectionStart");f||(f=0);while(i.charAt(f)==="\\"&&f>0)f-=1;var l=/[\w\{\}]/;while(l.test(i.charAt(f))&&f'+this.get_library_html(t)+'"+'
'+"

"+M.util.get_string("editequation_desc","atto_equation")+"

"+'"+'
'+'
'+"
"+'"+"
"+"");return n.one("#atto_equation_submit").on("click",M.atto_equation.set_equation,this,t),n.one("#atto_equation_equation").on("valuechange",M.atto_equation.update_preview,this,t),n.one("#atto_equation_equation").on("keyup",M.atto_equation.update_preview,this,t),n.one("#atto_equation_equation").on("mouseup",M.atto_equation.update_preview,this,t),n.delegate("click",M.atto_equation.select_library_item,"#atto_equation_library button",this,t),n},select_library_item:function(e,t){var n=e.currentTarget.getAttribute("data-tex");e.preventDefault(),input=e.currentTarget.ancestor(".atto_form").one("textarea"),value=input.get("value"),value=value.substring(0,this.lastcursorpos)+n+value.substring(this.lastcursorpos,value.length),input.set("value",value),M.atto_equation.update_preview(!1,t),input.focus()},get_library_html:function(t){var n='
',r=0,i=1;n+="",n+="
";for(i=1;i<5;i++){n+='
';var s=this.library["group"+i].split("\n");for(r=0;r$$'+s[r]+"$$");n+="
"}n+="
",n+="
";var o=M.cfg.wwwroot+"/lib/editor/atto/plugins/equation/ajax.php",u={sesskey:M.cfg.sesskey,contextid:this.contextids[t],action:"filtertext",text:n};return preview=e.io(o,{sync:!0,data:u,method:"POST"}),preview.status===200&&(n=preview.responseText),n}}},"@VERSION@",{requires:["node","escape","io","event-valuechange","tabview"]}); diff --git a/lib/editor/atto/plugins/equation/yui/build/moodle-atto_equation-button/moodle-atto_equation-button.js b/lib/editor/atto/plugins/equation/yui/build/moodle-atto_equation-button/moodle-atto_equation-button.js new file mode 100644 index 00000000000..0667bacc269 --- /dev/null +++ b/lib/editor/atto/plugins/equation/yui/build/moodle-atto_equation-button/moodle-atto_equation-button.js @@ -0,0 +1,365 @@ +YUI.add('moodle-atto_equation-button', 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 . + +/** + * Atto text editor equation plugin. + * + * @package editor-atto + * @copyright 2013 Damyon Wiese + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +M.atto_equation = M.atto_equation || { + /** + * The window used to get the equation details. + * + * @property dialogue + * @type M.core.dialogue + * @default null + */ + dialogue : null, + + /** + * The selection object returned by the browser. + * + * @property selection + * @type Range + * @default null + */ + selection : null, + + /** + * A mapping of elementids to contextids. + * + * @property contextids + * @type Object + * @default {} + */ + contextids : {}, + + /** + * A nested object containing a the configured list of tex examples. + * + * @property library + * @type Object + * @default {} + */ + library : {}, + + /** + * The last cursor index in the source. + * + * @property lastcursor + * @type Integer + * @default 0 + */ + lastcursor : 0, + + /** + * Display the chooser dialogue. + * + * @method display_chooser + * @param Event e + * @param string elementid + */ + display_chooser : function(e, elementid) { + e.preventDefault(); + if (!M.editor_atto.is_active(elementid)) { + M.editor_atto.focus(elementid); + } + M.atto_equation.selection = M.editor_atto.get_selection(); + if (M.atto_equation.selection !== false && (!M.atto_equation.selection.collapsed)) { + var dialogue; + if (!M.atto_equation.dialogue) { + dialogue = new M.core.dialogue({ + visible: false, + modal: true, + close: true, + draggable: true, + width: '800px' + }); + } else { + dialogue = M.atto_equation.dialogue; + } + + dialogue.render(); + dialogue.set('bodyContent', M.atto_equation.get_form_content(elementid)); + dialogue.set('headerContent', M.util.get_string('pluginname', 'atto_equation')); + + var tabview = new Y.TabView({ + srcNode: '#atto_equation_library' + }); + + tabview.render(); + dialogue.show(); + M.atto_equation.resolve_equation(); + M.atto_equation.update_preview(false, elementid); + M.atto_equation.dialogue = dialogue; + } + }, + + /** + * Add this button to the form. + * + * @method init + * @param {Object} params + */ + init : function(params) { + var iconurl = M.util.image_url('e/math', 'core'); + + if (params.texfilteractive) { + // Save the elementid/contextid mapping. + this.contextids[params.elementid] = params.contextid; + // Save the button library. + this.library = params.library; + + // Add the button to the toolbar. + M.editor_atto.add_toolbar_button(params.elementid, 'equation', iconurl, params.group, this.display_chooser); + } + }, + + /** + * If there is selected text and it is part of an equation, + * extract the equation (and set it in the form). + * + * @method resolve_equation + */ + resolve_equation : function() { + // Find the equation in the surrounding text. + var selectednode = M.editor_atto.get_selection_parent_node(), + text, + equation; + + // Note this is a document fragment and YUI doesn't like them. + if (!selectednode) { + return; + } + + 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); + Y.one('#atto_equation_equation').set('text', equation); + } + }, + + /** + * The OK button has been pressed - make the changes to the source. + * + * @method set_equation + * @param {Y.Event} e + * @param {String} elementid + */ + set_equation : function(e, elementid) { + var input, + selectednode, + text, + pattern, + equation, + value; + + e.preventDefault(); + M.atto_equation.dialogue.hide(); + M.editor_atto.set_selection(M.atto_equation.selection); + + input = e.currentTarget.ancestor('.atto_form').one('textarea'); + + value = input.get('value'); + if (value !== '') { + value = '$$ ' + value.trim() + ' $$'; + selectednode = Y.one(M.editor_atto.get_selection_parent_node()), + text = selectednode.get('text'); + pattern = /\$\$[\S\s]*\$\$/; + equation = pattern.exec(text); + if (equation && equation.length) { + // Replace the equation. + equation = equation.pop(); + text = text.replace(equation, '$$' + value + '$$'); + selectednode.set('text', text); + } else { + // Insert the new equation. + if (document.selection && document.selection.createRange().pasteHTML) { + document.selection.createRange().pasteHTML(value); + } else { + document.execCommand('insertHTML', false, value); + } + + } + + // Clean the YUI ids from the HTML. + M.editor_atto.text_updated(elementid); + } + }, + + /** + * Update the preview div to match the current equation. + * + * @param Event e - unused + * @param String elementid - The editor elementid. + * @method update_preview + */ + update_preview : function(e, elementid) { + var textarea = Y.one('#atto_equation_equation'); + var equation = textarea.get('value'), url, preview; + var prefix = ''; + var cursorlatex = '\\square ' ; + + var currentpos = textarea.get('selectionStart'); + if (!currentpos) { + currentpos = 0; + } + // Move the cursor so it does not break expressions. + // + while (equation.charAt(currentpos) === '\\' && currentpos > 0) { + currentpos -= 1; + } + var ischar = /[\w\{\}]/; + 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); + if (e) { + e.preventDefault(); + } + url = M.cfg.wwwroot + '/lib/editor/atto/plugins/equation/ajax.php'; + params = { + sesskey: M.cfg.sesskey, + contextid: this.contextids[elementid], + action : 'filtertext', + text : '$$ ' + equation + ' $$' + }; + + + preview = Y.io(url, { sync: true, + data: params }); + if (preview.status === 200) { + Y.one('#atto_equation_preview').setHTML(preview.responseText); + } + }, + + /** + * Return the HTML of the form to show in the dialogue. + * + * @method get_form_content + * @param string elementid + * @return string + */ + get_form_content : function(elementid) { + var content = Y.Node.create('
' + + this.get_library_html(elementid) + + '' + + '
' + + '

' + M.util.get_string('editequation_desc', 'atto_equation') + '

' + + '' + + '
' + + '
' + + '
' + + '' + + '
' + + '
'); + + content.one('#atto_equation_submit').on('click', M.atto_equation.set_equation, this, elementid); + content.one('#atto_equation_equation').on('valuechange', M.atto_equation.update_preview, this, elementid); + content.one('#atto_equation_equation').on('keyup', M.atto_equation.update_preview, this, elementid); + content.one('#atto_equation_equation').on('mouseup', M.atto_equation.update_preview, this, elementid); + + content.delegate('click', M.atto_equation.select_library_item, '#atto_equation_library button', this, elementid); + + return content; + }, + + /** + * Reponse to button presses in the tex library panels. + * + * @method select_library_item + * @param Event event + * @param string elementid + * @return string + */ + select_library_item : function(event, elementid) { + var tex = event.currentTarget.getAttribute('data-tex'); + + event.preventDefault(); + + input = event.currentTarget.ancestor('.atto_form').one('textarea'); + + value = input.get('value'); + + value = value.substring(0, this.lastcursorpos) + tex + value.substring(this.lastcursorpos, value.length); + + input.set('value', value); + M.atto_equation.update_preview(false, elementid); + input.focus(); + }, + + /** + * Return the HTML for rendering the library of predefined buttons. + * + * @method get_library_html + * @param string elementid + * @return string + */ + get_library_html : function(elementid) { + var content = '
', i = 0, group = 1; + content += ''; + content += '
'; + for (group = 1; group < 5; group++) { + content += '
'; + var examples = this.library['group' + group].split("\n"); + for (i = 0; i < examples.length; i++) { + if (examples[i]) { + examples[i] = Y.Escape.html(examples[i]); + content += ''; + } + } + content += '
'; + } + content += '
'; + content += '
'; + + var url = M.cfg.wwwroot + '/lib/editor/atto/plugins/equation/ajax.php'; + var params = { + sesskey: M.cfg.sesskey, + contextid: this.contextids[elementid], + action : 'filtertext', + text : content + }; + + preview = Y.io(url, { sync: true, data: params, method: 'POST'}); + + if (preview.status === 200) { + content = preview.responseText; + } + return content; + } +}; + + +}, '@VERSION@', {"requires": ["node", "escape", "io", "event-valuechange", "tabview"]}); diff --git a/lib/editor/atto/plugins/equation/yui/src/button/build.json b/lib/editor/atto/plugins/equation/yui/src/button/build.json new file mode 100644 index 00000000000..a396fd1903a --- /dev/null +++ b/lib/editor/atto/plugins/equation/yui/src/button/build.json @@ -0,0 +1,10 @@ +{ + "name": "moodle-atto_equation-button", + "builds": { + "moodle-atto_equation-button": { + "jsfiles": [ + "button.js" + ] + } + } +} diff --git a/lib/editor/atto/plugins/equation/yui/src/button/js/button.js b/lib/editor/atto/plugins/equation/yui/src/button/js/button.js new file mode 100644 index 00000000000..8a38f592c5c --- /dev/null +++ b/lib/editor/atto/plugins/equation/yui/src/button/js/button.js @@ -0,0 +1,360 @@ +// 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 . + +/** + * Atto text editor equation plugin. + * + * @package editor-atto + * @copyright 2013 Damyon Wiese + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +M.atto_equation = M.atto_equation || { + /** + * The window used to get the equation details. + * + * @property dialogue + * @type M.core.dialogue + * @default null + */ + dialogue : null, + + /** + * The selection object returned by the browser. + * + * @property selection + * @type Range + * @default null + */ + selection : null, + + /** + * A mapping of elementids to contextids. + * + * @property contextids + * @type Object + * @default {} + */ + contextids : {}, + + /** + * A nested object containing a the configured list of tex examples. + * + * @property library + * @type Object + * @default {} + */ + library : {}, + + /** + * The last cursor index in the source. + * + * @property lastcursor + * @type Integer + * @default 0 + */ + lastcursor : 0, + + /** + * Display the chooser dialogue. + * + * @method display_chooser + * @param Event e + * @param string elementid + */ + display_chooser : function(e, elementid) { + e.preventDefault(); + if (!M.editor_atto.is_active(elementid)) { + M.editor_atto.focus(elementid); + } + M.atto_equation.selection = M.editor_atto.get_selection(); + if (M.atto_equation.selection !== false && (!M.atto_equation.selection.collapsed)) { + var dialogue; + if (!M.atto_equation.dialogue) { + dialogue = new M.core.dialogue({ + visible: false, + modal: true, + close: true, + draggable: true, + width: '800px' + }); + } else { + dialogue = M.atto_equation.dialogue; + } + + dialogue.render(); + dialogue.set('bodyContent', M.atto_equation.get_form_content(elementid)); + dialogue.set('headerContent', M.util.get_string('pluginname', 'atto_equation')); + + var tabview = new Y.TabView({ + srcNode: '#atto_equation_library' + }); + + tabview.render(); + dialogue.show(); + M.atto_equation.resolve_equation(); + M.atto_equation.update_preview(false, elementid); + M.atto_equation.dialogue = dialogue; + } + }, + + /** + * Add this button to the form. + * + * @method init + * @param {Object} params + */ + init : function(params) { + var iconurl = M.util.image_url('e/math', 'core'); + + if (params.texfilteractive) { + // Save the elementid/contextid mapping. + this.contextids[params.elementid] = params.contextid; + // Save the button library. + this.library = params.library; + + // Add the button to the toolbar. + M.editor_atto.add_toolbar_button(params.elementid, 'equation', iconurl, params.group, this.display_chooser); + } + }, + + /** + * If there is selected text and it is part of an equation, + * extract the equation (and set it in the form). + * + * @method resolve_equation + */ + resolve_equation : function() { + // Find the equation in the surrounding text. + var selectednode = M.editor_atto.get_selection_parent_node(), + text, + equation; + + // Note this is a document fragment and YUI doesn't like them. + if (!selectednode) { + return; + } + + 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); + Y.one('#atto_equation_equation').set('text', equation); + } + }, + + /** + * The OK button has been pressed - make the changes to the source. + * + * @method set_equation + * @param {Y.Event} e + * @param {String} elementid + */ + set_equation : function(e, elementid) { + var input, + selectednode, + text, + pattern, + equation, + value; + + e.preventDefault(); + M.atto_equation.dialogue.hide(); + M.editor_atto.set_selection(M.atto_equation.selection); + + input = e.currentTarget.ancestor('.atto_form').one('textarea'); + + value = input.get('value'); + if (value !== '') { + value = '$$ ' + value.trim() + ' $$'; + selectednode = Y.one(M.editor_atto.get_selection_parent_node()), + text = selectednode.get('text'); + pattern = /\$\$[\S\s]*\$\$/; + equation = pattern.exec(text); + if (equation && equation.length) { + // Replace the equation. + equation = equation.pop(); + text = text.replace(equation, '$$' + value + '$$'); + selectednode.set('text', text); + } else { + // Insert the new equation. + if (document.selection && document.selection.createRange().pasteHTML) { + document.selection.createRange().pasteHTML(value); + } else { + document.execCommand('insertHTML', false, value); + } + + } + + // Clean the YUI ids from the HTML. + M.editor_atto.text_updated(elementid); + } + }, + + /** + * Update the preview div to match the current equation. + * + * @param Event e - unused + * @param String elementid - The editor elementid. + * @method update_preview + */ + update_preview : function(e, elementid) { + var textarea = Y.one('#atto_equation_equation'); + var equation = textarea.get('value'), url, preview; + var prefix = ''; + var cursorlatex = '\\square ' ; + + var currentpos = textarea.get('selectionStart'); + if (!currentpos) { + currentpos = 0; + } + // Move the cursor so it does not break expressions. + // + while (equation.charAt(currentpos) === '\\' && currentpos > 0) { + currentpos -= 1; + } + var ischar = /[\w\{\}]/; + 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); + if (e) { + e.preventDefault(); + } + url = M.cfg.wwwroot + '/lib/editor/atto/plugins/equation/ajax.php'; + params = { + sesskey: M.cfg.sesskey, + contextid: this.contextids[elementid], + action : 'filtertext', + text : '$$ ' + equation + ' $$' + }; + + + preview = Y.io(url, { sync: true, + data: params }); + if (preview.status === 200) { + Y.one('#atto_equation_preview').setHTML(preview.responseText); + } + }, + + /** + * Return the HTML of the form to show in the dialogue. + * + * @method get_form_content + * @param string elementid + * @return string + */ + get_form_content : function(elementid) { + var content = Y.Node.create('
' + + this.get_library_html(elementid) + + '' + + '
' + + '

' + M.util.get_string('editequation_desc', 'atto_equation') + '

' + + '' + + '
' + + '
' + + '
' + + '' + + '
' + + '
'); + + content.one('#atto_equation_submit').on('click', M.atto_equation.set_equation, this, elementid); + content.one('#atto_equation_equation').on('valuechange', M.atto_equation.update_preview, this, elementid); + content.one('#atto_equation_equation').on('keyup', M.atto_equation.update_preview, this, elementid); + content.one('#atto_equation_equation').on('mouseup', M.atto_equation.update_preview, this, elementid); + + content.delegate('click', M.atto_equation.select_library_item, '#atto_equation_library button', this, elementid); + + return content; + }, + + /** + * Reponse to button presses in the tex library panels. + * + * @method select_library_item + * @param Event event + * @param string elementid + * @return string + */ + select_library_item : function(event, elementid) { + var tex = event.currentTarget.getAttribute('data-tex'); + + event.preventDefault(); + + input = event.currentTarget.ancestor('.atto_form').one('textarea'); + + value = input.get('value'); + + value = value.substring(0, this.lastcursorpos) + tex + value.substring(this.lastcursorpos, value.length); + + input.set('value', value); + M.atto_equation.update_preview(false, elementid); + input.focus(); + }, + + /** + * Return the HTML for rendering the library of predefined buttons. + * + * @method get_library_html + * @param string elementid + * @return string + */ + get_library_html : function(elementid) { + var content = '
', i = 0, group = 1; + content += ''; + content += '
'; + for (group = 1; group < 5; group++) { + content += '
'; + var examples = this.library['group' + group].split("\n"); + for (i = 0; i < examples.length; i++) { + if (examples[i]) { + examples[i] = Y.Escape.html(examples[i]); + content += ''; + } + } + content += '
'; + } + content += '
'; + content += '
'; + + var url = M.cfg.wwwroot + '/lib/editor/atto/plugins/equation/ajax.php'; + var params = { + sesskey: M.cfg.sesskey, + contextid: this.contextids[elementid], + action : 'filtertext', + text : content + }; + + preview = Y.io(url, { sync: true, data: params, method: 'POST'}); + + if (preview.status === 200) { + content = preview.responseText; + } + return content; + } +}; diff --git a/lib/editor/atto/plugins/equation/yui/src/button/meta/button.json b/lib/editor/atto/plugins/equation/yui/src/button/meta/button.json new file mode 100644 index 00000000000..11747f5bbc2 --- /dev/null +++ b/lib/editor/atto/plugins/equation/yui/src/button/meta/button.json @@ -0,0 +1,11 @@ +{ + "moodle-atto_equation-button": { + "requires": [ + "node", + "escape", + "io", + "event-valuechange", + "tabview" + ] + } +} diff --git a/lib/editor/atto/settings.php b/lib/editor/atto/settings.php index eb5ac8f3d66..af357f84ec0 100644 --- a/lib/editor/atto/settings.php +++ b/lib/editor/atto/settings.php @@ -37,7 +37,7 @@ if ($ADMIN->fulltree) { 'files = image, media, managefiles' . "\n" . 'style2 = underline, strike, subscript, superscript' . "\n" . 'indent = indent, outdent' . "\n" . - 'insert = charmap, table, clear' . "\n" . + 'insert = equation, charmap, table, clear' . "\n" . 'accessibility = accessibilitychecker, accessibilityhelper' . "\n" . 'other = html'; $setting = new admin_setting_configtextarea('editor_atto/toolbar', @@ -46,7 +46,6 @@ if ($ADMIN->fulltree) { $default); $settings->add($setting); - } $ADMIN->add('editoratto', $settings); @@ -58,3 +57,4 @@ foreach (core_plugin_manager::instance()->get_plugins_of_type('atto') as $plugin // Required or the editor plugininfo will add this section twice. unset($settings); $settings = null; + diff --git a/pix/e/dragmath.png b/pix/e/math.png similarity index 100% rename from pix/e/dragmath.png rename to pix/e/math.png diff --git a/pix/e/dragmath.svg b/pix/e/math.svg similarity index 100% rename from pix/e/dragmath.svg rename to pix/e/math.svg