diff --git a/filter/glossary/yui/build/moodle-filter_glossary-autolinker/moodle-filter_glossary-autolinker-debug.js b/filter/glossary/yui/build/moodle-filter_glossary-autolinker/moodle-filter_glossary-autolinker-debug.js index 0a1867900fa..92b7fe50f2c 100644 --- a/filter/glossary/yui/build/moodle-filter_glossary-autolinker/moodle-filter_glossary-autolinker-debug.js +++ b/filter/glossary/yui/build/moodle-filter_glossary-autolinker/moodle-filter_glossary-autolinker-debug.js @@ -72,6 +72,8 @@ Y.extend(AUTOLINKER, Y.Base, { alertpanel = new M.core.alert({title:data.entries[key].concept, message:definition, modal:false, yesLabel: M.util.get_string('ok', 'moodle')}); alertpanel.show(); + Y.fire(M.core.event.FILTER_CONTENT_UPDATED, {nodes: (new Y.NodeList(alertpanel.get('boundingBox')))}); + Y.Node.one('#id_yuialertconfirm-' + alertpanel.get('COUNT')).focus(); } @@ -141,6 +143,7 @@ M.filter_glossary.init_filter_autolinking = function(config) { "json-parse", "event-delegate", "overlay", + "moodle-core-event", "moodle-core-notification-alert" ] }); diff --git a/filter/glossary/yui/build/moodle-filter_glossary-autolinker/moodle-filter_glossary-autolinker-min.js b/filter/glossary/yui/build/moodle-filter_glossary-autolinker/moodle-filter_glossary-autolinker-min.js index 5d6f83b4b80..a895bd0664e 100644 --- a/filter/glossary/yui/build/moodle-filter_glossary-autolinker/moodle-filter_glossary-autolinker-min.js +++ b/filter/glossary/yui/build/moodle-filter_glossary-autolinker/moodle-filter_glossary-autolinker-min.js @@ -1 +1 @@ -YUI.add("moodle-filter_glossary-autolinker",function(e,t){var n="Glossary filter autolinker",r="width",i="height",s="menubar",o="location",u="scrollbars",a="resizable",f="toolbar",l="status",c="directories",h="fullscreen",p="dependent",d;d=function(){d.superclass.constructor.apply(this,arguments)},e.extend(d,e.Base,{overlay:null,initializer:function(){var t=this;e.delegate("click",function(n){n.preventDefault();var r="",i=e.Node.create('
'),s=new e.Overlay({headerContent:r,bodyContent:i}),o,u;t.overlay=s,s.render(e.one(document.body)),o=this.getAttribute("href").replace("showentry.php","showentry_ajax.php"),u={method:"get",context:t,on:{success:function(e,t){this.display_callback(t.responseText)},failure:function(e,t){var n=t.statusText;M.cfg.developerdebug&&(t.statusText+=" ("+o+")"),this.display_callback("bodyContent",n)}}},e.io(o,u)},e.one(document.body),"a.glossary.autolink.concept")},display_callback:function(t){var n,r,i;try{n=e.JSON.parse(t);if(n.success){this.overlay.hide();for(r in n.entries)definition=n.entries[r].definition+n.entries[r].attachments,i=new M.core.alert({title:n.entries[r].concept,message:definition,modal:!1,yesLabel:M.util.get_string("ok","moodle")}),i.show(),e.Node.one("#id_yuialertconfirm-"+i.get("COUNT")).focus();return!0}n.error&&new M.core.ajaxException(n)}catch(s){new M.core.exception(s)}return!1}},{NAME:n,ATTRS:{url:{validator:e.Lang.isString,value:M.cfg.wwwroot+"/mod/glossary/showentry.php"},name:{validator:e.Lang.isString,value:"glossaryconcept"},options:{getter:function(){return{width:this.get(r),height:this.get(i),menubar:this.get(s),location:this.get(o),scrollbars:this.get(u),resizable:this.get(a),toolbar:this.get(f),status:this.get(l),directories:this.get(c),fullscreen:this.get(h),dependent:this.get(p)}},readOnly:!0},width:{value:600},height:{value:450},menubar:{value:!1},location:{value:!1},scrollbars:{value:!0},resizable:{value:!0},toolbar:{value:!0},status:{value:!0},directories:{value:!1},fullscreen:{value:!1},dependent:{value:!0}}}),M.filter_glossary=M.filter_glossary||{},M.filter_glossary.init_filter_autolinking=function(e){return new d(e)}},"@VERSION@",{requires:["base","node","io-base","json-parse","event-delegate","overlay","moodle-core-notification-alert"]}); +YUI.add("moodle-filter_glossary-autolinker",function(e,t){var n="Glossary filter autolinker",r="width",i="height",s="menubar",o="location",u="scrollbars",a="resizable",f="toolbar",l="status",c="directories",h="fullscreen",p="dependent",d;d=function(){d.superclass.constructor.apply(this,arguments)},e.extend(d,e.Base,{overlay:null,initializer:function(){var t=this;e.delegate("click",function(n){n.preventDefault();var r="",i=e.Node.create('
'),s=new e.Overlay({headerContent:r,bodyContent:i}),o,u;t.overlay=s,s.render(e.one(document.body)),o=this.getAttribute("href").replace("showentry.php","showentry_ajax.php"),u={method:"get",context:t,on:{success:function(e,t){this.display_callback(t.responseText)},failure:function(e,t){var n=t.statusText;M.cfg.developerdebug&&(t.statusText+=" ("+o+")"),this.display_callback("bodyContent",n)}}},e.io(o,u)},e.one(document.body),"a.glossary.autolink.concept")},display_callback:function(t){var n,r,i;try{n=e.JSON.parse(t);if(n.success){this.overlay.hide();for(r in n.entries)definition=n.entries[r].definition+n.entries[r].attachments,i=new M.core.alert({title:n.entries[r].concept,message:definition,modal:!1,yesLabel:M.util.get_string("ok","moodle")}),i.show(),e.fire(M.core.event.FILTER_CONTENT_UPDATED,{nodes:new e.NodeList(i.get("boundingBox"))}),e.Node.one("#id_yuialertconfirm-"+i.get("COUNT")).focus();return!0}n.error&&new M.core.ajaxException(n)}catch(s){new M.core.exception(s)}return!1}},{NAME:n,ATTRS:{url:{validator:e.Lang.isString,value:M.cfg.wwwroot+"/mod/glossary/showentry.php"},name:{validator:e.Lang.isString,value:"glossaryconcept"},options:{getter:function(){return{width:this.get(r),height:this.get(i),menubar:this.get(s),location:this.get(o),scrollbars:this.get(u),resizable:this.get(a),toolbar:this.get(f),status:this.get(l),directories:this.get(c),fullscreen:this.get(h),dependent:this.get(p)}},readOnly:!0},width:{value:600},height:{value:450},menubar:{value:!1},location:{value:!1},scrollbars:{value:!0},resizable:{value:!0},toolbar:{value:!0},status:{value:!0},directories:{value:!1},fullscreen:{value:!1},dependent:{value:!0}}}),M.filter_glossary=M.filter_glossary||{},M.filter_glossary.init_filter_autolinking=function(e){return new d(e)}},"@VERSION@",{requires:["base","node","io-base","json-parse","event-delegate","overlay","moodle-core-event","moodle-core-notification-alert"]}); diff --git a/filter/glossary/yui/build/moodle-filter_glossary-autolinker/moodle-filter_glossary-autolinker.js b/filter/glossary/yui/build/moodle-filter_glossary-autolinker/moodle-filter_glossary-autolinker.js index 0a1867900fa..92b7fe50f2c 100644 --- a/filter/glossary/yui/build/moodle-filter_glossary-autolinker/moodle-filter_glossary-autolinker.js +++ b/filter/glossary/yui/build/moodle-filter_glossary-autolinker/moodle-filter_glossary-autolinker.js @@ -72,6 +72,8 @@ Y.extend(AUTOLINKER, Y.Base, { alertpanel = new M.core.alert({title:data.entries[key].concept, message:definition, modal:false, yesLabel: M.util.get_string('ok', 'moodle')}); alertpanel.show(); + Y.fire(M.core.event.FILTER_CONTENT_UPDATED, {nodes: (new Y.NodeList(alertpanel.get('boundingBox')))}); + Y.Node.one('#id_yuialertconfirm-' + alertpanel.get('COUNT')).focus(); } @@ -141,6 +143,7 @@ M.filter_glossary.init_filter_autolinking = function(config) { "json-parse", "event-delegate", "overlay", + "moodle-core-event", "moodle-core-notification-alert" ] }); diff --git a/filter/glossary/yui/src/autolinker/js/autolinker.js b/filter/glossary/yui/src/autolinker/js/autolinker.js index 3217f4a2a10..8879603b88c 100644 --- a/filter/glossary/yui/src/autolinker/js/autolinker.js +++ b/filter/glossary/yui/src/autolinker/js/autolinker.js @@ -70,6 +70,8 @@ Y.extend(AUTOLINKER, Y.Base, { alertpanel = new M.core.alert({title:data.entries[key].concept, message:definition, modal:false, yesLabel: M.util.get_string('ok', 'moodle')}); alertpanel.show(); + Y.fire(M.core.event.FILTER_CONTENT_UPDATED, {nodes: (new Y.NodeList(alertpanel.get('boundingBox')))}); + Y.Node.one('#id_yuialertconfirm-' + alertpanel.get('COUNT')).focus(); } diff --git a/filter/glossary/yui/src/autolinker/meta/autolinker.json b/filter/glossary/yui/src/autolinker/meta/autolinker.json index ac4042ed378..065cb2eea78 100644 --- a/filter/glossary/yui/src/autolinker/meta/autolinker.json +++ b/filter/glossary/yui/src/autolinker/meta/autolinker.json @@ -7,6 +7,7 @@ "json-parse", "event-delegate", "overlay", + "moodle-core-event", "moodle-core-notification-alert" ] } diff --git a/filter/mathjaxloader/filter.php b/filter/mathjaxloader/filter.php new file mode 100644 index 00000000000..a22e5bf130d --- /dev/null +++ b/filter/mathjaxloader/filter.php @@ -0,0 +1,171 @@ +. + +/** + * This filter provides automatic support for MathJax + * + * @package filter_mathjaxloader + * @copyright 2013 Damyon Wiese (damyon@moodle.com) + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die(); + +/** + * Mathjax filtering + */ +class filter_mathjaxloader extends moodle_text_filter { + + /* + * Perform a mapping of the moodle language code to the equivalent for MathJax. + * + * @param string $moodlelangcode - The moodle language code - e.g. en_pirate + * @return string The MathJax language code. + */ + public function map_language_code($moodlelangcode) { + $mathjaxlangcodes = array('br', + 'cdo', + 'cs', + 'da', + 'de', + 'en', + 'eo', + 'es', + 'fa', + 'fi', + 'fr', + 'gl', + 'he', + 'ia', + 'it', + 'ja', + 'ko', + 'lb', + 'mk', + 'nl', + 'oc', + 'pl', + 'pt', + 'pt-br', + 'ru', + 'sl', + 'sv', + 'tr', + 'uk', + 'zh-hans'); + $exceptions = array('cz' => 'cs'); + + // First see if this is an exception. + if (isset($exceptions[$moodlelangcode])) { + $moodlelangcode = $exceptions[$moodlelangcode]; + } + + // Now look for an exact lang string match. + if (in_array($moodlelangcode, $mathjaxlangcodes)) { + return $moodlelangcode; + } + + // Now try shortening the moodle lang string. + $moodlelangcode = preg_replace('/-.*/', '', $moodlelangcode); + // Look for a match on the shortened string. + if (in_array($moodlelangcode, $mathjaxlangcodes)) { + return $moodlelangcode; + } + // All failed - use english. + return 'en'; + } + + /* + * Add the javascript to enable mathjax processing on this page. + * + * @param moodle_page $page The current page. + * @param context $context The current context. + */ + public function setup($page, $context) { + global $CFG; + // This only requires execution once per request. + static $jsinitialised = false; + + if (empty($jsinitialised)) { + if (strpos($CFG->httpswwwroot, 'https:') === 0) { + $url = get_config('filter_mathjaxloader', 'httpsurl'); + } else { + $url = get_config('filter_mathjaxloader', 'httpurl'); + } + $lang = $this->map_language_code(current_language()); + $url = new moodle_url($url, array('delayStartupUntil' => 'configured')); + + $moduleconfig = array( + 'name' => 'mathjax', + 'fullpath' => $url + ); + + $page->requires->js_module($moduleconfig); + + $config = get_config('filter_mathjaxloader', 'mathjaxconfig'); + + $params = array('mathjaxconfig' => $config, 'lang' => $lang); + + $page->requires->yui_module('moodle-filter_mathjaxloader-loader', 'M.filter_mathjaxloader.configure', array($params)); + + $jsinitialised = true; + } + } + + /* + * This function wraps the filtered text in a span, that mathjaxloader is configured to process. + * + * @param string $text The text to filter. + * @param array $options The filter options. + */ + public function filter($text, array $options = array()) { + global $PAGE; + + $legacy = get_config('filter_mathjaxloader', 'texfiltercompatibility'); + $extradelimiters = explode(',', get_config('filter_mathjaxloader', 'additionaldelimiters')); + if ($legacy) { + // This replaces any of the tex filter maths delimiters with the default for inline maths in MathJAX "\( blah \)". + // E.g. " blah ". + $text = preg_replace('|<(/?) *tex( [^>]*)?>|u', '[\1tex]', $text); + // E.g. "[tex.*] blah [/tex]". + $text = str_replace('[tex]', '\\(', $text); + $text = str_replace('[/tex]', '\\)', $text); + // E.g. "$$ blah $$". + $text = preg_replace('|\$\$[\S\s]\$\$|u', '\\(\1\\)', $text); + // E.g. "\[ blah \]". + $text = str_replace('\\[', '\\(', $text); + $text = str_replace('\\]', '\\)', $text); + } + + $hasinline = strpos($text, '\\(') !== false && strpos($text, '\\)') !== false; + $hasdisplay = (strpos($text, '$$') !== false) || + (strpos($text, '\\[') !== false && strpos($text, '\\]') !== false); + + $hasextra = false; + + foreach ($extradelimiters as $extra) { + if ($extra && strpos($text, $extra) !== false) { + $hasextra = true; + break; + } + } + if ($hasinline || $hasdisplay || $hasextra) { + $PAGE->requires->yui_module('moodle-filter_mathjaxloader-loader', 'M.filter_mathjaxloader.typeset'); + return '' . $text . ''; + } + return $text; + } +} diff --git a/filter/mathjaxloader/lang/en/filter_mathjaxloader.php b/filter/mathjaxloader/lang/en/filter_mathjaxloader.php new file mode 100644 index 00000000000..8ba35837d25 --- /dev/null +++ b/filter/mathjaxloader/lang/en/filter_mathjaxloader.php @@ -0,0 +1,45 @@ +. + +/** + * Strings for component 'filter_mathjaxloader', language 'en'. + * + * @package filter_mathjaxloader + * @copyright 1999 onwards Martin Dougiamas {@link http://moodle.com} + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +$string['filtername'] = 'MathJax'; +$string['additionaldelimiters'] = 'Additional equation delimiters'; +$string['additionaldelimiters_help'] = 'MathJax filter parses text for equations contained within delimiter characters. + +The list of recognised delimiter characters can be added to here (e.g. AsciiMath uses `). Delimiters can contain multiple characters and multiple delimiters can be separated with commas.'; +$string['httpurl'] = 'HTTP MathJax URL'; +$string['httpurl_help'] = 'Full URL to MathJax library. Used when the page is loaded via http.'; +$string['httpsurl'] = 'HTTPS MathJax URL'; +$string['httpsurl_help'] = 'Full URL to MathJax library. Used when the page is loaded via https (secure). '; +$string['texfiltercompatibility'] = 'Tex filter compatibility'; +$string['texfiltercompatibility_help'] = 'The MathJax filter can be used as a replacement for the Tex filter. + +To support all the delimiters supported by the Tex filter MathJax will be configured to display all equations "inline" with the tex.'; +$string['localinstall'] = 'Local MathJax installation'; +$string['localinstall_help'] = 'The default MathJAX configuration uses the CDN version of MathJAX, but MathJAX can be installed locally if required. + +Some reasons this might be useful are to save on bandwidth - or because of local proxy restrictions. + +To use a local installation of MathJAX, first download the full MathJax library from http://www.mathjax.org/. Then install it on a web server. Finally update the MathJax filter settings httpurl and/or httpsurl to point to the local MathJax.js url.'; +$string['mathjaxsettings'] = 'MathJax configuration'; +$string['mathjaxsettings_desc'] = 'The default MathJAX configuration should be appropriate for most users, but MathJax is highly configurable and any of the standard MathJax configuration options can be added here.'; diff --git a/filter/mathjaxloader/readme_moodle.txt b/filter/mathjaxloader/readme_moodle.txt new file mode 100644 index 00000000000..a46c89569c7 --- /dev/null +++ b/filter/mathjaxloader/readme_moodle.txt @@ -0,0 +1,15 @@ +Description of MathJAX library integration in Moodle +========================================================================================= + +License: Apache 2.0 +Source: http://www.mathjax.org + +Moodle maintainer: Damyon Wiese + +========================================================================================= +This library is not shipped with Moodle, but this filter is provided, which can be used to +correctly load MathJax into a page from the CDN. Alternatively you can download the entire +library and install it locally, then use this filter to load that local version. + +The only changes required to this filter to handle different MathJax versions is to update +the default CDN urls in settings.php - and update the list of language mappings - in filter.php. diff --git a/filter/mathjaxloader/settings.php b/filter/mathjaxloader/settings.php new file mode 100644 index 00000000000..deeb9751e1f --- /dev/null +++ b/filter/mathjaxloader/settings.php @@ -0,0 +1,86 @@ +. + +/** + * MathJAX filter settings + * + * @package filter_mathjaxloader + * @copyright 2014 Damyon Wiese + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die; + +if ($ADMIN->fulltree) { + $item = new admin_setting_heading('filter_mathjaxloader/localinstall', + new lang_string('localinstall', 'filter_mathjaxloader'), + new lang_string('localinstall_help', 'filter_mathjaxloader')); + $settings->add($item); + + $item = new admin_setting_configtext('filter_mathjaxloader/httpurl', + new lang_string('httpurl', 'filter_mathjaxloader'), + new lang_string('httpurl_help', 'filter_mathjaxloader'), + 'http://cdn.mathjax.org/mathjax/2.3-latest/MathJax.js', + PARAM_RAW); + $settings->add($item); + + $item = new admin_setting_configtext('filter_mathjaxloader/httpsurl', + new lang_string('httpsurl', 'filter_mathjaxloader'), + new lang_string('httpsurl_help', 'filter_mathjaxloader'), + 'https://c328740.ssl.cf1.rackcdn.com/mathjax/2.3-latest/MathJax.js', + PARAM_RAW); + $settings->add($item); + + $item = new admin_setting_configcheckbox('filter_mathjaxloader/texfiltercompatibility', + new lang_string('texfiltercompatibility', 'filter_mathjaxloader'), + new lang_string('texfiltercompatibility_help', 'filter_mathjaxloader'), + 0); + $settings->add($item); + + $default = ' +MathJax.Hub.Config({ + config: ["MMLorHTML.js", "Safe.js"], + jax: ["input/TeX","input/MathML","output/HTML-CSS","output/NativeMML"], + extensions: ["tex2jax.js","mml2jax.js","MathMenu.js","MathZoom.js"], + TeX: { + extensions: ["AMSmath.js","AMSsymbols.js","noErrors.js","noUndefined.js"] + }, + menuSettings: { + zoom: "Double-Click", + mpContext: true, + mpMouse: true + }, + errorSettings: { message: ["!"] }, + skipStartupTypeset: true, + messageStyle: "none" +}); +'; + + $item = new admin_setting_configtextarea('filter_mathjaxloader/mathjaxconfig', + new lang_string('mathjaxsettings','filter_mathjaxloader'), + new lang_string('mathjaxsettings_desc', 'filter_mathjaxloader'), + $default); + + $settings->add($item); + + $item = new admin_setting_configtext('filter_mathjaxloader/additionaldelimiters', + new lang_string('additionaldelimiters', 'filter_mathjaxloader'), + new lang_string('additionaldelimiters_help', 'filter_mathjaxloader'), + '', + PARAM_RAW); + $settings->add($item); + +} diff --git a/filter/mathjaxloader/styles.css b/filter/mathjaxloader/styles.css new file mode 100644 index 00000000000..f09529267e9 --- /dev/null +++ b/filter/mathjaxloader/styles.css @@ -0,0 +1,3 @@ +.jsenabled #MathJax_ZoomFrame { + position: absolute; +} diff --git a/filter/mathjaxloader/version.php b/filter/mathjaxloader/version.php new file mode 100644 index 00000000000..666c4b8572c --- /dev/null +++ b/filter/mathjaxloader/version.php @@ -0,0 +1,29 @@ +. + +/** + * MathJax filter version information + * + * @package filter_mathjaxloader + * @copyright 2014 Damyon Wiese (damyon@moodle.com) + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die(); + +$plugin->version = 2013110500; +$plugin->requires = 2013110500; // Requires this Moodle version +$plugin->component= 'filter_mathjaxloader'; diff --git a/filter/mathjaxloader/yui/build/moodle-filter_mathjax-loader/moodle-filter_mathjax-loader-debug.js b/filter/mathjaxloader/yui/build/moodle-filter_mathjax-loader/moodle-filter_mathjax-loader-debug.js new file mode 100644 index 00000000000..a2df1208ebf --- /dev/null +++ b/filter/mathjaxloader/yui/build/moodle-filter_mathjax-loader/moodle-filter_mathjax-loader-debug.js @@ -0,0 +1,46 @@ +YUI.add('moodle-filter_mathjax-loader', 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 . + +/** + * Mathjax JS Loader. + * + * @package filter_mathjax + * @copyright 2014 Damyon Wiese + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +M.filter_mathjax = M.filter_mathjax || { + init : function(params) { + // Add an inline configuration tag to the head. + var config = Y.Node.create(''); + config.setHTML(params.mathjaxconfig); + Y.one('head').append(config); + // Load the mathjax library. + Y.Get.js(params.mathjaxurl, function() { + // Tell mathjax to call back for the language files. + debugger; + MathJax.Localization.addTranslation(params.lang,null,{ + menuTitle: params.langdescription, + directory: M.cfg.wwwroot + "/filter/mathjax/language.php?lang=" + }); + }); + + + } +}; + + +}, '@VERSION@', {"requires": ["get", "node"]}); diff --git a/filter/mathjaxloader/yui/build/moodle-filter_mathjax-loader/moodle-filter_mathjax-loader-min.js b/filter/mathjaxloader/yui/build/moodle-filter_mathjax-loader/moodle-filter_mathjax-loader-min.js new file mode 100644 index 00000000000..6970a0f8d52 --- /dev/null +++ b/filter/mathjaxloader/yui/build/moodle-filter_mathjax-loader/moodle-filter_mathjax-loader-min.js @@ -0,0 +1 @@ +YUI.add("moodle-filter_mathjax-loader",function(e,t){M.filter_mathjax=M.filter_mathjax||{init:function(t){var n=e.Node.create('');n.setHTML(t.mathjaxconfig),e.one("head").append(n),e.Get.js(t.mathjaxurl,function(){debugger;MathJax.Localization.addTranslation(t.lang,null,{menuTitle:t.langdescription,directory:M.cfg.wwwroot+"/filter/mathjax/language.php?lang="})})}}},"@VERSION@",{requires:["get","node"]}); diff --git a/filter/mathjaxloader/yui/build/moodle-filter_mathjax-loader/moodle-filter_mathjax-loader.js b/filter/mathjaxloader/yui/build/moodle-filter_mathjax-loader/moodle-filter_mathjax-loader.js new file mode 100644 index 00000000000..a2df1208ebf --- /dev/null +++ b/filter/mathjaxloader/yui/build/moodle-filter_mathjax-loader/moodle-filter_mathjax-loader.js @@ -0,0 +1,46 @@ +YUI.add('moodle-filter_mathjax-loader', 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 . + +/** + * Mathjax JS Loader. + * + * @package filter_mathjax + * @copyright 2014 Damyon Wiese + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +M.filter_mathjax = M.filter_mathjax || { + init : function(params) { + // Add an inline configuration tag to the head. + var config = Y.Node.create(''); + config.setHTML(params.mathjaxconfig); + Y.one('head').append(config); + // Load the mathjax library. + Y.Get.js(params.mathjaxurl, function() { + // Tell mathjax to call back for the language files. + debugger; + MathJax.Localization.addTranslation(params.lang,null,{ + menuTitle: params.langdescription, + directory: M.cfg.wwwroot + "/filter/mathjax/language.php?lang=" + }); + }); + + + } +}; + + +}, '@VERSION@', {"requires": ["get", "node"]}); diff --git a/filter/mathjaxloader/yui/build/moodle-filter_mathjaxloader-loader/moodle-filter_mathjaxloader-loader-debug.js b/filter/mathjaxloader/yui/build/moodle-filter_mathjaxloader-loader/moodle-filter_mathjaxloader-loader-debug.js new file mode 100644 index 00000000000..15f0c756eea --- /dev/null +++ b/filter/mathjaxloader/yui/build/moodle-filter_mathjaxloader-loader/moodle-filter_mathjaxloader-loader-debug.js @@ -0,0 +1,120 @@ +YUI.add('moodle-filter_mathjaxloader-loader', 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 . + +/** + * Mathjax JS Loader. + * + * @package filter_mathjaxloader + * @copyright 2014 Damyon Wiese + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +M.filter_mathjaxloader = M.filter_mathjaxloader || { + + /** + * The users current language - this can't be set until MathJax is loaded - so we need to store it. + * @property _lang + * @type String + * @default '' + * @private + */ + _lang: '', + + /** + * Boolean used to prevent configuring MathJax twice. + * @property _configured + * @type Boolean + * @default '' + * @private + */ + _configured: false, + + /** + * Called by the filter when it is active on any page. + * This does not load MathJAX yet - it addes the configuration to the head incase it gets loaded later. + * It also subscribes to the filter-content-updated event so MathJax can respond to content loaded by Ajax. + * + * @method typeset + * @param {Object} params List of configuration params containing mathjaxconfig (text) and lang + */ + configure: function(params) { + + // Add a js configuration object to the head. + // See "http://docs.mathjax.org/en/latest/dynamic.html#ajax-mathjax" + var script = document.createElement("script"); + script.type = "text/x-mathjax-config"; + script[(window.opera ? "innerHTML" : "text")] = params.mathjaxconfig; + document.getElementsByTagName("head")[0].appendChild(script); + + // Save the lang config until MathJax is actually loaded. + this._lang = params.lang; + + // Listen for events triggered when new text is added to a page that needs + // processing by a filter. + Y.on(M.core.event.FILTER_CONTENT_UPDATED, this.contentUpdated, this); + }, + + /** + * Set the correct language for the MathJax menus. Only do this once. + * + * @method setLocale + * @private + */ + _setLocale: function() { + if (!this._configured) { + MathJax.Localization.setLocale(this._lang); + MathJax.Hub.Configured(); + this._configured = true; + } + }, + + /** + * Called by the filter when an equation is found while rendering the page. + * + * @method typeset + */ + typeset: function() { + if (!this._configured) { + var self = this; + Y.use('mathjax', function() { + self._setLocale(); + Y.all('.filter_mathjaxloader_equation').each(function(node) { + MathJax.Hub.Queue(["Typeset", MathJax.Hub, node.getDOMNode()]); + }); + }); + } + }, + + /** + * Handle content updated events - typeset the new content. + * @method contentUpdated + * @param Y.Event - Custom event with "nodes" indicating the root of the updated nodes. + */ + contentUpdated: function(event) { + var self = this; + Y.use('mathjax', function() { + self._setLocale(); + event.nodes.each(function (node) { + node.all('.filter_mathjaxloader_equation').each(function(node) { + MathJax.Hub.Queue(["Typeset", MathJax.Hub, node.getDOMNode()]); + }); + }); + }); + } +}; + + +}, '@VERSION@', {"requires": ["moodle-core-event"]}); diff --git a/filter/mathjaxloader/yui/build/moodle-filter_mathjaxloader-loader/moodle-filter_mathjaxloader-loader-min.js b/filter/mathjaxloader/yui/build/moodle-filter_mathjaxloader-loader/moodle-filter_mathjaxloader-loader-min.js new file mode 100644 index 00000000000..a5cd7f1a411 --- /dev/null +++ b/filter/mathjaxloader/yui/build/moodle-filter_mathjaxloader-loader/moodle-filter_mathjaxloader-loader-min.js @@ -0,0 +1 @@ +YUI.add("moodle-filter_mathjaxloader-loader",function(e,t){M.filter_mathjaxloader=M.filter_mathjaxloader||{_lang:"",_configured:!1,configure:function(t){var n=document.createElement("script");n.type="text/x-mathjax-config",n[window.opera?"innerHTML":"text"]=t.mathjaxconfig,document.getElementsByTagName("head")[0].appendChild(n),this._lang=t.lang,e.on(M.core.event.FILTER_CONTENT_UPDATED,this.contentUpdated,this)},_setLocale:function(){this._configured||(MathJax.Localization.setLocale(this._lang),MathJax.Hub.Configured(),this._configured=!0)},typeset:function(){if(!this._configured){var t=this;e.use("mathjax",function(){t._setLocale(),e.all(".filter_mathjaxloader_equation").each(function(e){MathJax.Hub.Queue(["Typeset",MathJax.Hub,e.getDOMNode()])})})}},contentUpdated:function(t){var n=this;e.use("mathjax",function(){n._setLocale(),t.nodes.each(function(e){e.all(".filter_mathjaxloader_equation").each(function(e){MathJax.Hub.Queue(["Typeset",MathJax.Hub,e.getDOMNode()])})})})}}},"@VERSION@",{requires:["moodle-core-event"]}); diff --git a/filter/mathjaxloader/yui/build/moodle-filter_mathjaxloader-loader/moodle-filter_mathjaxloader-loader.js b/filter/mathjaxloader/yui/build/moodle-filter_mathjaxloader-loader/moodle-filter_mathjaxloader-loader.js new file mode 100644 index 00000000000..15f0c756eea --- /dev/null +++ b/filter/mathjaxloader/yui/build/moodle-filter_mathjaxloader-loader/moodle-filter_mathjaxloader-loader.js @@ -0,0 +1,120 @@ +YUI.add('moodle-filter_mathjaxloader-loader', 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 . + +/** + * Mathjax JS Loader. + * + * @package filter_mathjaxloader + * @copyright 2014 Damyon Wiese + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +M.filter_mathjaxloader = M.filter_mathjaxloader || { + + /** + * The users current language - this can't be set until MathJax is loaded - so we need to store it. + * @property _lang + * @type String + * @default '' + * @private + */ + _lang: '', + + /** + * Boolean used to prevent configuring MathJax twice. + * @property _configured + * @type Boolean + * @default '' + * @private + */ + _configured: false, + + /** + * Called by the filter when it is active on any page. + * This does not load MathJAX yet - it addes the configuration to the head incase it gets loaded later. + * It also subscribes to the filter-content-updated event so MathJax can respond to content loaded by Ajax. + * + * @method typeset + * @param {Object} params List of configuration params containing mathjaxconfig (text) and lang + */ + configure: function(params) { + + // Add a js configuration object to the head. + // See "http://docs.mathjax.org/en/latest/dynamic.html#ajax-mathjax" + var script = document.createElement("script"); + script.type = "text/x-mathjax-config"; + script[(window.opera ? "innerHTML" : "text")] = params.mathjaxconfig; + document.getElementsByTagName("head")[0].appendChild(script); + + // Save the lang config until MathJax is actually loaded. + this._lang = params.lang; + + // Listen for events triggered when new text is added to a page that needs + // processing by a filter. + Y.on(M.core.event.FILTER_CONTENT_UPDATED, this.contentUpdated, this); + }, + + /** + * Set the correct language for the MathJax menus. Only do this once. + * + * @method setLocale + * @private + */ + _setLocale: function() { + if (!this._configured) { + MathJax.Localization.setLocale(this._lang); + MathJax.Hub.Configured(); + this._configured = true; + } + }, + + /** + * Called by the filter when an equation is found while rendering the page. + * + * @method typeset + */ + typeset: function() { + if (!this._configured) { + var self = this; + Y.use('mathjax', function() { + self._setLocale(); + Y.all('.filter_mathjaxloader_equation').each(function(node) { + MathJax.Hub.Queue(["Typeset", MathJax.Hub, node.getDOMNode()]); + }); + }); + } + }, + + /** + * Handle content updated events - typeset the new content. + * @method contentUpdated + * @param Y.Event - Custom event with "nodes" indicating the root of the updated nodes. + */ + contentUpdated: function(event) { + var self = this; + Y.use('mathjax', function() { + self._setLocale(); + event.nodes.each(function (node) { + node.all('.filter_mathjaxloader_equation').each(function(node) { + MathJax.Hub.Queue(["Typeset", MathJax.Hub, node.getDOMNode()]); + }); + }); + }); + } +}; + + +}, '@VERSION@', {"requires": ["moodle-core-event"]}); diff --git a/filter/mathjaxloader/yui/src/loader/build.json b/filter/mathjaxloader/yui/src/loader/build.json new file mode 100644 index 00000000000..b262702bd16 --- /dev/null +++ b/filter/mathjaxloader/yui/src/loader/build.json @@ -0,0 +1,10 @@ +{ + "name": "moodle-filter_mathjaxloader-loader", + "builds": { + "moodle-filter_mathjaxloader-loader": { + "jsfiles": [ + "loader.js" + ] + } + } +} diff --git a/filter/mathjaxloader/yui/src/loader/js/loader.js b/filter/mathjaxloader/yui/src/loader/js/loader.js new file mode 100644 index 00000000000..fb7fe1be167 --- /dev/null +++ b/filter/mathjaxloader/yui/src/loader/js/loader.js @@ -0,0 +1,115 @@ +// 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 . + +/** + * Mathjax JS Loader. + * + * @package filter_mathjaxloader + * @copyright 2014 Damyon Wiese + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +M.filter_mathjaxloader = M.filter_mathjaxloader || { + + /** + * The users current language - this can't be set until MathJax is loaded - so we need to store it. + * @property _lang + * @type String + * @default '' + * @private + */ + _lang: '', + + /** + * Boolean used to prevent configuring MathJax twice. + * @property _configured + * @type Boolean + * @default '' + * @private + */ + _configured: false, + + /** + * Called by the filter when it is active on any page. + * This does not load MathJAX yet - it addes the configuration to the head incase it gets loaded later. + * It also subscribes to the filter-content-updated event so MathJax can respond to content loaded by Ajax. + * + * @method typeset + * @param {Object} params List of configuration params containing mathjaxconfig (text) and lang + */ + configure: function(params) { + + // Add a js configuration object to the head. + // See "http://docs.mathjax.org/en/latest/dynamic.html#ajax-mathjax" + var script = document.createElement("script"); + script.type = "text/x-mathjax-config"; + script[(window.opera ? "innerHTML" : "text")] = params.mathjaxconfig; + document.getElementsByTagName("head")[0].appendChild(script); + + // Save the lang config until MathJax is actually loaded. + this._lang = params.lang; + + // Listen for events triggered when new text is added to a page that needs + // processing by a filter. + Y.on(M.core.event.FILTER_CONTENT_UPDATED, this.contentUpdated, this); + }, + + /** + * Set the correct language for the MathJax menus. Only do this once. + * + * @method setLocale + * @private + */ + _setLocale: function() { + if (!this._configured) { + MathJax.Localization.setLocale(this._lang); + MathJax.Hub.Configured(); + this._configured = true; + } + }, + + /** + * Called by the filter when an equation is found while rendering the page. + * + * @method typeset + */ + typeset: function() { + if (!this._configured) { + var self = this; + Y.use('mathjax', function() { + self._setLocale(); + Y.all('.filter_mathjaxloader_equation').each(function(node) { + MathJax.Hub.Queue(["Typeset", MathJax.Hub, node.getDOMNode()]); + }); + }); + } + }, + + /** + * Handle content updated events - typeset the new content. + * @method contentUpdated + * @param Y.Event - Custom event with "nodes" indicating the root of the updated nodes. + */ + contentUpdated: function(event) { + var self = this; + Y.use('mathjax', function() { + self._setLocale(); + event.nodes.each(function (node) { + node.all('.filter_mathjaxloader_equation').each(function(node) { + MathJax.Hub.Queue(["Typeset", MathJax.Hub, node.getDOMNode()]); + }); + }); + }); + } +}; diff --git a/filter/mathjaxloader/yui/src/loader/meta/loader.json b/filter/mathjaxloader/yui/src/loader/meta/loader.json new file mode 100644 index 00000000000..eaca4224906 --- /dev/null +++ b/filter/mathjaxloader/yui/src/loader/meta/loader.json @@ -0,0 +1,7 @@ +{ + "moodle-filter_mathjaxloader-loader": { + "requires": [ + "moodle-core-event" + ] + } +} diff --git a/lib/classes/plugin_manager.php b/lib/classes/plugin_manager.php index d06464b6435..580265a2c9b 100644 --- a/lib/classes/plugin_manager.php +++ b/lib/classes/plugin_manager.php @@ -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' ), diff --git a/lib/editor/atto/plugins/equation/lib.php b/lib/editor/atto/plugins/equation/lib.php index 3e2816ed13f..87865f27b99 100644 --- a/lib/editor/atto/plugins/equation/lib.php +++ b/lib/editor/atto/plugins/equation/lib.php @@ -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')); } diff --git a/lib/editor/atto/plugins/equation/settings.php b/lib/editor/atto/plugins/equation/settings.php index 9aec16a2f7c..11d75bcf52a 100644 --- a/lib/editor/atto/plugins/equation/settings.php +++ b/lib/editor/atto/plugins/equation/settings.php @@ -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); + } 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 index 00abb9c9ce2..77ae2888560 100644 --- 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 @@ -48,6 +48,10 @@ var COMPONENTNAME = 'atto_equation', SUBMIT: '.' + CSS.SUBMIT, LIBRARY_BUTTON: '.' + CSS.LIBRARY + ' button' }, + DELIMITERS = { + START: '\\(', + END: '\\)' + }, TEMPLATES = { FORM: '' + '
' + @@ -72,7 +76,7 @@ var COMPONENTNAME = 'atto_equation', '{{#each library}}' + '
' + '{{#split "\n" elements}}' + - '' + + '' + '{{/split}}' + '
' + '{{/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('' + DELIMITERS.START + ' ' + texNode.get('text') + ' ' + DELIMITERS.END + ''); + 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"]}); 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 index c043250ead3..85204f1668f 100644 --- 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 @@ -1 +1,2 @@ -YUI.add("moodle-atto_equation-button",function(e,t){var n="atto_equation",r={EQUATION_TEXT:"atto_equation_equation",EQUATION_PREVIEW:"atto_equation_preview",SUBMIT:"atto_equation_submit",LIBRARY:"atto_equation_library",LIBRARY_GROUP_PREFIX:"atto_equation_library"},i={LIBRARY_GROUP_PREFIX:"."+r.LIBRARY_GROUP_PREFIX,EQUATION_TEXT:"."+r.EQUATION_TEXT,EQUATION_PREVIEW:"."+r.EQUATION_PREVIEW,SUBMIT:"."+r.SUBMIT,LIBRARY_BUTTON:"."+r.LIBRARY+" button"},s={FORM:'{{{library}}}

',LIBRARY:'
{{#each library}}
{{#split "\n" elements}}{{/split}}
{{/each}}
'};e.namespace("M.atto_equation").Button=e.Base.create("button",e.M.editor_atto.EditorPlugin,[],{_currentSelection:null,_lastCursorPos:0,_content:null,initializer:function(){this.get("texfilteractive")&&(this.addButton({icon:"e/math",callback:this._displayDialogue}),this.get("host").on("atto:selectionchanged",function(){this._resolveEquation()?this.highlightButtons():this.unHighlightButtons()},this))},_displayDialogue:function(){this._currentSelection=this.get("host").getSelection();if(this._currentSelection===!1)return;var t=this.getDialogue({headerContent:M.util.get_string("pluginname",n),focusAfterHide:!0,width:600}),r=this._getDialogueContent();t.set("bodyContent",r);var s=r.one(i.LIBRARY_GROUP_PREFIX),o=new e.TabView({srcNode:s});o.render(),t.show();var u=this._resolveEquation();u&&r.one(i.EQUATION_TEXT).set("text",u),this._updatePreview(!1)},_resolveEquation:function(){var t=this.get("host").getSelectionParentNode(),n,r;return t?(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),r):!1):!1},_setEquation:function(t){var n,r,i,s,o,u,a=this.get("host");t.preventDefault(),this.getDialogue({focusAfterHide:null}).hide(),n=t.currentTarget.ancestor(".atto_form").one("textarea"),u=n.get("value"),u!==""&&(a.setSelection(this._currentSelection),u="$$ "+u.trim()+" $$",r=e.one(a.getSelectionParentNode()),i=r.get("text"),s=/\$\$[\S\s]*\$\$/,o=s.exec(i),o&&o.length?(o=o.pop(),i=i.replace(o,"$$"+u+"$$"),r.set("text",i)):a.insertContentAtFocusPoint(u),this.markUpdated())},_updatePreview:function(t){var n=this._content.one(i.EQUATION_TEXT),r=n.get("value"),s,o,u=n.get("selectionStart"),a="",f="\\square ",l;t&&t.preventDefault(),u||(u=0);while(r.charAt(u)==="\\"&&u>0)u-=1;l=/[\w\{\}]/;while(l.test(r.charAt(u))&&u0)i=r.shift(),s+=n.fn(i);return s}),o=t({elementid:this.get("host").get("elementid"),component:n,library:i,CSS:r});var u=M.cfg.wwwroot+"/lib/editor/atto/plugins/equation/ajax.php",a={sesskey:M.cfg.sesskey,contextid:this.get("contextid"),action:"filtertext",text:o};return preview=e.io(u,{sync:!0,data:a,method:"POST"}),preview.status===200&&(o=preview.responseText),o}},{ATTRS:{texfilteractive:{value:!1},contextid:{value:null},library:{value:{}},texdocsurl:{value:null}}})},"@VERSION@",{requires:["moodle-editor_atto-plugin","io","event-valuechange","tabview"]}); +YUI.add("moodle-atto_equation-button",function(e,t){var n="atto_equation",r={EQUATION_TEXT:"atto_equation_equation",EQUATION_PREVIEW:"atto_equation_preview",SUBMIT:"atto_equation_submit",LIBRARY:"atto_equation_library",LIBRARY_GROUP_PREFIX:"atto_equation_library"},i={LIBRARY_GROUP_PREFIX:"."+r.LIBRARY_GROUP_PREFIX,EQUATION_TEXT:"."+r.EQUATION_TEXT,EQUATION_PREVIEW:"."+r.EQUATION_PREVIEW,SUBMIT:"."+r.SUBMIT,LIBRARY_BUTTON:"."+r.LIBRARY+" button"},s={START:"\\(",END:"\\)"},o={FORM:'
{{{library}}}

',LIBRARY:'
{{#each library}}
{{#split "\n" elements}}{{/split}}
{{/each}}
'};e.namespace("M.atto_equation").Button=e.Base.create("button",e.M.editor_atto.EditorPlugin,[],{_currentSelection:null,_lastCursorPos:0,_content:null,_sourceEquation:"",initializer:function(){this.get("texfilteractive")&&(this.addButton({icon:"e/math",callback:this._displayDialogue}),this.get("host").on("atto:selectionchanged",function(){this._resolveEquation()?this.highlightButtons():this.unHighlightButtons()},this),this.editor.all("tex").each(function(t){var n=e.Node.create(""+s.START+" "+t.get("text")+" "+s.END+"");t.replace(n)}))},_displayDialogue:function(){this._currentSelection=this.get("host").getSelection();if(this._currentSelection===!1)return;var t=this.getDialogue({headerContent:M.util.get_string("pluginname",n),focusAfterHide:!0,width:600}),r=this._getDialogueContent();t.set("bodyContent",r);var s=r.one(i.LIBRARY_GROUP_PREFIX),o=new e.TabView({srcNode:s});o.render(),t.show(),e.fire(M.core.event.FILTER_CONTENT_UPDATED,{nodes:new e.NodeList(t.get("boundingBox"))});var u=this._resolveEquation();u&&r.one(i.EQUATION_TEXT).set("text",u),this._updatePreview(!1)},_resolveEquation:function(){var t=this.get("host").getSelectionParentNode(),n,r,i=[],s;if(!t)return!1;n=e.one(t).get("text"),i.push(/\$\$([\S\s]*)\$\$/),i.push(/\\\(([\S\s]*)\\\)/),i.push(/\\\[([\S\s]*)\\\]/),i.push(/\[tex\]([\S\s]*)\[\/tex\]/);for(s=0;s0)a-=1;c=/[a-zA-Z\{\}]/;while(c.test(r.charAt(a))&&a0)i=r.shift().trim(),s+=n.fn(i);return s}),u=t({elementid:this.get("host").get("elementid"),component:n,library:i,CSS:r,DELIMITERS:s});var a=M.cfg.wwwroot+"/lib/editor/atto/plugins/equation/ajax.php",f={sesskey:M.cfg.sesskey,contextid +:this.get("contextid"),action:"filtertext",text:u};return preview=e.io(a,{sync:!0,data:f,method:"POST"}),preview.status===200&&(u=preview.responseText),u}},{ATTRS:{texfilteractive:{value:!1},contextid:{value:null},library:{value:{}},texdocsurl:{value:null}}})},"@VERSION@",{requires:["moodle-editor_atto-plugin","moodle-core-event","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 index 76df89c2ec8..1153695e2d1 100644 --- 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 @@ -48,6 +48,10 @@ var COMPONENTNAME = 'atto_equation', SUBMIT: '.' + CSS.SUBMIT, LIBRARY_BUTTON: '.' + CSS.LIBRARY + ' button' }, + DELIMITERS = { + START: '\\(', + END: '\\)' + }, TEMPLATES = { FORM: '' + '
' + @@ -72,7 +76,7 @@ var COMPONENTNAME = 'atto_equation', '{{#each library}}' + '
' + '{{#split "\n" elements}}' + - '' + + '' + '{{/split}}' + '
' + '{{/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('' + DELIMITERS.START + ' ' + texNode.get('text') + ' ' + DELIMITERS.END + ''); + 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"]}); 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 index a5af93df7ef..9d8185a181f 100644 --- a/lib/editor/atto/plugins/equation/yui/src/button/js/button.js +++ b/lib/editor/atto/plugins/equation/yui/src/button/js/button.js @@ -46,6 +46,10 @@ var COMPONENTNAME = 'atto_equation', SUBMIT: '.' + CSS.SUBMIT, LIBRARY_BUTTON: '.' + CSS.LIBRARY + ' button' }, + DELIMITERS = { + START: '\\(', + END: '\\)' + }, TEMPLATES = { FORM: '' + '' + @@ -70,7 +74,7 @@ var COMPONENTNAME = 'atto_equation', '{{#each library}}' + '
' + '{{#split "\n" elements}}' + - '' + + '' + '{{/split}}' + '
' + '{{/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('' + DELIMITERS.START + ' ' + texNode.get('text') + ' ' + DELIMITERS.END + ''); + 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 } + } }); 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 index c0d749fae02..6103cb338de 100644 --- a/lib/editor/atto/plugins/equation/yui/src/button/meta/button.json +++ b/lib/editor/atto/plugins/equation/yui/src/button/meta/button.json @@ -2,6 +2,7 @@ "moodle-atto_equation-button": { "requires": [ "moodle-editor_atto-plugin", + "moodle-core-event", "io", "event-valuechange", "tabview" diff --git a/lib/yui/build/moodle-core-event/moodle-core-event-debug.js b/lib/yui/build/moodle-core-event/moodle-core-event-debug.js new file mode 100644 index 00000000000..9bb34797b66 --- /dev/null +++ b/lib/yui/build/moodle-core-event/moodle-core-event-debug.js @@ -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 . + +/** + * @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"]}); diff --git a/lib/yui/build/moodle-core-event/moodle-core-event-min.js b/lib/yui/build/moodle-core-event/moodle-core-event-min.js new file mode 100644 index 00000000000..b0168680ce5 --- /dev/null +++ b/lib/yui/build/moodle-core-event/moodle-core-event-min.js @@ -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"]}); diff --git a/lib/yui/build/moodle-core-event/moodle-core-event.js b/lib/yui/build/moodle-core-event/moodle-core-event.js new file mode 100644 index 00000000000..d2c481f2c0e --- /dev/null +++ b/lib/yui/build/moodle-core-event/moodle-core-event.js @@ -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 . + +/** + * @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"]}); diff --git a/lib/yui/src/event/build.json b/lib/yui/src/event/build.json new file mode 100644 index 00000000000..62712457527 --- /dev/null +++ b/lib/yui/src/event/build.json @@ -0,0 +1,10 @@ +{ + "name": "moodle-core-event", + "builds": { + "moodle-core-event": { + "jsfiles": [ + "event.js" + ] + } + } +} diff --git a/lib/yui/src/event/js/event.js b/lib/yui/src/event/js/event.js new file mode 100644 index 00000000000..39fe4f6c862 --- /dev/null +++ b/lib/yui/src/event/js/event.js @@ -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 . + +/** + * @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. diff --git a/lib/yui/src/event/meta/event.json b/lib/yui/src/event/meta/event.json new file mode 100644 index 00000000000..89a7d388714 --- /dev/null +++ b/lib/yui/src/event/meta/event.json @@ -0,0 +1,7 @@ +{ + "moodle-core-event": { + "requires": [ + "event-custom" + ] + } +}