diff options
Diffstat (limited to 'js/mathjax/extensions/MathEvents.js')
-rw-r--r-- | js/mathjax/extensions/MathEvents.js | 624 |
1 files changed, 624 insertions, 0 deletions
diff --git a/js/mathjax/extensions/MathEvents.js b/js/mathjax/extensions/MathEvents.js new file mode 100644 index 0000000..2f83e05 --- /dev/null +++ b/js/mathjax/extensions/MathEvents.js @@ -0,0 +1,624 @@ +// @license magnet:?xt=urn:btih:8e4f440f4c65981c5bf93c76d35135ba5064d8b7dn=apache-2.0.txt Apache-2.0 +/* -*- 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-2020 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.7.9"; + + 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: -6, y: -3, // menu button offsets + wx: -2 // button offset for full-width equations + }, + 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_Menu_Button .MathJax_Hover_Arrow": { + position:"absolute", + cursor:"pointer", + display:"inline-block", + border:"2px solid #AAA", + "border-radius":"4px", + "-webkit-border-radius": "4px", // Safari and Chrome + "-moz-border-radius": "4px", // Firefox + "-khtml-border-radius": "4px", // Konqueror + "font-family":"'Courier New',Courier", + "font-size":"9px", + color:"#F0F0F0" + }, + ".MathJax_Menu_Button .MathJax_Hover_Arrow span": { + display:"block", + "background-color":"#AAA", + border:"1px solid", + "border-radius":"3px", + "line-height":0, + padding:"4px" + }, + ".MathJax_Hover_Arrow:hover": { + color:"white!important", + border:"2px solid #CCC!important" + }, + ".MathJax_Hover_Arrow:hover span": { + "background-color":"#CCC!important" + } + } + }; + + + // + // 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 + + /*************************************************************/ + /* + * Enum element for key codes. + */ + KEY: { + RETURN: 13, + ESCAPE: 27, + SPACE: 32, + LEFT: 37, + UP: 38, + RIGHT: 39, + DOWN: 40, + LESS: 188, + GREATER: 190, + DASH: 189 + }, + + 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; + }, + + // + // Keydown event handler. Should only fire on Space key. + // + Keydown: function (event, math) { + if (!event) event = window.event; + if (event.keyCode === EVENT.KEY.SPACE) { + EVENT.ContextMenu(event, this); + }; + }, + + // + // 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").submenu; + 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.submenu.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 && (HUB.isMathJaxNode(from) !== HUB.isMathJaxNode(to) || + 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 && (HUB.isMathJaxNode(from) !== HUB.isMathJaxNode(to) || + 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", className:"MathJax_Menu_Button", + style:{display:"inline-block", "z-index": 1, width:0, height:0, position:"relative"} + },[["span",{ + className: "MathJax_Hover_Arrow", isMathJax: true, math: math, + 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)" + } + },[["span",{isMathJax:true},"\u25BC"]]]] + ); + 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); +// @license-end |