123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619 |
- /* -*- 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-2017 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.2";
- 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
- },
- 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);
|