diff --git a/.babelrc b/.babelrc index 667e607d..d57acdde 100644 --- a/.babelrc +++ b/.babelrc @@ -1,7 +1,8 @@ { "presets": [ ["es2015", { - "modules": false + "modules": false, + "loose": true }], "flow" ], diff --git a/.eslintrc b/.eslintrc index 7543ba04..09b94bf7 100644 --- a/.eslintrc +++ b/.eslintrc @@ -70,6 +70,13 @@ "valid-jsdoc": 0, "require-jsdoc": 0 }, + "overrides": [{ + "files": ["katex.js", "src/**/*.js"], + "excludedFiles": "unicodeMake.js", + "rules": { + "no-restricted-syntax": [2, "ForOfStatement", "ClassDeclaration[superClass]", "ClassExpression[superClass]"] + } + }], "env": { "es6": true, "node": true, diff --git a/src/Namespace.js b/src/Namespace.js index 410dc42d..7b915dc6 100644 --- a/src/Namespace.js +++ b/src/Namespace.js @@ -46,11 +46,13 @@ export default class Namespace { "to pop global namespace; please report this as a bug"); } const undefs = this.undefStack.pop(); - for (const undef of Object.getOwnPropertyNames(undefs)) { - if (undefs[undef] === undefined) { - delete this.current[undef]; - } else { - this.current[undef] = undefs[undef]; + for (const undef in undefs) { + if (undefs.hasOwnProperty(undef)) { + if (undefs[undef] === undefined) { + delete this.current[undef]; + } else { + this.current[undef] = undefs[undef]; + } } } } diff --git a/src/SourceLocation.js b/src/SourceLocation.js index 4cfe6578..68da356c 100644 --- a/src/SourceLocation.js +++ b/src/SourceLocation.js @@ -14,7 +14,8 @@ export default class SourceLocation { this.lexer = lexer; this.start = start; this.end = end; - Object.freeze(this); // Immutable to allow sharing in range(). + // $FlowFixMe, do not polyfill + Object["freeze"](this); // Immutable to allow sharing in range(). } /** @@ -40,4 +41,3 @@ export default class SourceLocation { } } } - diff --git a/src/buildCommon.js b/src/buildCommon.js index aaab7b75..1ebc2252 100644 --- a/src/buildCommon.js +++ b/src/buildCommon.js @@ -315,7 +315,8 @@ const sizeElementFromChildren = function( let depth = 0; let maxFontSize = 0; - for (const child of elem.children) { + for (let i = 0; i < elem.children.length; i++) { + const child = elem.children[i]; if (child.height > height) { height = child.height; } @@ -490,7 +491,8 @@ const getVListChildrenAndDepth = function(params: VListParam): { // We always start at the bottom, so calculate the bottom by adding up // all the sizes let bottom = params.positionData; - for (const child of params.children) { + for (let i = 0; i < params.children.length; i++) { + const child = params.children[i]; bottom -= child.type === "kern" ? child.size : child.elem.height + child.elem.depth; @@ -531,7 +533,8 @@ const makeVList = function(params: VListParam, options: Options): DomSpan { // be positioned precisely without worrying about font ascent and // line-height. let pstrutSize = 0; - for (const child of children) { + for (let i = 0; i < children.length; i++) { + const child = children[i]; if (child.type === "elem") { const elem = child.elem; pstrutSize = Math.max(pstrutSize, elem.maxFontSize, elem.height); @@ -546,7 +549,8 @@ const makeVList = function(params: VListParam, options: Options): DomSpan { let minPos = depth; let maxPos = depth; let currPos = depth; - for (const child of children) { + for (let i = 0; i < children.length; i++) { + const child = children[i]; if (child.type === "kern") { currPos += child.size; } else { diff --git a/src/domTree.js b/src/domTree.js index e56944fa..450bac9c 100644 --- a/src/domTree.js +++ b/src/domTree.js @@ -7,6 +7,9 @@ * work with the DOM. * * Similar functions for working with MathML nodes exist in mathMLTree.js. + * + * TODO: refactor `span` and `anchor` into common superclass when + * target environments support class inheritance */ import {scriptFromCodepoint} from "./unicodeScripts"; import utils from "./utils"; @@ -25,6 +28,103 @@ const createClass = function(classes: string[]): string { return classes.filter(cls => cls).join(" "); }; +const initNode = function( + classes?: string[], + options?: Options, + style?: CssStyle, +) { + this.classes = classes || []; + this.attributes = {}; + this.height = 0; + this.depth = 0; + this.maxFontSize = 0; + this.style = style || {}; + if (options) { + if (options.style.isTight()) { + this.classes.push("mtight"); + } + const color = options.getColor(); + if (color) { + this.style.color = color; + } + } +}; + +/** + * Convert into an HTML node + */ +const toNode = function(tagName: string): HTMLElement { + const node = document.createElement(tagName); + + // Apply the class + node.className = createClass(this.classes); + + // Apply inline styles + for (const style in this.style) { + if (this.style.hasOwnProperty(style)) { + // $FlowFixMe Flow doesn't seem to understand span.style's type. + node.style[style] = this.style[style]; + } + } + + // Apply attributes + for (const attr in this.attributes) { + if (this.attributes.hasOwnProperty(attr)) { + node.setAttribute(attr, this.attributes[attr]); + } + } + + // Append the children, also as HTML nodes + for (let i = 0; i < this.children.length; i++) { + node.appendChild(this.children[i].toNode()); + } + + return node; +}; + +/** + * Convert into an HTML markup string + */ +const toMarkup = function(tagName: string): string { + let markup = `<${tagName}`; + + // Add the class + if (this.classes.length) { + markup += ` class="${utils.escape(createClass(this.classes))}"`; + } + + let styles = ""; + + // Add the styles, after hyphenation + for (const style in this.style) { + if (this.style.hasOwnProperty(style)) { + styles += `${utils.hyphenate(style)}:${this.style[style]};`; + } + } + + if (styles) { + markup += ` style="${utils.escape(styles)}"`; + } + + // Add the attributes + for (const attr in this.attributes) { + if (this.attributes.hasOwnProperty(attr)) { + markup += ` ${attr}="${utils.escape(this.attributes[attr])}"`; + } + } + + markup += ">"; + + // Add the markup of the children, also as markup + for (let i = 0; i < this.children.length; i++) { + markup += this.children[i].toMarkup(); + } + + markup += ``; + + return markup; +}; + export type CssStyle = {[name: string]: string}; export interface HtmlDomNode extends VirtualNode { @@ -47,8 +147,16 @@ export type SvgChildNode = pathNode | lineNode; export type documentFragment = tree.documentFragment; -export class HtmlDomContainer - implements HtmlDomNode { +/** + * This node represents a span node, with a className, a list of children, and + * an inline style. It also contains information about its height, depth, and + * maxFontSize. + * + * Represents two types with different uses: SvgSpan to wrap an SVG and DomSpan + * otherwise. This typesafety is important when HTML builders access a span's + * children. + */ +class span implements HtmlDomNode { children: ChildType[]; attributes: {[string]: string}; classes: string[]; @@ -64,26 +172,12 @@ export class HtmlDomContainer options?: Options, style?: CssStyle, ) { - this.classes = classes || []; + initNode.call(this, classes, options, style); this.children = children || []; - this.attributes = {}; - this.height = 0; - this.depth = 0; - this.maxFontSize = 0; - this.style = Object.assign({}, style); - if (options) { - if (options.style.isTight()) { - this.classes.push("mtight"); - } - const color = options.getColor(); - if (color) { - this.style.color = color; - } - } } /** - * Sets an arbitrary attribute on the node. Warning: use this wisely. Not + * Sets an arbitrary attribute on the span. Warning: use this wisely. Not * all browsers support attributes the same, and having too many custom * attributes is probably bad. */ @@ -104,119 +198,27 @@ export class HtmlDomContainer return false; } - tagName(): string { - throw new Error("use of generic HtmlDomContainer tagName"); - } - - /** - * Convert into an HTML node - */ toNode(): HTMLElement { - const node = document.createElement(this.tagName()); - - // Apply the class - node.className = createClass(this.classes); - - // Apply inline styles - for (const style in this.style) { - if (Object.prototype.hasOwnProperty.call(this.style, style)) { - // $FlowFixMe Flow doesn't seem to understand node.style's type. - node.style[style] = this.style[style]; - } - } - - // Apply attributes - for (const attr in this.attributes) { - if (this.attributes.hasOwnProperty(attr)) { - node.setAttribute(attr, this.attributes[attr]); - } - } - - // Append the children, also as HTML nodes - for (let i = 0; i < this.children.length; i++) { - node.appendChild(this.children[i].toNode()); - } - - return node; + return toNode.call(this, "span"); } - /** - * Convert into an HTML markup string - */ toMarkup(): string { - let markup = "<" + this.tagName(); - - // Add the class - if (this.classes.length) { - markup += ` class="${utils.escape(createClass(this.classes))}"`; - } - - let styles = ""; - - // Add the styles, after hyphenation - for (const style in this.style) { - if (this.style.hasOwnProperty(style)) { - styles += utils.hyphenate(style) + ":" + this.style[style] + ";"; - } - } - - if (styles) { - markup += ` style="${utils.escape(styles)}"`; - } - - // Add the attributes - for (const attr in this.attributes) { - if (this.attributes.hasOwnProperty(attr)) { - markup += " " + attr + "=\""; - markup += utils.escape(this.attributes[attr]); - markup += "\""; - } - } - - markup += ">"; - - // Add the markup of the children, also as markup - for (let i = 0; i < this.children.length; i++) { - markup += this.children[i].toMarkup(); - } - - markup += ``; - - return markup; + return toMarkup.call(this, "span"); } } /** - * This node represents a span node, with a className, a list of children, and - * an inline style. It also contains information about its height, depth, and - * maxFontSize. - * - * Represents two types with different uses: SvgSpan to wrap an SVG and DomSpan - * otherwise. This typesafety is important when HTML builders access a span's - * children. + * This node represents an anchor () element with a hyperlink. See `span` + * for further details. */ -class span extends HtmlDomContainer { - constructor( - classes?: string[], - children?: ChildType[], - options?: Options, - style?: CssStyle, - ) { - super(classes, children, options, style); - } - - tagName() { - return "span"; - } -} - -/** - * This node represents an anchor () element with a hyperlink, a list of classes, - * a list of children, and an inline style. It also contains information about its - * height, depth, and maxFontSize. - */ -class anchor extends HtmlDomContainer { - href: string; +class anchor implements HtmlDomNode { + children: HtmlDomNode[]; + attributes: {[string]: string}; + classes: string[]; + height: number; + depth: number; + maxFontSize: number; + style: CssStyle; constructor( href: string, @@ -224,12 +226,29 @@ class anchor extends HtmlDomContainer { children: HtmlDomNode[], options: Options, ) { - super(classes, children, options); + initNode.call(this, classes, options); + this.children = children || []; this.setAttribute('href', href); } - tagName() { - return "a"; + setAttribute(attribute: string, value: string) { + this.attributes[attribute] = value; + } + + hasClass(className: string): boolean { + return utils.contains(this.classes, className); + } + + tryCombine(sibling: HtmlDomNode): boolean { + return false; + } + + toNode(): HTMLElement { + return toNode.call(this, "a"); + } + + toMarkup(): string { + return toMarkup.call(this, "a"); } } @@ -274,7 +293,7 @@ class symbolNode implements HtmlDomNode { this.skew = skew || 0; this.width = width || 0; this.classes = classes || []; - this.style = Object.assign({}, style); + this.style = style || {}; this.maxFontSize = 0; // Mark text from non-Latin scripts with specific classes so that we @@ -534,13 +553,13 @@ export function assertSymbolDomNode( } } -export function assertDomContainer( +export function assertSpan( group: HtmlDomNode, -): HtmlDomContainer { - if (group instanceof HtmlDomContainer) { +): span { + if (group instanceof span) { return group; } else { - throw new Error(`Expected HtmlDomContainer but got ${String(group)}.`); + throw new Error(`Expected span but got ${String(group)}.`); } } diff --git a/src/functions/accent.js b/src/functions/accent.js index bb13e638..f811b51d 100644 --- a/src/functions/accent.js +++ b/src/functions/accent.js @@ -5,7 +5,7 @@ import mathMLTree from "../mathMLTree"; import utils from "../utils"; import stretchy from "../stretchy"; import ParseNode, {assertNodeType, checkNodeType} from "../ParseNode"; -import {assertDomContainer, assertSymbolDomNode} from "../domTree"; +import {assertSpan, assertSymbolDomNode} from "../domTree"; import * as html from "../buildHTML"; import * as mml from "../buildMathML"; @@ -40,7 +40,7 @@ export const htmlBuilder: HtmlBuilderSupSub<"accent"> = (grp, options) => { // Rerender the supsub group with its new base, and store that // result. - supSubGroup = assertDomContainer(html.buildGroup(supSub, options)); + supSubGroup = assertSpan(html.buildGroup(supSub, options)); // reset original base supSub.value.base = group; diff --git a/src/functions/font.js b/src/functions/font.js index 3a495d43..91b810b8 100644 --- a/src/functions/font.js +++ b/src/functions/font.js @@ -86,18 +86,10 @@ defineFunction({ }, }); -const oldFontFuncsMap = { - "\\rm": "mathrm", - "\\sf": "mathsf", - "\\tt": "mathtt", - "\\bf": "mathbf", - "\\it": "mathit", -}; - // Old font changing functions defineFunction({ type: "font", - names: Object.keys(oldFontFuncsMap), + names: ["\\rm", "\\sf", "\\tt", "\\bf", "\\it"], props: { numArgs: 0, allowedInText: true, @@ -106,7 +98,7 @@ defineFunction({ const {mode} = parser; parser.consumeSpaces(); const body = parser.parseExpression(true, breakOnTokenText); - const style = oldFontFuncsMap[funcName]; + const style = `math${funcName.slice(1)}`; return new ParseNode("font", { type: "font", diff --git a/src/functions/operatorname.js b/src/functions/operatorname.js index 6249ef5d..4d872b53 100644 --- a/src/functions/operatorname.js +++ b/src/functions/operatorname.js @@ -39,7 +39,8 @@ defineFunction({ const expression = html.buildExpression( groupValue, options.withFont("mathrm"), true); - for (const child of expression) { + for (let i = 0; i < expression.length; i++) { + const child = expression[i]; if (child instanceof domTree.symbolNode) { // Per amsopn package, // change minus to hyphen and \ast to asterisk @@ -60,7 +61,8 @@ defineFunction({ // Is expression a string or has it something like a fraction? let isAllString = true; // default - for (const node of expression) { + for (let i = 0; i < expression.length; i++) { + const node = expression[i]; if (node instanceof mathMLTree.SpaceNode) { // Do nothing } else if (node instanceof mathMLTree.MathNode) { diff --git a/src/mathMLTree.js b/src/mathMLTree.js index 1f31e515..2761db34 100644 --- a/src/mathMLTree.js +++ b/src/mathMLTree.js @@ -80,8 +80,8 @@ export class MathNode implements MathDomNode { } } - for (const child of this.children) { - node.appendChild(child.toNode()); + for (let i = 0; i < this.children.length; i++) { + node.appendChild(this.children[i].toNode()); } return node; diff --git a/src/unicodeScripts.js b/src/unicodeScripts.js index 46483730..cc590b1e 100644 --- a/src/unicodeScripts.js +++ b/src/unicodeScripts.js @@ -84,8 +84,10 @@ const scriptData: Array