UNPKG

react-components

Version:

React components used by Khan Academy

1,554 lines (1,328 loc) 1.06 MB
(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){ "use strict"; /* WARNING - DEPRECATED * * We recommend that you don't use this mixin. It's not idiomatic react and * leads to inefficient and janky code. It's almost impossible to recover good * performance with this component. It's also invasive - hard to remove after * it's invaded your code. */ /** * BackboneMixin - automatic binding and unbinding for react classes mirroring * backbone models and views. Example: * * const Model = Backbone.Model.extend({ ... }); * const Collection = Backbone.Collection.extend({ ... }); * * const Example = React.createClass({ * mixins: [BackboneMixin], * getBackboneModels: function() { * return [this.model, this.collection]; * } * }); * * List the models and collections that your class uses and it'll be * automatically `forceUpdate`-ed when they change. * * This binds *and* unbinds the events. */ var BackboneMixin = { componentDidMount: function componentDidMount() { this._backboneModels = this.getBackboneModels(); this._validateModelArray(this._backboneModels); this._bind(this._backboneModels); }, componentWillUnmount: function componentWillUnmount() { this._unbind(this._backboneModels); }, // The backbone models may have changed - rebind to the new ones componentDidUpdate: function componentDidUpdate(nextProps, nextState) { var previousModels = this._backboneModels; var currentModels = this._backboneModels = this.getBackboneModels(); var oldModels = []; var newModels = []; var _iteratorNormalCompletion = true; var _didIteratorError = false; var _iteratorError = undefined; try { for (var _iterator = previousModels[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) { var model = _step.value; if (currentModels.indexOf(model) < 0) { oldModels.push(model); } } } catch (err) { _didIteratorError = true; _iteratorError = err; } finally { try { if (!_iteratorNormalCompletion && _iterator.return) { _iterator.return(); } } finally { if (_didIteratorError) { throw _iteratorError; } } } var _iteratorNormalCompletion2 = true; var _didIteratorError2 = false; var _iteratorError2 = undefined; try { for (var _iterator2 = currentModels[Symbol.iterator](), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) { var _model = _step2.value; if (previousModels.indexOf(_model) < 0) { newModels.push(_model); } } } catch (err) { _didIteratorError2 = true; _iteratorError2 = err; } finally { try { if (!_iteratorNormalCompletion2 && _iterator2.return) { _iterator2.return(); } } finally { if (_didIteratorError2) { throw _iteratorError2; } } } this._unbind(oldModels); this._bind(newModels); }, _bind: function _bind(models) { models.map(function (model) { model.on("add change remove reset", this._backboneForceUpdate, this); }.bind(this)); }, _unbind: function _unbind(models) { models.map(function (model) { model.off("add change remove reset", this._backboneForceUpdate, this); }.bind(this)); }, _backboneForceUpdate: function _backboneForceUpdate() { // TODO(joel): more rigorous fix needed? -- for the following error: // "Invariant Violation: forceUpdate(...): Can only force an update on // mounted or mounting components." if (this.isMounted()) { this.forceUpdate(); } }, _validateModelArray: function _validateModelArray(backboneModels) { if (!Array.isArray(backboneModels)) { throw new Error("getBackboneModels must return an array. " + "get this " + backboneModels + " out of here."); } } }; module.exports = BackboneMixin; },{}],2:[function(require,module,exports){ "use strict"; var React = require("react"); /* You know when you want to propagate input to a parent... * but then that parent does something with the input... * then changing the props of the input... * on every keystroke... * so if some input is invalid or incomplete... * the input gets reset or otherwise effed... * * This is the solution. * * Enough melodrama. Its an input that only sends changes * to its parent on blur. */ var BlurInput = React.createClass({ displayName: "BlurInput", propTypes: { className: React.PropTypes.string, style: React.PropTypes.any, value: React.PropTypes.string.isRequired, onChange: React.PropTypes.func.isRequired }, getInitialState: function getInitialState() { return { value: this.props.value }; }, componentWillReceiveProps: function componentWillReceiveProps(nextProps) { this.setState({ value: nextProps.value }); }, handleChange: function handleChange(e) { this.setState({ value: e.target.value }); }, handleBlur: function handleBlur(e) { this.props.onChange(e.target.value); }, render: function render() { return React.createElement("input", { className: this.props.className, style: this.props.style, type: "text", value: this.state.value, onChange: this.handleChange, onBlur: this.handleBlur }); } }); module.exports = BlurInput; },{"react":237}],3:[function(require,module,exports){ 'use strict'; /* ButtonGroup is an aesthetically pleasing group of buttons. * * The class requires these properties: * buttons - an array of objects with keys: * "value": this is the value returned when the button is selected * "content": this is the JSX shown within the button, typically a string * that gets rendered as the button's display text * "title": this is the title-text shown on hover * onChange - a function that is provided with the updated value * (which it then is responsible for updating) * * The class has these optional properties: * value - the initial value of the button selected, defaults to null. * allowEmpty - if false, exactly one button _must_ be selected; otherwise * it defaults to true and _at most_ one button (0 or 1) may be selected. * * Requires stylesheets/perseus-admin-package/editor.less to look nice. */ var React = require('react'); var ReactDOM = require("react-dom"); var styles = require('./styles.js'); var css = require("aphrodite").css; var ButtonGroup = React.createClass({ displayName: 'ButtonGroup', propTypes: { value: React.PropTypes.any, buttons: React.PropTypes.arrayOf(React.PropTypes.shape({ value: React.PropTypes.any.isRequired, content: React.PropTypes.node, title: React.PropTypes.string })).isRequired, onChange: React.PropTypes.func.isRequired, allowEmpty: React.PropTypes.bool }, getDefaultProps: function getDefaultProps() { return { value: null, allowEmpty: true }; }, focus: function focus() { ReactDOM.findDOMNode(this).focus(); return true; }, toggleSelect: function toggleSelect(newValue) { var value = this.props.value; if (this.props.allowEmpty) { // Select the new button or unselect if it's already selected this.props.onChange(value !== newValue ? newValue : null); } else { this.props.onChange(newValue); } }, render: function render() { var _this = this; var value = this.props.value; var buttons = this.props.buttons.map(function (button, i) { return React.createElement( 'button', { title: button.title, type: 'button', id: "" + i, ref: "button" + i, key: "" + i, className: css(styles.button.buttonStyle, button.value === value && styles.button.selectedStyle), onClick: _this.toggleSelect.bind(_this, button.value) }, button.content || "" + button.value ); }); var outerStyle = { display: 'inline-block' }; return React.createElement( 'div', { style: outerStyle }, buttons ); } }); module.exports = ButtonGroup; },{"./styles.js":14,"aphrodite":22,"react":237,"react-dom":73}],4:[function(require,module,exports){ "use strict"; var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; /* This component makes its children a drag target. Example: * * <DragTarget onDrop={this.handleDrop}>Drag to me</DragTarget> * * ... * * handleDrop: function(e) { * this.addImages(e.nativeEvent.dataTransfer.files); * } * * Now "Drag to me" will be a drag target - when something is dragged over it, * the element will become partially transparent as a visual indicator that * it's a target. */ // TODO(joel) - indicate before the hover is over the target that it's possible // to drag into the target. This would (I think) require a high level handler - // like on Perseus itself, waiting for onDragEnter, then passing down the // event. Sounds like a pain. Possible workaround - create a div covering the // entire page... // // Other extensions: // * custom styles for global drag and dragOver // * only respond to certain types of drags (only images for instance)! var React = require('react'); var DragTarget = React.createClass({ displayName: "DragTarget", propTypes: { // All props not listed here are forwarded to the root element without // modification. onDrop: React.PropTypes.func.isRequired, component: React.PropTypes.any, // component type shouldDragHighlight: React.PropTypes.func, style: React.PropTypes.any }, getDefaultProps: function getDefaultProps() { return { component: "div", shouldDragHighlight: function shouldDragHighlight() { return true; } }; }, getInitialState: function getInitialState() { return { dragHover: false }; }, handleDrop: function handleDrop(e) { e.stopPropagation(); e.preventDefault(); this.setState({ dragHover: false }); this.props.onDrop(e); }, handleDragEnd: function handleDragEnd() { this.setState({ dragHover: false }); }, handleDragOver: function handleDragOver(e) { e.preventDefault(); }, handleDragLeave: function handleDragLeave() { this.setState({ dragHover: false }); }, handleDragEnter: function handleDragEnter(e) { this.setState({ dragHover: this.props.shouldDragHighlight(e) }); }, render: function render() { var opacity = this.state.dragHover ? { "opacity": 0.3 } : {}; var Component = this.props.component; return React.createElement(Component, _extends({}, this.props, { style: Object.assign({}, this.props.style, opacity), onDrop: this.handleDrop, onDragEnd: this.handleDragEnd, onDragOver: this.handleDragOver, onDragEnter: this.handleDragEnter, onDragLeave: this.handleDragLeave })); } }); module.exports = DragTarget; },{"react":237}],5:[function(require,module,exports){ "use strict"; /** * Performs sprintf-like %(name)s replacement on str, and returns a React * fragment of the string interleaved with those replacements. The replacements * can be any valid React node including strings and numbers. * * For example: * interpolateStringToFragment("test", {}) -> * test * interpolateStringToFragment("test %(num)s", {num: 5}) -> * test 5 * interpolateStringToFragment("test %(num)s", {num: <Count />}) -> * test <Count /> */ var createFragment = require('react-addons-create-fragment'); var interpolationMarker = /%\(([\w_]+)\)s/g; var interpolateStringToFragment = function interpolateStringToFragment(str, options) { options = options || {}; // Split the string into its language fragments and substitutions var split = str.split(interpolationMarker); var result = { "text_0": split[0] }; // Replace the substitutions with the appropriate option for (var i = 1; i < split.length; i += 2) { var key = split[i]; var replaceWith = options[key]; if (replaceWith === undefined) { replaceWith = "%(" + key + ")s"; } // We prefix each substitution key with a number that increments each // time it's used, so "test %(num)s %(fruit)s and %(num)s again" turns // into an object with keys: // [text_0, 0_num, text_2, 0_fruit, text_4, 1_num, text_6] // This is better than just using the array index in the case that we // switch between two translated strings with the same variables. // Admittedly, an edge case. var j = 0; while (result.hasOwnProperty(j + "_" + key)) { j++; } result[j + "_" + key] = replaceWith; // Because the regex has one capturing group, the `split` array always // has an odd number of elements, so this always stays in bounds. result["text_" + (i + 1)] = split[i + 1]; } return createFragment(result); }; /** * A simple i18n react component-like function to allow for string * interpolation destined for the output of a react render() function * * This function understands react components, or other things renderable by * react, passed in as props. * * Examples: * <$_ first="Motoko" last="Kusanagi"> * Hello, %(first)s %(last)s! * </$> * * which react/jsx compiles to: * $_({first: "Motoko", last: "Kusanagi"}, "Hello, %(first)s %(last)s!") * * * <$_ textbox={<input type="text" />}> * Please enter a number: %(textbox)s * </$_> * * which react/jsx compiles to: * $_({textbox: React.DOM.input({type: "text"}), * "Please enter a number: %(textbox)s") * * Note: this is not a full react component to avoid complex handling of other * things added to props, such as this.props.ref and this.props.children */ var $_ = function $_(options, str) { if (arguments.length !== 2 || typeof str !== "string") { return "<$_> must have exactly one child, which must be a string"; } return interpolateStringToFragment(str, options); }; module.exports = $_; },{"react-addons-create-fragment":70}],6:[function(require,module,exports){ 'use strict'; var _require = require("aphrodite"), StyleSheet = _require.StyleSheet, css = _require.css; var React = require('react'); var colors = { grayLight: '#aaa', basicBorderColor: '#ccc', white: '#fff' }; var triangleBeforeAfter = { borderBottom: '9px solid transparent', borderTop: '9px solid transparent', content: '" "', height: '0', position: 'absolute', top: '0', width: '0' }; var styles = StyleSheet.create({ infoTip: { display: 'inline-block', marginLeft: '5px', position: 'relative' }, infoTipContainer: { position: 'absolute', top: '-12px', left: '22px', zIndex: '1000' }, infoTipTriangle: { height: '10px', left: '0', position: 'absolute', top: '8px', width: '0', zIndex: '1', ':before': Object.assign({}, triangleBeforeAfter, { borderRight: '9px solid #bbb', right: '0' }), ':after': Object.assign({}, triangleBeforeAfter, { borderRight: '9px solid ' + colors.white, right: '-1px' }) }, verticalShadow: { border: '1px solid ' + colors.basicBorderColor, boxShadow: '0 1px 3px ' + colors.basicBorderColor, borderBottom: '1px solid ' + colors.grayLight }, infoTipContentContainer: { background: colors.white, padding: '5px 10px', width: '240px' } }); var questionMark = 'data:image/png;base64,' + 'iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAAGXRFWHRTb2Z0d2FyZQBB' + 'ZG9iZSBJbWFnZVJlYWR5ccllPAAAA3NpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/' + 'eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+' + 'IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2Jl' + 'IFhNUCBDb3JlIDUuNS1jMDIxIDc5LjE1NDkxMSwgMjAxMy8xMC8yOS0xMTo0NzoxNiAg' + 'ICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5' + 'LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9' + 'IiIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHht' + 'bG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3Vy' + 'Y2VSZWYjIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHht' + 'cE1NOk9yaWdpbmFsRG9jdW1lbnRJRD0ieG1wLmRpZDo2N2M3NTAxYS04YmVlLTQ0M2Mt' + 'YmRiNS04OGM2N2IxN2NhYzEiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6OUJCRTk4' + 'Qjc4NjAwMTFFMzg3QUJDNEI4Mzk2QTRGQkQiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5p' + 'aWQ6OUJCRTk4QjY4NjAwMTFFMzg3QUJDNEI4Mzk2QTRGQkQiIHhtcDpDcmVhdG9yVG9v' + 'bD0iQWRvYmUgUGhvdG9zaG9wIENDIChNYWNpbnRvc2gpIj4gPHhtcE1NOkRlcml2ZWRG' + 'cm9tIHN0UmVmOmluc3RhbmNlSUQ9InhtcC5paWQ6NGE5ZDI0OTMtODk1NC00OGFkLTlh' + 'MTgtZDAwM2MwYWNjNDJlIiBzdFJlZjpkb2N1bWVudElEPSJ4bXAuZGlkOjY3Yzc1MDFh' + 'LThiZWUtNDQzYy1iZGI1LTg4YzY3YjE3Y2FjMSIvPiA8L3JkZjpEZXNjcmlwdGlvbj4g' + 'PC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/Pqm89uYAAADM' + 'SURBVHjaXJA9DoJAEIUH1M4TUHIFsCMGen9OwCGw1YRGW2ntKel0exsojHIBC0ouQAyU' + 'viFDstmXfNmZeS+zm7XSNCXRFiRgJf0bXIHixpbhGdxBBJYC1w/xaA424MhNEATkui71' + 'fU9KqfEU78UbD9PdbJRlOdae55GmhIP+1NV1TcMwkOM41DSNHvRtMhTHMRVFQW3b6mOL' + 'gx99kue5GRp/gIOZuZGvNpTNwjD8oliANU+qqqKu6/TQBdymN57AHjzBT+B6Jx79BRgA' + 'vc49kQA4yxgAAAAASUVORK5CYII='; var InfoTip = React.createClass({ displayName: 'InfoTip', propTypes: { children: React.PropTypes.node }, getInitialState: function getInitialState() { return { hover: false }; }, handleMouseEnter: function handleMouseEnter() { this.setState({ hover: true }); }, handleMouseLeave: function handleMouseLeave() { this.setState({ hover: false }); }, render: function render() { return React.createElement( 'div', { className: css(styles.infoTip) }, React.createElement('img', { width: 10, height: 10, src: questionMark, onMouseEnter: this.handleMouseEnter, onMouseLeave: this.handleMouseLeave }), React.createElement( 'div', { className: css(styles.infoTipContainer), style: { display: this.state.hover ? 'block' : 'none' } }, React.createElement('div', { className: css(styles.infoTipTriangle) }), React.createElement( 'div', { className: css(styles.verticalShadow, styles.infoTipContentContainer) }, this.props.children ) ) ); } }); module.exports = InfoTip; },{"aphrodite":22,"react":237}],7:[function(require,module,exports){ "use strict"; var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; /** * KaTeX A11y * A library for converting KaTeX math into readable strings. */ // NOTE(jeresig): We need to keep this file as pure ES5 to avoid import // problems into webapp. /* eslint-disable no-var */ /* global katex */ var stringMap = { "(": "left parenthesis", ")": "right parenthesis", "[": "open bracket", "]": "close bracket", "\\{": "left brace", "\\}": "right brace", "\\lvert": "open vertical bar", "\\rvert": "close vertical bar", "|": "vertical bar", "\\uparrow": "up arrow", "\\Uparrow": "up arrow", "\\downarrow": "down arrow", "\\Downarrow": "down arrow", "\\updownarrow": "up down arrow", "\\leftarrow": "left arrow", "\\Leftarrow": "left arrow", "\\rightarrow": "right arrow", "\\Rightarrow": "right arrow", "\\langle": "open angle", "\\rangle": "close angle", "\\lfloor": "open floor", "\\rfloor": "close floor", "\\int": "integral", "\\intop": "integral", "\\lim": "limit", "\\ln": "natural log", "\\log": "log", "\\sin": "sine", "\\cos": "cosine", "\\tan": "tangent", "\\cot": "cotangent", "\\sum": "sum", "/": "slash", ",": "comma", ".": "point", "-": "negative", "+": "plus", "~": "tilde", ":": "colon", "?": "question mark", "'": "apostrophe", "\\%": "percent", " ": "space", "\\ ": "space", "\\$": "dollar sign", "\\angle": "angle", "\\degree": "degree", "\\circ": "circle", "\\vec": "vector", "\\triangle": "triangle", "\\pi": "pi", "\\prime": "prime", "\\infty": "infinity", "\\alpha": "alpha", "\\beta": "beta", "\\gamma": "gamma", "\\omega": "omega", "\\theta": "theta", "\\sigma": "sigma", "\\lambda": "lambda", "\\tau": "tau", "\\Delta": "delta", "\\delta": "delta", "\\mu": "mu", "\\rho": "rho", "\\nabla": "del", "\\ell": "ell", "\\ldots": "dots" }; var powerMap = { "\\prime": "prime", "\\degree": "degree", "\\circ": "degree" }; var openMap = { "|": "open vertical bar", ".": "" }; var closeMap = { "|": "close vertical bar", ".": "" }; var binMap = { "+": "plus", "-": "minus", "\\pm": "plus minus", "\\cdot": "dot", "*": "times", "/": "divided by", "\\times": "times", "\\div": "divided by", "\\circ": "circle", "\\bullet": "bullet" }; var relMap = { "=": "equals", "\\approx": "approximately equals", "\\neq": "does not equal", "\\ne": "does not equal", "\\geq": "is greater than or equal to", "\\ge": "is greater than or equal to", "\\leq": "is less than or equal to", "\\le": "is less than or equal to", ">": "is greater than", "<": "is less than", "\\leftarrow": "left arrow", "\\Leftarrow": "left arrow", "\\rightarrow": "right arrow", "\\Rightarrow": "right arrow", ":": "colon" }; var buildString = function buildString(str, type, a11yStrings) { if (!str) { return; } var ret; if (type === "open") { ret = str in openMap ? openMap[str] : stringMap[str] || str; } else if (type === "close") { ret = str in closeMap ? closeMap[str] : stringMap[str] || str; } else if (type === "bin") { ret = binMap[str] || str; } else if (type === "rel") { ret = relMap[str] || str; } else { ret = stringMap[str] || str; } // If nothing was found and it's not a plain string or number if (ret === str && !/^\w+$/.test(str)) { // This is likely a case that we'll need to handle throw new Error("KaTeX a11y " + type + " string not found: " + str); } // If the text to add is a number and there is already a string // in the list and the last string is a number then we should // combine them into a single number if (/^\d+$/.test(ret) && a11yStrings.length > 0 && /^\d+$/.test(a11yStrings[a11yStrings.length - 1])) { a11yStrings[a11yStrings.length - 1] += ret; } else if (ret) { a11yStrings.push(ret); } }; var buildRegion = function buildRegion(a11yStrings, callback) { var region = []; a11yStrings.push(region); callback(region); }; var typeHandlers = { accent: function accent(tree, a11yStrings) { buildRegion(a11yStrings, function (a11yStrings) { buildA11yStrings(tree.value.base, a11yStrings); a11yStrings.push("with"); buildA11yStrings(tree.value.accent, a11yStrings); a11yStrings.push("on top"); }); }, bin: function bin(tree, a11yStrings) { buildString(tree.value, "bin", a11yStrings); }, close: function close(tree, a11yStrings) { buildString(tree.value, "close", a11yStrings); }, color: function color(tree, a11yStrings) { var color = tree.value.color.replace(/katex-/, ""); buildRegion(a11yStrings, function (a11yStrings) { a11yStrings.push("start color " + color); buildA11yStrings(tree.value.value, a11yStrings); a11yStrings.push("end color " + color); }); }, delimsizing: function delimsizing(tree, a11yStrings) { if (tree.value.value && tree.value.value !== ".") { buildString(tree.value.value, "normal", a11yStrings); } }, genfrac: function genfrac(tree, a11yStrings) { buildRegion(a11yStrings, function (a11yStrings) { // NOTE: Not sure if this is a safe assumption // hasBarLine true -> fraction, false -> binomial if (tree.value.hasBarLine) { a11yStrings.push("start fraction"); buildString(tree.value.leftDelim, "open", a11yStrings); buildA11yStrings(tree.value.numer, a11yStrings); a11yStrings.push("divided by"); buildA11yStrings(tree.value.denom, a11yStrings); buildString(tree.value.rightDelim, "close", a11yStrings); a11yStrings.push("end fraction"); } else { a11yStrings.push("start binomial"); buildString(tree.value.leftDelim, "open", a11yStrings); buildA11yStrings(tree.value.numer, a11yStrings); a11yStrings.push("over"); buildA11yStrings(tree.value.denom, a11yStrings); buildString(tree.value.rightDelim, "close", a11yStrings); a11yStrings.push("end binomial"); } }); }, // inner katex: function katex(tree, a11yStrings) { a11yStrings.push("KaTeX"); }, leftright: function leftright(tree, a11yStrings) { buildRegion(a11yStrings, function (a11yStrings) { buildString(tree.value.left, "open", a11yStrings); buildA11yStrings(tree.value.body, a11yStrings); buildString(tree.value.right, "close", a11yStrings); }); }, llap: function llap(tree, a11yStrings) { buildA11yStrings(tree.value.body, a11yStrings); }, mathord: function mathord(tree, a11yStrings) { buildA11yStrings(tree.value, a11yStrings); }, op: function op(tree, a11yStrings) { buildString(tree.value.body, "normal", a11yStrings); }, open: function open(tree, a11yStrings) { buildString(tree.value, "open", a11yStrings); }, ordgroup: function ordgroup(tree, a11yStrings) { buildA11yStrings(tree.value, a11yStrings); }, overline: function overline(tree, a11yStrings) { buildRegion(a11yStrings, function (a11yStrings) { a11yStrings.push("start overline"); buildA11yStrings(tree.value.body, a11yStrings); a11yStrings.push("end overline"); }); }, phantom: function phantom(tree, a11yStrings) { a11yStrings.push("empty space"); }, punct: function punct(tree, a11yStrings) { buildString(tree.value, "punct", a11yStrings); }, rel: function rel(tree, a11yStrings) { buildString(tree.value, "rel", a11yStrings); }, rlap: function rlap(tree, a11yStrings) { buildA11yStrings(tree.value.body, a11yStrings); }, rule: function rule(tree, a11yStrings) { // NOTE: Is there something more useful that we can put here? a11yStrings.push("rule"); }, sizing: function sizing(tree, a11yStrings) { buildA11yStrings(tree.value.value, a11yStrings); }, spacing: function spacing(tree, a11yStrings) { a11yStrings.push("space"); }, styling: function styling(tree, a11yStrings) { // We ignore the styling and just pass through the contents buildA11yStrings(tree.value.value, a11yStrings); }, sqrt: function sqrt(tree, a11yStrings) { buildRegion(a11yStrings, function (a11yStrings) { if (tree.value.index) { a11yStrings.push("root"); a11yStrings.push("start index"); buildA11yStrings(tree.value.index, a11yStrings); a11yStrings.push("end index"); } a11yStrings.push("square root of"); buildA11yStrings(tree.value.body, a11yStrings); a11yStrings.push("end square root"); }); }, supsub: function supsub(tree, a11yStrings) { if (tree.value.base) { buildA11yStrings(tree.value.base, a11yStrings); } if (tree.value.sub) { buildRegion(a11yStrings, function (a11yStrings) { a11yStrings.push("start subscript"); buildA11yStrings(tree.value.sub, a11yStrings); a11yStrings.push("end subscript"); }); } var sup = tree.value.sup; if (sup) { // There are some cases that just read better if we don't have // the extra start/end baggage, so we skip the extra text var newPower = powerMap[sup]; var supValue = sup.value; // The value stored inside the sup property is not always // consistent. It could be a string (handled above), an object // with a string property in value, or an array of objects that // have a value property. if (!newPower && supValue) { // If supValue is an object and it has a length of 1 we assume // it's an array that has only a single item in it. This is the // case that we care about and we only check that one value. if ((typeof supValue === "undefined" ? "undefined" : _typeof(supValue)) === "object" && supValue.length === 1) { newPower = powerMap[supValue[0].value]; // This is the case where it's a string in the value property } else { newPower = powerMap[supValue]; } } buildRegion(a11yStrings, function (a11yStrings) { if (newPower) { a11yStrings.push(newPower); return; } a11yStrings.push("start superscript"); buildA11yStrings(tree.value.sup, a11yStrings); a11yStrings.push("end superscript"); }); } }, text: function text(tree, a11yStrings) { if (typeof tree.value !== "string") { buildA11yStrings(tree.value.body, a11yStrings); } else { buildString(tree, "normal", a11yStrings); } }, textord: function textord(tree, a11yStrings) { buildA11yStrings(tree.value, a11yStrings); } }; var buildA11yStrings = function buildA11yStrings(tree, a11yStrings) { a11yStrings = a11yStrings || []; // Handle strings if (typeof tree === "string") { buildString(tree, "normal", a11yStrings); // Handle arrays } else if (tree.constructor === Array) { for (var i = 0; i < tree.length; i++) { buildA11yStrings(tree[i], a11yStrings); } // Everything else is assumed to be an object... } else { if (!tree.type || !(tree.type in typeHandlers)) { throw new Error("KaTeX a11y un-recognized type: " + tree.type); } else { typeHandlers[tree.type](tree, a11yStrings); } } return a11yStrings; }; var renderStrings = function renderStrings(a11yStrings, a11yNode) { var doc = a11yNode.ownerDocument; for (var i = 0; i < a11yStrings.length; i++) { var a11yString = a11yStrings[i]; if (i > 0) { // Note: We insert commas in (not just spaces) to provide // screen readers with some "breathing room". When inserting the // commas the screen reader knows to pause slightly and it provides // an overall better listening experience. a11yNode.appendChild(doc.createTextNode(", ")); } if (typeof a11yString === "string") { a11yNode.appendChild(doc.createTextNode(a11yString)); } else { var newBaseNode = doc.createElement("span"); // NOTE(jeresig): We may want to add in a tabIndex property // to the node here, in order to support keyboard navigation. a11yNode.appendChild(newBaseNode); renderStrings(a11yString, newBaseNode); } } }; var flattenStrings = function flattenStrings(a11yStrings, results) { if (!results) { results = []; } for (var i = 0; i < a11yStrings.length; i++) { var a11yString = a11yStrings[i]; if (typeof a11yString === "string") { results.push(a11yString); } else { flattenStrings(a11yString, results); } } return results; }; var parseMath = function parseMath(text) { // NOTE: `katex` is a global, should be included using require return katex.__parse(text); }; var render = function render(text, a11yNode) { var tree = parseMath(text); var a11yStrings = buildA11yStrings(tree); renderStrings(a11yStrings, a11yNode); }; var flatten = function flatten(array) { var result = []; array.forEach(function (item) { if (Array.isArray(item)) { result = result.concat(flatten(item)); } else { result.push(item); } }); return result; }; var renderString = function renderString(text) { var tree = parseMath(text); var a11yStrings = buildA11yStrings(tree); return flatten(a11yStrings).join(", "); }; if (typeof module !== "undefined") { module.exports = { render: render, renderString: renderString, parseMath: parseMath }; } else { undefined.katexA11yRender = render; } },{}],8:[function(require,module,exports){ 'use strict'; /* Create a new "layer" on the page, like a modal or overlay. * * const LayeredComponent = React.createClass({ * mixins: [LayeredComponentMixin], * render: function() { * // render like usual * }, * renderLayer: function() { * // render a separate layer (the modal or overlay) * } * }); * * From http://jsfiddle.net/LBAr8/ */ var React = require('react'); var ReactDOM = require("react-dom"); var LayeredComponentMixin = { componentDidMount: function componentDidMount() { // Appending to the body is easier than managing the z-index of // everything on the page. It's also better for accessibility and // makes stacking a snap (since components will stack in mount order). this._layer = document.createElement('div'); document.body.appendChild(this._layer); this._renderLayer(); }, componentDidUpdate: function componentDidUpdate() { this._renderLayer(); }, componentWillUnmount: function componentWillUnmount() { this._unrenderLayer(); document.body.removeChild(this._layer); }, _renderLayer: function _renderLayer() { // By calling this method in componentDidMount() and // componentDidUpdate(), you're effectively creating a "wormhole" that // funnels React's hierarchical updates through to a DOM node on an // entirely different part of the page. var layerElement = this.renderLayer(); // Renders can return null, but React.render() doesn't like being asked // to render null. If we get null back from renderLayer(), just render // a noscript element, like React does when an element's render returns // null. if (layerElement === null) { ReactDOM.render(React.createElement('noscript', null), this._layer); } else { ReactDOM.render(layerElement, this._layer); } if (this.layerDidMount) { this.layerDidMount(this._layer); } }, _unrenderLayer: function _unrenderLayer() { if (this.layerWillUnmount) { this.layerWillUnmount(this._layer); } ReactDOM.unmountComponentAtNode(this._layer); } }; module.exports = LayeredComponentMixin; },{"react":237,"react-dom":73}],9:[function(require,module,exports){ "use strict"; var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; /* Render a bootstrap modal. * * TODO(joel) figure out how to use the header, body, and footer styles * * Example: * * <Modal onClose={this.props.onClose}> * <div className="modal-header"> * <h2>{header}</h2> * </div> * <div className="modal-body"> * {body} * </div> * <div className="modal-footer"> * {footer} * </div> * </Modal> */ var aphrodite = require("aphrodite"); var React = require("react"); var css = aphrodite.css; var StyleSheet = aphrodite.StyleSheet; var styles = StyleSheet.create({ modalStyle: { position: "fixed", width: "500px", margin: "0 0 0 -250px", top: "60px", left: "50%", backgroundColor: "white", border: "1px solid rgba(0, 0, 0, 0.3)", borderRadius: "6px", zIndex: 1050 }, modalBackdropStyle: { opacity: 0.7, position: "fixed", top: 0, right: 0, bottom: 0, left: 0, zIndex: 1040, backgroundColor: "black" } }); var Modal = React.createClass({ displayName: "Modal", propTypes: { children: React.PropTypes.node, className: React.PropTypes.string, // Close the modal when esc is pressed? Defaults to true. keyboard: React.PropTypes.bool, onClose: React.PropTypes.func, // TODO(joel) reimplement // Bootstrap modal's backdrop argument: Includes a modal-backdrop // element. Alternatively, specify static for a backdrop which doesn't // close the modal on click. Defaults to true. backdrop: React.PropTypes.oneOfType([React.PropTypes.bool, React.PropTypes.string]) }, getDefaultProps: function getDefaultProps() { return { className: "", onClose: function onClose() {}, keyboard: true, backdrop: true }; }, componentDidMount: function componentDidMount() { window.addEventListener("keydown", this._listenForEsc, true); }, componentWillUnmount: function componentWillUnmount() { window.removeEventListener("keydown", this._listenForEsc, true); }, _listenForEsc: function _listenForEsc(event) { if (this.props.keyboard && (event.key === "Escape" || event.keyCode === 27)) { this.props.onClose(); } }, render: function render() { var className = [css(styles.modalStyle), this.props.className, "modal"].join(" "); var modal = React.createElement( "div", _extends({}, this.props, { tabIndex: "-1", className: className }), this.props.children ); var backdrop = React.createElement("div", { className: css(styles.modalBackdropStyle) }); return React.createElement( "div", null, modal, backdrop ); } }); module.exports = Modal; },{"aphrodite":22,"react":237}],10:[function(require,module,exports){ 'use strict'; /* MultiButtonGroup is an aesthetically pleasing group of buttons, * which allows multiple buttons to be selected at the same time. * * The class requires these properties: * buttons - an array of objects with keys: * "value": this is the value returned when the button is selected * "content": this is the JSX shown within the button, typically a string * that gets rendered as the button's display text * "title": this is the title-text shown on hover * onChange - a function that is provided with an array of the updated * values (which it then is responsible for updating) * * The class has these optional properties: * values - an array of the initial values of the buttons selected. * * Requires stylesheets/perseus-admin-package/editor.less to look nice. */ var React = require('react'); var ReactDOM = require("react-dom"); var styles = require('./styles.js'); var css = require("aphrodite").css; var MultiButtonGroup = React.createClass({ displayName: 'MultiButtonGroup', propTypes: { values: React.PropTypes.arrayOf(React.PropTypes.any), buttons: React.PropTypes.arrayOf(React.PropTypes.shape({ value: React.PropTypes.any.isRequired, content: React.PropTypes.node, title: React.PropTypes.string })).isRequired, onChange: React.PropTypes.func.isRequired, allowEmpty: React.PropTypes.bool }, getDefaultProps: function getDefaultProps() { return { values: [], allowEmpty: true }; }, focus: function focus() { ReactDOM.findDOMNode(this).focus(); return true; }, toggleSelect: function toggleSelect(newValue) { var values = (this.props.values || []).slice(0); var allowEmpty = this.props.allowEmpty; if (values.indexOf(newValue) >= 0 && (values.length > 1 || allowEmpty)) { // If the value is already selected, unselect it values.splice(values.indexOf(newValue), 1); } else { // Otherwise merge with other values and return if (values.indexOf(newValue) < 0) { values.push(newValue); } } this.props.onChange(values); }, render: function render() { var _this = this; var values = this.props.values || []; var buttons = this.props.buttons.map(function (button, i) { var selected = values.indexOf(button.value) >= 0; return React.createElement( 'button', { title: button.title, type: 'button', id: "" + i, key: "" + i, ref: "button" + i, className: css(styles.button.buttonStyle, selected && styles.button.selectedStyle), onClick: _this.toggleSelect.bind(_this, button.value) }, button.content || "" + button.value ); }); var outerStyle = { display: 'inline-block' }; return React.createElement( 'div', { style: outerStyle }, buttons ); } }); module.exports = MultiButtonGroup; },{"./styles.js":14,"aphrodite":22,"react":237,"react-dom":73}],11:[function(require,module,exports){ "use strict"; /* This mixin provides a simple setInterval method. * * Example: * * const Component = React.createClass({ * ... * componentDidMount: function() { * this.setInterval(this.doSomething, 1000); * this.setInterval(this.doSomethingElse, 5000); * } * ... * }); * * doSomething is called every second and doSomethingElse is called every five * seconds. Their intervals will be canceled automatically when the component * unmounts. */ var SetIntervalMixin = { componentWillMount: function componentWillMount() { this.intervals = []; }, setInterval: function (_setInterval) { function setInterval(_x, _x2) { return _setInterval.apply(this, arguments); } setInterval.toString = function () { return _setInterval.toString(); }; return setInterval; }(function (fn, ms) { this.intervals.push(setInterval(fn, ms)); }), componentWillUnmount: function componentWillUnmount() { this.intervals.forEach(clearInterval); } }; module.exports = SetIntervalMixin; },{}],12:[function(require,module,exports){ 'use strict'; var React = require('react'); var ReactDOM = require("react-dom"); var PT = React.PropTypes; // Takes an array of components to sort var SortableArea = React.createClass({ displayName: 'SortableArea', propTypes: { className: PT.string, components: PT.arrayOf(PT.node).isRequired, onReorder: PT.func.isRequired, style: PT.any, verify: PT.func }, getDefaultProps: function getDefaultProps() { return { verify: function verify() { return true; } }; }, getInitialState: function getInitialState() { return { // index of the component being dragged dragging: null, components: this.props.components }; }, // Firefox refuses to drag an element unless you set data on it. Hackily // add data each time an item is dragged. componentDidMount: function componentDidMount() { this._setDragEvents(); }, componentWillReceiveProps: function componentWillReceiveProps(nextProps) { this.setState({ components: nextProps.components }); }, componentDidUpdate: function componentDidUpdate() { this._setDragEvents(); }, // Alternatively send each handler to each component individually, // partially applied onDragStart: function onDragStart(startIndex) { this.setState({ dragging: startIndex }); }, onDrop: function onDrop() { // tell the parent component this.setState({ dragging: null }); this.props.onReorder(this.state.components); }, onDragEnter: function onDragEnter(enterIndex) { // When a label is first dragged it triggers a dragEnter with itself, // which we don't care about. if (this.state.dragging === enterIndex) { return; } var newComponents = this.state.components.slice(); // splice the tab out of its old position var removed = newComponents.splice(this.state.dragging, 1); // ... and into its new position newComponents.splice(enterIndex, 0, removed[0]); var verified = this.props.verify(newComponents); if (verified) { this.setState({ dragging: enterIndex, components: newComponents }); } return verified; }, _listenEvent: function _listenEvent(e) { e.dataTransfer.setData('hackhackhack', 'because browsers!'); }, _cancelEvent: function _cancelEvent(e) { // prevent the browser from redirecting to 'because browsers!' e.preventDefault(); }, _setDragEvents: function _setDragEvents() { this._dragItems = this._dragItems || []; var items = ReactDOM.findDOMNode(this).querySelectorAll('[draggable=true]'); var oldItems = []; var newItems = []; for (var i = 0; i < this._dragItems.length; i++) { var item = this._dragItems[i]; if (items.indexOf(item) < 0) { oldItems.push(item); } } for (var _i = 0; _i < items.length; _i++) { var _item = items[_i]; if (this._dragItems.indexOf(_item) < 0) { newItems.push(_item); } } for (var _i2 = 0; _i2 < newItems.length; _i2++) { var dragItem = newItems[_i2]; dragItem.addEventListener('dragstart', this._listenEvent); dragItem.addEventListener('drop', this._cancelEvent); } for (var _i3 = 0; _i3 < oldItems.length; _i3++) { var _dragItem = oldItems[_i3]; _dragItem.removeEventListener('dragstart', this._listenEvent);