diff --git a/unpacked/extensions/MathEvents.js b/unpacked/extensions/MathEvents.js new file mode 100644 index 000000000..ff6d316c0 --- /dev/null +++ b/unpacked/extensions/MathEvents.js @@ -0,0 +1,571 @@ +/* -*- Mode: Javascript; indent-tabs-mode:nil; js-indent-level: 2 -*- */ +/* vim: set ts=2 et sw=2 tw=80: */ + +/************************************************************* + * + * MathJax/extensions/MathEvents.js + * + * Implements the event handlers needed by the output jax to perform + * menu, hover, and other events. + * + * --------------------------------------------------------------------- + * + * Copyright (c) 2011-2015 The MathJax Consortium + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +(function (HUB,HTML,AJAX,CALLBACK,LOCALE,OUTPUT,INPUT) { + var VERSION = "2.5.0"; + + var EXTENSION = MathJax.Extension; + var ME = EXTENSION.MathEvents = {version: VERSION}; + + var SETTINGS = HUB.config.menuSettings; + + var CONFIG = { + hover: 500, // time required to be considered a hover + frame: { + x: 3.5, y: 5, // frame padding and + bwidth: 1, // frame border width (in pixels) + bcolor: "#A6D", // frame border color + hwidth: "15px", // haze width + hcolor: "#83A" // haze color + }, + button: { + x: -4, y: -3, // menu button offsets + wx: -2, // button offset for full-width equations + src: AJAX.urlRev(OUTPUT.imageDir+"/MenuArrow-15.png") // button image + }, + fadeinInc: .2, // increment for fade-in + fadeoutInc: .05, // increment for fade-out + fadeDelay: 50, // delay between fade-in or fade-out steps + fadeoutStart: 400, // delay before fade-out after mouseout + fadeoutDelay: 15*1000, // delay before automatic fade-out + + styles: { + ".MathJax_Hover_Frame": { + "border-radius": ".25em", // Opera 10.5 and IE9 + "-webkit-border-radius": ".25em", // Safari and Chrome + "-moz-border-radius": ".25em", // Firefox + "-khtml-border-radius": ".25em", // Konqueror + + "box-shadow": "0px 0px 15px #83A", // Opera 10.5 and IE9 + "-webkit-box-shadow": "0px 0px 15px #83A", // Safari and Chrome + "-moz-box-shadow": "0px 0px 15px #83A", // Forefox + "-khtml-box-shadow": "0px 0px 15px #83A", // Konqueror + + border: "1px solid #A6D ! important", + display: "inline-block", position:"absolute" + }, + + ".MathJax_Hover_Arrow": { + position:"absolute", + width:"15px", height:"11px", + cursor:"pointer" + } + } + }; + + + // + // Common event-handling code + // + var EVENT = ME.Event = { + + LEFTBUTTON: 0, // the event.button value for left button + RIGHTBUTTON: 2, // the event.button value for right button + MENUKEY: "altKey", // the event value for alternate context menu + + Mousedown: function (event) {return EVENT.Handler(event,"Mousedown",this)}, + Mouseup: function (event) {return EVENT.Handler(event,"Mouseup",this)}, + Mousemove: function (event) {return EVENT.Handler(event,"Mousemove",this)}, + Mouseover: function (event) {return EVENT.Handler(event,"Mouseover",this)}, + Mouseout: function (event) {return EVENT.Handler(event,"Mouseout",this)}, + Click: function (event) {return EVENT.Handler(event,"Click",this)}, + DblClick: function (event) {return EVENT.Handler(event,"DblClick",this)}, + Menu: function (event) {return EVENT.Handler(event,"ContextMenu",this)}, + + // + // Call the output jax's event handler or the zoom handler + // + Handler: function (event,type,math) { + if (AJAX.loadingMathMenu) {return EVENT.False(event)} + var jax = OUTPUT[math.jaxID]; + if (!event) {event = window.event} + event.isContextMenu = (type === "ContextMenu"); + if (jax[type]) {return jax[type](event,math)} + if (EXTENSION.MathZoom) {return EXTENSION.MathZoom.HandleEvent(event,type,math)} + }, + + // + // Try to cancel the event in every way we can + // + False: function (event) { + if (!event) {event = window.event} + if (event) { + if (event.preventDefault) {event.preventDefault()} else {event.returnValue = false} + if (event.stopPropagation) {event.stopPropagation()} + event.cancelBubble = true; + } + return false; + }, + + // + // Load the contextual menu code, if needed, and post the menu + // + ContextMenu: function (event,math,force) { + // + // Check if we are showing menus + // + var JAX = OUTPUT[math.jaxID], jax = JAX.getJaxFromMath(math); + var show = (JAX.config.showMathMenu != null ? JAX : HUB).config.showMathMenu; + if (!show || (SETTINGS.context !== "MathJax" && !force)) return; + + // + // Remove selections, remove hover fades + // + if (ME.msieEventBug) {event = window.event || event} + EVENT.ClearSelection(); HOVER.ClearHoverTimer(); + if (jax.hover) { + if (jax.hover.remove) {clearTimeout(jax.hover.remove); delete jax.hover.remove} + jax.hover.nofade = true; + } + + // + // If the menu code is loaded, + // Check if localization needs loading; + // If not, post the menu, and return. + // Otherwise wait for the localization to load + // Otherwse load the menu code. + // Try again after the file is loaded. + // + var MENU = MathJax.Menu; var load, fn; + if (MENU) { + if (MENU.loadingDomain) {return EVENT.False(event)} + load = LOCALE.loadDomain("MathMenu"); + if (!load) { + MENU.jax = jax; + var source = MENU.menu.Find("Show Math As").menu; + source.items[0].name = jax.sourceMenuTitle; + source.items[0].format = (jax.sourceMenuFormat||"MathML"); + source.items[1].name = INPUT[jax.inputJax].sourceMenuTitle; + source.items[5].disabled = !INPUT[jax.inputJax].annotationEncoding; + + // + // Try and find each known annotation format and enable the menu + // items accordingly. + // + var annotations = source.items[2]; annotations.disabled = true; + var annotationItems = annotations.menu.items; + annotationList = MathJax.Hub.Config.semanticsAnnotations; + for (var i = 0, m = annotationItems.length; i < m; i++) { + var name = annotationItems[i].name[1] + if (jax.root && jax.root.getAnnotation(name) !== null) { + annotations.disabled = false; + annotationItems[i].hidden = false; + } else { + annotationItems[i].hidden = true; + } + } + + var MathPlayer = MENU.menu.Find("Math Settings","MathPlayer"); + MathPlayer.hidden = !(jax.outputJax === "NativeMML" && HUB.Browser.hasMathPlayer); + return MENU.menu.Post(event); + } + MENU.loadingDomain = true; + fn = function () {delete MENU.loadingDomain}; + } else { + if (AJAX.loadingMathMenu) {return EVENT.False(event)} + AJAX.loadingMathMenu = true; + load = AJAX.Require("[MathJax]/extensions/MathMenu.js"); + fn = function () { + delete AJAX.loadingMathMenu; + if (!MathJax.Menu) {MathJax.Menu = {}} + } + } + var ev = { + pageX:event.pageX, pageY:event.pageY, + clientX:event.clientX, clientY:event.clientY + }; + CALLBACK.Queue( + load, fn, // load the file and delete the marker when done + ["ContextMenu",EVENT,ev,math,force] // call this function again + ); + return EVENT.False(event); + }, + + // + // Mousedown handler for alternate means of accessing menu + // + AltContextMenu: function (event,math) { + var JAX = OUTPUT[math.jaxID]; + var show = (JAX.config.showMathMenu != null ? JAX : HUB).config.showMathMenu; + if (show) { + show = (JAX.config.showMathMenuMSIE != null ? JAX : HUB).config.showMathMenuMSIE; + if (SETTINGS.context === "MathJax" && !SETTINGS.mpContext && show) { + if (!ME.noContextMenuBug || event.button !== EVENT.RIGHTBUTTON) return; + } else { + if (!event[EVENT.MENUKEY] || event.button !== EVENT.LEFTBUTTON) return; + } + return JAX.ContextMenu(event,math,true); + } + }, + + ClearSelection: function () { + if (ME.safariContextMenuBug) {setTimeout("window.getSelection().empty()",0)} + if (document.selection) {setTimeout("document.selection.empty()",0)} + }, + + getBBox: function (span) { + span.appendChild(ME.topImg); + var h = ME.topImg.offsetTop, d = span.offsetHeight-h, w = span.offsetWidth; + span.removeChild(ME.topImg); + return {w:w, h:h, d:d}; + } + + }; + + // + // Handle hover "discoverability" + // + var HOVER = ME.Hover = { + + // + // Check if we are moving from a non-MathJax element to a MathJax one + // and either start fading in again (if it is fading out) or start the + // timer for the hover + // + Mouseover: function (event,math) { + if (SETTINGS.discoverable || SETTINGS.zoom === "Hover") { + var from = event.fromElement || event.relatedTarget, + to = event.toElement || event.target; + if (from && to && (from.isMathJax != to.isMathJax || + HUB.getJaxFor(from) !== HUB.getJaxFor(to))) { + var jax = this.getJaxFromMath(math); + if (jax.hover) {HOVER.ReHover(jax)} else {HOVER.HoverTimer(jax,math)} + return EVENT.False(event); + } + } + }, + // + // Check if we are moving from a MathJax element to a non-MathJax one + // and either start fading out, or clear the timer if we haven't + // hovered yet + // + Mouseout: function (event,math) { + if (SETTINGS.discoverable || SETTINGS.zoom === "Hover") { + var from = event.fromElement || event.relatedTarget, + to = event.toElement || event.target; + if (from && to && (from.isMathJax != to.isMathJax || + HUB.getJaxFor(from) !== HUB.getJaxFor(to))) { + var jax = this.getJaxFromMath(math); + if (jax.hover) {HOVER.UnHover(jax)} else {HOVER.ClearHoverTimer()} + return EVENT.False(event); + } + } + }, + // + // Restart hover timer if the mouse moves + // + Mousemove: function (event,math) { + if (SETTINGS.discoverable || SETTINGS.zoom === "Hover") { + var jax = this.getJaxFromMath(math); if (jax.hover) return; + if (HOVER.lastX == event.clientX && HOVER.lastY == event.clientY) return; + HOVER.lastX = event.clientX; HOVER.lastY = event.clientY; + HOVER.HoverTimer(jax,math); + return EVENT.False(event); + } + }, + + // + // Clear the old timer and start a new one + // + HoverTimer: function (jax,math) { + this.ClearHoverTimer(); + this.hoverTimer = setTimeout(CALLBACK(["Hover",this,jax,math]),CONFIG.hover); + }, + ClearHoverTimer: function () { + if (this.hoverTimer) {clearTimeout(this.hoverTimer); delete this.hoverTimer} + }, + + // + // Handle putting up the hover frame + // + Hover: function (jax,math) { + // + // Check if Zoom handles the hover event + // + if (EXTENSION.MathZoom && EXTENSION.MathZoom.Hover({},math)) return; + // + // Get the hover data + // + var JAX = OUTPUT[jax.outputJax], + span = JAX.getHoverSpan(jax,math), + bbox = JAX.getHoverBBox(jax,span,math), + show = (JAX.config.showMathMenu != null ? JAX : HUB).config.showMathMenu; + var dx = CONFIG.frame.x, dy = CONFIG.frame.y, dd = CONFIG.frame.bwidth; // frame size + if (ME.msieBorderWidthBug) {dd = 0} + jax.hover = {opacity:0, id:jax.inputID+"-Hover"}; + // + // The frame and menu button + // + var frame = HTML.Element("span",{ + id:jax.hover.id, isMathJax: true, + style:{display:"inline-block", width:0, height:0, position:"relative"} + },[["span",{ + className:"MathJax_Hover_Frame", isMathJax: true, + style:{ + display:"inline-block", position:"absolute", + top:this.Px(-bbox.h-dy-dd-(bbox.y||0)), left:this.Px(-dx-dd+(bbox.x||0)), + width:this.Px(bbox.w+2*dx), height:this.Px(bbox.h+bbox.d+2*dy), + opacity:0, filter:"alpha(opacity=0)" + }} + ]] + ); + var button = HTML.Element("span",{ + isMathJax: true, id:jax.hover.id+"Menu", + style:{display:"inline-block", "z-index": 1, width:0, height:0, position:"relative"} + },[["img",{ + className: "MathJax_Hover_Arrow", isMathJax: true, math: math, + src: CONFIG.button.src, onclick: this.HoverMenu, jax:JAX.id, + style: { + left:this.Px(bbox.w+dx+dd+(bbox.x||0)+CONFIG.button.x), + top:this.Px(-bbox.h-dy-dd-(bbox.y||0)-CONFIG.button.y), + opacity:0, filter:"alpha(opacity=0)" + } + }]] + ); + if (bbox.width) { + frame.style.width = button.style.width = bbox.width; + frame.style.marginRight = button.style.marginRight = "-"+bbox.width; + frame.firstChild.style.width = bbox.width; + button.firstChild.style.left = ""; + button.firstChild.style.right = this.Px(CONFIG.button.wx); + } + // + // Add the frame and button + // + span.parentNode.insertBefore(frame,span); + if (show) {span.parentNode.insertBefore(button,span)} + if (span.style) {span.style.position = "relative"} // so math is on top of hover frame + // + // Start the hover fade-in + // + this.ReHover(jax); + }, + // + // Restart the hover fade in and fade-out timers + // + ReHover: function (jax) { + if (jax.hover.remove) {clearTimeout(jax.hover.remove)} + jax.hover.remove = setTimeout(CALLBACK(["UnHover",this,jax]),CONFIG.fadeoutDelay); + this.HoverFadeTimer(jax,CONFIG.fadeinInc); + }, + // + // Start the fade-out + // + UnHover: function (jax) { + if (!jax.hover.nofade) {this.HoverFadeTimer(jax,-CONFIG.fadeoutInc,CONFIG.fadeoutStart)} + }, + // + // Handle the fade-in and fade-out + // + HoverFade: function (jax) { + delete jax.hover.timer; + jax.hover.opacity = Math.max(0,Math.min(1,jax.hover.opacity + jax.hover.inc)); + jax.hover.opacity = Math.floor(1000*jax.hover.opacity)/1000; + var frame = document.getElementById(jax.hover.id), + button = document.getElementById(jax.hover.id+"Menu"); + frame.firstChild.style.opacity = jax.hover.opacity; + frame.firstChild.style.filter = "alpha(opacity="+Math.floor(100*jax.hover.opacity)+")"; + if (button) { + button.firstChild.style.opacity = jax.hover.opacity; + button.firstChild.style.filter = frame.style.filter; + } + if (jax.hover.opacity === 1) {return} + if (jax.hover.opacity > 0) {this.HoverFadeTimer(jax,jax.hover.inc); return} + frame.parentNode.removeChild(frame); + if (button) {button.parentNode.removeChild(button)} + if (jax.hover.remove) {clearTimeout(jax.hover.remove)} + delete jax.hover; + }, + // + // Set the fade to in or out (via inc) and start the timer, if needed + // + HoverFadeTimer: function (jax,inc,delay) { + jax.hover.inc = inc; + if (!jax.hover.timer) { + jax.hover.timer = setTimeout(CALLBACK(["HoverFade",this,jax]),(delay||CONFIG.fadeDelay)); + } + }, + + // + // Handle a click on the menu button + // + HoverMenu: function (event) { + if (!event) {event = window.event} + return OUTPUT[this.jax].ContextMenu(event,this.math,true); + }, + + // + // Clear all hover timers + // + ClearHover: function (jax) { + if (jax.hover.remove) {clearTimeout(jax.hover.remove)} + if (jax.hover.timer) {clearTimeout(jax.hover.timer)} + HOVER.ClearHoverTimer(); + delete jax.hover; + }, + + // + // Make a measurement in pixels + // + Px: function (m) { + if (Math.abs(m) < .006) {return "0px"} + return m.toFixed(2).replace(/\.?0+$/,"") + "px"; + }, + + // + // Preload images so they show up with the menu + // + getImages: function () { + if (SETTINGS.discoverable) { + var menu = new Image(); + menu.src = CONFIG.button.src; + } + } + + }; + + // + // Handle touch events. + // + // Use double-tap-and-hold as a replacement for context menu event. + // Use double-tap as a replacement for double click. + // + var TOUCH = ME.Touch = { + + last: 0, // time of last tap event + delay: 500, // delay time for double-click + + // + // Check if this is a double-tap, and if so, start the timer + // for the double-tap and hold (to trigger the contextual menu) + // + start: function (event) { + var now = new Date().getTime(); + var dblTap = (now - TOUCH.last < TOUCH.delay && TOUCH.up); + TOUCH.last = now; TOUCH.up = false; + if (dblTap) { + TOUCH.timeout = setTimeout(TOUCH.menu,TOUCH.delay,event,this); + event.preventDefault(); + } + }, + + // + // Check if there is a timeout pending, i.e., we have a + // double-tap and were waiting to see if it is held long + // enough for the menu. Since we got the end before the + // timeout, it is a double-click, not a double-tap-and-hold. + // Prevent the default action and issue a double click. + // + end: function (event) { + var now = new Date().getTime(); + TOUCH.up = (now - TOUCH.last < TOUCH.delay); + if (TOUCH.timeout) { + clearTimeout(TOUCH.timeout); + delete TOUCH.timeout; TOUCH.last = 0; TOUCH.up = false; + event.preventDefault(); + return EVENT.Handler((event.touches[0]||event.touch),"DblClick",this); + } + }, + + // + // If the timeout passes without an end event, we issue + // the contextual menu event. + // + menu: function (event,math) { + delete TOUCH.timeout; TOUCH.last = 0; TOUCH.up = false; + return EVENT.Handler((event.touches[0]||event.touch),"ContextMenu",math); + } + + }; + + // + // Mobile screens are small, so use larger version of arrow + // + if (HUB.Browser.isMobile) { + var arrow = CONFIG.styles[".MathJax_Hover_Arrow"]; + arrow.width = "25px"; arrow.height = "18px"; + CONFIG.button.x = -6; + } + + // + // Set up browser-specific values + // + HUB.Browser.Select({ + MSIE: function (browser) { + var mode = (document.documentMode || 0); + var isIE8 = browser.versionAtLeast("8.0"); + ME.msieBorderWidthBug = (document.compatMode === "BackCompat"); // borders are inside offsetWidth/Height + ME.msieEventBug = browser.isIE9; // must get event from window even though event is passed + ME.msieAlignBug = (!isIE8 || mode < 8); // inline-block spans don't rest on baseline + if (mode < 9) {EVENT.LEFTBUTTON = 1} // IE < 9 has wrong event.button values + }, + Safari: function (browser) { + ME.safariContextMenuBug = true; // selection can be started by contextmenu event + }, + Opera: function (browser) { + ME.operaPositionBug = true; // position is wrong unless border is used + }, + Konqueror: function (browser) { + ME.noContextMenuBug = true; // doesn't produce contextmenu event + } + }); + + // + // Used in measuring zoom and hover positions + // + ME.topImg = (ME.msieAlignBug ? + HTML.Element("img",{style:{width:0,height:0,position:"relative"},src:"about:blank"}) : + HTML.Element("span",{style:{width:0,height:0,display:"inline-block"}}) + ); + if (ME.operaPositionBug) {ME.topImg.style.border="1px solid"} + + // + // Get configuration from user + // + ME.config = CONFIG = HUB.CombineConfig("MathEvents",CONFIG); + var SETFRAME = function () { + var haze = CONFIG.styles[".MathJax_Hover_Frame"]; + haze.border = CONFIG.frame.bwidth+"px solid "+CONFIG.frame.bcolor+" ! important"; + haze["box-shadow"] = haze["-webkit-box-shadow"] = + haze["-moz-box-shadow"] = haze["-khtml-box-shadow"] = + "0px 0px "+CONFIG.frame.hwidth+" "+CONFIG.frame.hcolor; + }; + + // + // Queue the events needed for startup + // + CALLBACK.Queue( + HUB.Register.StartupHook("End Config",{}), // wait until config is complete + [SETFRAME], + ["getImages",HOVER], + ["Styles",AJAX,CONFIG.styles], + ["Post",HUB.Startup.signal,"MathEvents Ready"], + ["loadComplete",AJAX,"[MathJax]/extensions/MathEvents.js"] + ); + +})(MathJax.Hub,MathJax.HTML,MathJax.Ajax,MathJax.Callback, + MathJax.Localization,MathJax.OutputJax,MathJax.InputJax); diff --git a/unpacked/extensions/Safe.js b/unpacked/extensions/Safe.js new file mode 100644 index 000000000..47a60ef50 --- /dev/null +++ b/unpacked/extensions/Safe.js @@ -0,0 +1,348 @@ +/* -*- Mode: Javascript; indent-tabs-mode:nil; js-indent-level: 2 -*- */ +/* vim: set ts=2 et sw=2 tw=80: */ + +/************************************************************* + * + * MathJax/extensions/Safe.js + * + * Implements a "Safe" mode that disables features that could be + * misused in a shared environment (such as href's to javascript URL's). + * See the CONFIG variable below for configuration options. + * + * --------------------------------------------------------------------- + * + * Copyright (c) 2013-2015 The MathJax Consortium + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +(function (HUB,AJAX) { + var VERSION = "2.5.0"; + + var CONFIG = MathJax.Hub.CombineConfig("Safe",{ + allow: { + // + // Values can be "all", "safe", or "none" + // + URLs: "safe", // safe are in safeProtocols below + classes: "safe", // safe start with MJX- + cssIDs: "safe", // safe start with MJX- + styles: "safe", // safe are in safeStyles below + fontsize: "all", // safe are between sizeMin and sizeMax em's + require: "safe" // safe are in safeRequire below + }, + sizeMin: .7, // \scriptsize + sizeMax: 1.44, // \large + safeProtocols: { + http: true, + https: true, + file: true, + javascript: false + }, + safeStyles: { + color: true, + backgroundColor: true, + border: true, + cursor: true, + margin: true, + padding: true, + textShadow: true, + fontFamily: true, + fontSize: true, + fontStyle: true, + fontWeight: true, + opacity: true, + outline: true + }, + safeRequire: { + action: true, + amscd: true, + amsmath: true, + amssymbols: true, + autobold: false, + "autoload-all": false, + bbox: true, + begingroup: true, + boldsymbol: true, + cancel: true, + color: true, + enclose: true, + extpfeil: true, + HTML: true, + mathchoice: true, + mhchem: true, + newcommand: true, + noErrors: false, + noUndefined: false, + unicode: true, + verb: true + } + }); + + var ALLOW = CONFIG.allow; + if (ALLOW.fontsize !== "all") {CONFIG.safeStyles.fontSize = false} + + var SAFE = MathJax.Extension.Safe = { + version: VERSION, + config: CONFIG, + div1: document.createElement("div"), // for CSS processing + div2: document.createElement("div"), + + // + // Methods called for MathML attribute processing + // + filter: { + href: "filterURL", + src: "filterURL", + altimg: "filterURL", + "class": "filterClass", + style: "filterStyles", + id: "filterID", + fontsize: "filterFontSize", + mathsize: "filterFontSize", + scriptminsize: "filterFontSize", + scriptsizemultiplier: "filterSizeMultiplier", + scriptlevel: "filterScriptLevel" + }, + + // + // Filter HREF URL's + // + filterURL: function (url) { + var protocol = (url.match(/^\s*([a-z]+):/i)||[null,""])[1].toLowerCase(); + if (ALLOW.URLs === "none" || + (ALLOW.URLs !== "all" && !CONFIG.safeProtocols[protocol])) {url = null} + return url; + }, + + // + // Filter class names and css ID's + // + filterClass: function (CLASS) { + if (ALLOW.classes === "none" || + (ALLOW.classes !== "all" && !CLASS.match(/^MJX-[-a-zA-Z0-9_.]+$/))) {CLASS = null} + return CLASS; + }, + filterID: function (id) { + if (ALLOW.cssIDs === "none" || + (ALLOW.cssIDs !== "all" && !id.match(/^MJX-[-a-zA-Z0-9_.]+$/))) {id = null} + return id; + }, + + // + // Filter style strings + // + filterStyles: function (styles) { + if (ALLOW.styles === "all") {return styles} + if (ALLOW.styles === "none") {return null} + try { + // + // Set the div1 styles to the given styles, and clear div2 + // + var STYLE1 = this.div1.style, STYLE2 = this.div2.style; + STYLE1.cssText = styles; STYLE2.cssText = ""; + // + // Check each allowed style and transfer OK ones to div2 + // + for (var name in CONFIG.safeStyles) {if (CONFIG.safeStyles.hasOwnProperty(name)) { + var value = this.filterStyle(name,STYLE1[name]); + if (value != null) {STYLE2[name] = value} + }} + // + // Return the div2 style string + // + styles = STYLE2.cssText; + } catch (e) {styles = null} + return styles; + }, + // + // Filter an individual name:value style pair + // + filterStyle: function (name,value) { + if (typeof value !== "string") {return null} + if (value.match(/^\s*expression/)) {return null} + if (value.match(/javascript:/)) {return null} + return (CONFIG.safeStyles[name] ? value : null); + }, + + // + // Filter TeX font size values (in em's) + // + filterSize: function (size) { + if (ALLOW.fontsize === "none") {return null} + if (ALLOW.fontsize !== "all") + {size = Math.min(Math.max(size,CONFIG.sizeMin),CONFIG.sizeMax)} + return size; + }, + filterFontSize: function (size) { + return (ALLOW.fontsize === "all" ? size: null); + }, + + // + // Filter scriptsizemultiplier + // + filterSizeMultiplier: function (size) { + if (ALLOW.fontsize === "none") {size = null} + else if (ALLOW.fontsize !== "all") {size = Math.min(1,Math.max(.6,size)).toString()} + return size; + }, + // + // Filter scriptLevel + // + filterScriptLevel: function (level) { + if (ALLOW.fontsize === "none") {level = null} + else if (ALLOW.fontsize !== "all") {level = Math.max(0,level).toString()} + return level; + }, + + // + // Filter TeX extension names + // + filterRequire: function (name) { + if (ALLOW.require === "none" || + (ALLOW.require !== "all" && !CONFIG.safeRequire[name.toLowerCase()])) + {name = null} + return name; + } + + }; + + HUB.Register.StartupHook("TeX HTML Ready",function () { + var TEX = MathJax.InputJax.TeX; + + TEX.Parse.Augment({ + + // + // Implements \href{url}{math} with URL filter + // + HREF_attribute: function (name) { + var url = SAFE.filterURL(this.GetArgument(name)), + arg = this.GetArgumentMML(name); + if (url) {arg.With({href:url})} + this.Push(arg); + }, + + // + // Implements \class{name}{math} with class-name filter + // + CLASS_attribute: function (name) { + var CLASS = SAFE.filterClass(this.GetArgument(name)), + arg = this.GetArgumentMML(name); + if (CLASS) { + if (arg["class"] != null) {CLASS = arg["class"] + " " + CLASS} + arg.With({"class":CLASS}); + } + this.Push(arg); + }, + + // + // Implements \style{style-string}{math} with style filter + // + STYLE_attribute: function (name) { + var style = SAFE.filterStyles(this.GetArgument(name)), + arg = this.GetArgumentMML(name); + if (style) { + if (arg.style != null) { + if (style.charAt(style.length-1) !== ";") {style += ";"} + style = arg.style + " " + style; + } + arg.With({style: style}); + } + this.Push(arg); + }, + + // + // Implements \cssId{id}{math} with ID filter + // + ID_attribute: function (name) { + var ID = SAFE.filterID(this.GetArgument(name)), + arg = this.GetArgumentMML(name); + if (ID) {arg.With({id:ID})} + this.Push(arg); + } + + }); + + }); + + HUB.Register.StartupHook("TeX Jax Ready",function () { + var TEX = MathJax.InputJax.TeX, + PARSE = TEX.Parse, METHOD = SAFE.filter; + + PARSE.Augment({ + + // + // Implements \require{name} with filtering + // + Require: function (name) { + var file = this.GetArgument(name).replace(/.*\//,"").replace(/[^a-z0-9_.-]/ig,""); + file = SAFE.filterRequire(file); + if (file) {this.Extension(null,file)} + }, + + // + // Controls \mmlToken attributes + // + MmlFilterAttribute: function (name,value) { + if (METHOD[name]) {value = SAFE[METHOD[name]](value)} + return value; + }, + + // + // Handles font size macros with filtering + // + SetSize: function (name,size) { + size = SAFE.filterSize(size); + if (size) { + this.stack.env.size = size; + this.Push(TEX.Stack.Item.style().With({styles: {mathsize: size+"em"}})); + } + } + + }); + }); + + HUB.Register.StartupHook("TeX bbox Ready",function () { + var TEX = MathJax.InputJax.TeX; + + // + // Filter the styles for \bbox + // + TEX.Parse.Augment({ + BBoxStyle: function (styles) {return SAFE.filterStyles(styles)} + }); + + }); + + HUB.Register.StartupHook("MathML Jax Ready",function () { + var PARSE = MathJax.InputJax.MathML.Parse, + METHOD = SAFE.filter; + + // + // Filter MathML attributes + // + PARSE.Augment({ + filterAttribute: function (name,value) { + if (METHOD[name]) {value = SAFE[METHOD[name]](value)} + return value; + } + }); + + }); + + // MathML input (href, style, fontsize, class, id) + + HUB.Startup.signal.Post("Safe Extension Ready"); + AJAX.loadComplete("[MathJax]/extensions/Safe.js"); + +})(MathJax.Hub,MathJax.Ajax);