react-components
Version:
React components used by Khan Academy
1,554 lines (1,328 loc) • 1.06 MB
JavaScript
(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);