<commit_before>import isArray from '../util/isArray'
import isArrayEqual from '../util/isArrayEqual'
import keys from '../util/keys'
import * as selectionHelpers from '../model/selectionHelpers'
import Surface from './Surface'
import RenderingEngine from './RenderingEngine'
import { getContainerPosition } from '../model/documentHelpers'

/**
 * Represents an editor for content rendered in a flow, such as a manuscript.
 *
 * @prop {String} name unique editor name
 * @prop {String} containerPath container id
 *
 * @example
 *
 * Create a full-fledged `ContainerEditor` for the `body` container of a document.
 * Allow Strong and Emphasis annotations and to switch text types between paragraph
 * and heading at level 1.
 *
 * ```js
 * $$(ContainerEditor, {
 *   name: 'bodyEditor',
 *   containerPath: ['body', 'nodes']
 * })
 * ```
 */
export default class ContainerEditor extends Surface {
  constructor (parent, props, el) {
    // TODO consolidate this - how is it used actually?
    props.containerPath = props.containerPath || props.node.getContentPath()
    props.name = props.name || props.containerPath.join('.') || props.node.id

    super(parent, props, el)

    this.containerPath = this.props.containerPath
    if (!isArray(this.containerPath)) {
      throw new Error("Property 'containerPath' is mandatory.")
    }

    this._deriveInternalState(this.props)
  }

  // Note: this component is self managed
  shouldRerender (newProps) {
    if (newProps.disabled !== this.props.disabled) return true
    // TODO: we should still detect when the document has changed,
    // see https://github.com/substance/substance/issues/543
    return false
  }

  willReceiveProps (newProps) {
    super.willReceiveProps.apply(this, arguments)
    this._deriveInternalState(newProps)
  }

  didMount () {
    super.didMount.apply(this, arguments)
    let editorSession = this.getEditorSession()
    editorSession.onUpdate('document', this._onContainerChanged, this, {
      path: this.getContainerPath()
    })
  }

  dispose () {
    super.dispose.apply(this, arguments)
    let editorSession = this.getEditorSession()
    editorSession.off(this)
  }

  render ($$) {
    let el = super.render($$)

    let doc = this.getDocument()
    let containerPath = this.getContainerPath()
    el.attr('data-id', containerPath.join('.'))

    // native spellcheck
    el.attr('spellcheck', this.props.spellcheck === 'native')

    let ids = doc.get(containerPath)
    el.append(
      ids.map((id, index) => {
        return this._renderNode($$, doc.get(id), index)
      })
    )

    // No editing if disabled by user or container is empty
    if (!this.props.disabled && !this.isEmpty()) {
      el.addClass('sm-enabled')
      el.setAttribute('contenteditable', true)
    }

    return el
  }

  _getClassNames () {
    return 'sc-container-editor sc-surface'
  }

  selectFirst () {
    let doc = this.getDocument()
    let containerPath = this.getContainerPath()
    let nodeIds = doc.get()
    if (nodeIds.length > 0) {
      const editorSession = this.getEditorSession()
      const first = doc.get(nodeIds[0])
      selectionHelpers.setCursor(editorSession, first, containerPath, 'before')
    }
  }

  _renderNode ($$, node, nodeIndex) {
    if (!node) throw new Error('Illegal argument')
    let ComponentClass = this._getNodeComponentClass(node)
    let props = this._getNodeProps(node)
    return $$(ComponentClass, props).ref(node.id)
  }

  _getNodeComponentClass (node) {
    let ComponentClass = this.getComponent(node.type)
    if (node.isText() || ComponentClass.prototype._isCustomNodeComponent || ComponentClass.prototype._isIsolatedNodeComponent) {
      return ComponentClass
    } else {
      return this.getComponent('isolated-node')
    }
  }

  _deriveInternalState (props) {
    let _state = this._state
    if (!props.hasOwnProperty('enabled') || props.enabled) {
      _state.enabled = true
    } else {
      _state.enabled = false
    }
  }

  _selectNextIsolatedNode (direction) {
    let selState = this.getEditorSession().getSelectionState()
    let node = (direction === 'left') ? selState.previousNode : selState.nextNode
    let isIsolatedNode = !node.isText() && !node.isList()
    if (!node || !isIsolatedNode) return false
    if (
      (direction === 'left' && selState.isFirst) ||
      (direction === 'right' && selState.isLast)
    ) {
      this.getEditorSession().setSelection({
        type: 'node',
        nodeId: node.id,
        containerPath: this.getContainerPath(),
        surfaceId: this.id
      })
      return true
    }
    return false
  }

  _handleLeftOrRightArrowKey (event) {
    event.stopPropagation()
    const doc = this.getDocument()
    const sel = this.getEditorSession().getSelection()
    const left = (event.keyCode === keys.LEFT)
    const right = !left
    const direction = left ? 'left' : 'right'

    if (sel && !sel.isNull()) {
      let containerPath = sel.containerPath
      // Don't react if we are at the boundary of the document
      if (sel.isNodeSelection()) {
        let nodeIds = doc.get(containerPath)
        let nodePos = getContainerPosition(doc, containerPath, sel.getNodeId())
        if ((left && nodePos === 0) || (right && nodePos === nodeIds.length - 1)) {
          event.preventDefault()
          return
        }
      }
      if (sel.isNodeSelection() && !event.shiftKey) {
        this.domSelection.collapse(direction)
      }
    }

    this._delayed(() => {
      this._updateModelSelection({ direction })
    })
  }

  _handleUpOrDownArrowKey (event) {
    event.stopPropagation()
    const doc = this.getDocument()
    const sel = this.getEditorSession().getSelection()
    const up = (event.keyCode === keys.UP)
    const down = !up
    const direction = up ? 'left' : 'right'

    if (sel && !sel.isNull()) {
      let containerPath = sel.containerPath
      // Don't react if we are at the boundary of the document
      if (sel.isNodeSelection()) {
        let nodeIds = doc.get(containerPath)
        let nodePos = getContainerPosition(doc, containerPath, sel.getNodeId())
        if ((up && nodePos === 0) || (down && nodePos === nodeIds.length - 1)) {
          event.preventDefault()
          return
        }
        // Unfortunately we need to navigate out of an isolated node
        // manually, as even Chrome on Win is not able to do it.
        let editorSession = this.getEditorSession()
        // TODO the following fixes the mentioned problem for
        // regular UP/DOWN (non expanding)
        // For SHIFT+DOWN it happens to work, and only SHIFT-UP when started as NodeSelection needs to be fixed
        if (!event.shiftKey) {
          event.preventDefault()
          if (up) {
            let prev = doc.get(nodeIds[nodePos - 1])
            selectionHelpers.setCursor(editorSession, prev, containerPath, 'after')
            return
          } else {
            let next = doc.get(nodeIds[nodePos + 1])
            selectionHelpers.setCursor(editorSession, next, containerPath, 'before')
            return
          }
        }
      }
    }

    this._delayed(() => {
      this._updateModelSelection({ direction })
    })
  }

  _handleTabKey (event) {
    const editorSession = this.getEditorSession()
    const sel = editorSession.getSelection()
    // EXPERIMENTAL: using TAB to enter an isolated node
    if (sel.isNodeSelection() && sel.isFull()) {
      const comp = this.refs[sel.getNodeId()]
      if (comp && selectionHelpers.stepIntoIsolatedNode(editorSession, comp)) {
        event.preventDefault()
        event.stopPropagation()
        return
      }
    }
    super._handleTabKey(event)
  }

  __handleTab (e) {
    e.preventDefault()
    if (e.shiftKey) {
      this.getEditorSession().transaction((tx) => {
        tx.dedent()
      }, { action: 'dedent' })
    } else {
      this.getEditorSession().transaction((tx) => {
        tx.indent()
      }, { action: 'indent' })
    }
  }

  // Used by Clipboard
  isContainerEditor () {
    return true
  }

  /**
    Returns the containerPath the editor is bound to
  */
  getContainerPath () {
    return this.containerPath
  }

  isEmpty () {
    let ids = this.getDocument().get(this.containerPath)
    return (!ids || ids.length === 0)
  }

  isEditable () {
    return super.isEditable.call(this) && !this.isEmpty()
  }

  // called by flow when subscribed resources have been updated
  _onContainerChanged (change) {
    let doc = this.getDocument()
    // first update the container
    let renderContext = RenderingEngine.createContext(this)
    let $$ = renderContext.$$
    let containerPath = this.getContainerPath()
    for (let i = 0; i < change.ops.length; i++) {
      let op = change.ops[i]
      if (isArrayEqual(op.path, containerPath)) {
        if (op.type === 'update') {
          let diff = op.diff
          if (diff.type === 'insert') {
            let nodeId = diff.getValue()
            let node = doc.get(nodeId)
            let nodeEl
            if (node) {
              nodeEl = this._renderNode($$, node)
            } else {
              // node does not exist anymore
              // so we insert a stub element, so that the number of child
              // elements is consistent
              nodeEl = $$('div')
            }
            this.insertAt(diff.getOffset(), nodeEl)
          } else if (diff.type === 'delete') {
            this.removeAt(diff.getOffset())
          }
        } else {
          this.empty()
          this.rerender()
        }
      }
    }
  }

  get _isContainerEditor () { return true }
}
<commit_msg>Make ContainerEditor initialization customizable.<commit_after>import isArray from '../util/isArray'
import isArrayEqual from '../util/isArrayEqual'
import keys from '../util/keys'
import * as selectionHelpers from '../model/selectionHelpers'
import Surface from './Surface'
import RenderingEngine from './RenderingEngine'
import { getContainerPosition } from '../model/documentHelpers'

/**
 * Represents an editor for content rendered in a flow, such as a manuscript.
 *
 * @prop {String} name unique editor name
 * @prop {String} containerPath container id
 *
 * @example
 *
 * Create a full-fledged `ContainerEditor` for the `body` container of a document.
 * Allow Strong and Emphasis annotations and to switch text types between paragraph
 * and heading at level 1.
 *
 * ```js
 * $$(ContainerEditor, {
 *   name: 'bodyEditor',
 *   containerPath: ['body', 'nodes']
 * })
 * ```
 */
export default class ContainerEditor extends Surface {
  constructor (parent, props, el) {
    // TODO consolidate this - how is it used actually?
    props.containerPath = props.containerPath || props.node.getContentPath()
    props.name = props.name || props.containerPath.join('.') || props.node.id

    super(parent, props, el)
  }

  _initialize () {
    super._initialize()

    this.containerPath = this.props.containerPath
    if (!isArray(this.containerPath)) {
      throw new Error("Property 'containerPath' is mandatory.")
    }

    this._deriveInternalState(this.props)
  }

  // Note: this component is self managed
  shouldRerender (newProps) {
    if (newProps.disabled !== this.props.disabled) return true
    // TODO: we should still detect when the document has changed,
    // see https://github.com/substance/substance/issues/543
    return false
  }

  willReceiveProps (newProps) {
    super.willReceiveProps.apply(this, arguments)
    this._deriveInternalState(newProps)
  }

  didMount () {
    super.didMount.apply(this, arguments)
    let editorSession = this.getEditorSession()
    editorSession.onUpdate('document', this._onContainerChanged, this, {
      path: this.getContainerPath()
    })
  }

  dispose () {
    super.dispose.apply(this, arguments)
    let editorSession = this.getEditorSession()
    editorSession.off(this)
  }

  render ($$) {
    let el = super.render($$)

    let doc = this.getDocument()
    let containerPath = this.getContainerPath()
    el.attr('data-id', containerPath.join('.'))

    // native spellcheck
    el.attr('spellcheck', this.props.spellcheck === 'native')

    let ids = doc.get(containerPath)
    el.append(
      ids.map((id, index) => {
        return this._renderNode($$, doc.get(id), index)
      })
    )

    // No editing if disabled by user or container is empty
    if (!this.props.disabled && !this.isEmpty()) {
      el.addClass('sm-enabled')
      el.setAttribute('contenteditable', true)
    }

    return el
  }

  _getClassNames () {
    return 'sc-container-editor sc-surface'
  }

  selectFirst () {
    let doc = this.getDocument()
    let containerPath = this.getContainerPath()
    let nodeIds = doc.get()
    if (nodeIds.length > 0) {
      const editorSession = this.getEditorSession()
      const first = doc.get(nodeIds[0])
      selectionHelpers.setCursor(editorSession, first, containerPath, 'before')
    }
  }

  _renderNode ($$, node, nodeIndex) {
    if (!node) throw new Error('Illegal argument')
    let ComponentClass = this._getNodeComponentClass(node)
    let props = this._getNodeProps(node)
    return $$(ComponentClass, props).ref(node.id)
  }

  _getNodeComponentClass (node) {
    let ComponentClass = this.getComponent(node.type)
    if (node.isText() || ComponentClass.prototype._isCustomNodeComponent || ComponentClass.prototype._isIsolatedNodeComponent) {
      return ComponentClass
    } else {
      return this.getComponent('isolated-node')
    }
  }

  _deriveInternalState (props) {
    let _state = this._state
    if (!props.hasOwnProperty('enabled') || props.enabled) {
      _state.enabled = true
    } else {
      _state.enabled = false
    }
  }

  _selectNextIsolatedNode (direction) {
    let selState = this.getEditorSession().getSelectionState()
    let node = (direction === 'left') ? selState.previousNode : selState.nextNode
    let isIsolatedNode = !node.isText() && !node.isList()
    if (!node || !isIsolatedNode) return false
    if (
      (direction === 'left' && selState.isFirst) ||
      (direction === 'right' && selState.isLast)
    ) {
      this.getEditorSession().setSelection({
        type: 'node',
        nodeId: node.id,
        containerPath: this.getContainerPath(),
        surfaceId: this.id
      })
      return true
    }
    return false
  }

  _handleLeftOrRightArrowKey (event) {
    event.stopPropagation()
    const doc = this.getDocument()
    const sel = this.getEditorSession().getSelection()
    const left = (event.keyCode === keys.LEFT)
    const right = !left
    const direction = left ? 'left' : 'right'

    if (sel && !sel.isNull()) {
      let containerPath = sel.containerPath
      // Don't react if we are at the boundary of the document
      if (sel.isNodeSelection()) {
        let nodeIds = doc.get(containerPath)
        let nodePos = getContainerPosition(doc, containerPath, sel.getNodeId())
        if ((left && nodePos === 0) || (right && nodePos === nodeIds.length - 1)) {
          event.preventDefault()
          return
        }
      }
      if (sel.isNodeSelection() && !event.shiftKey) {
        this.domSelection.collapse(direction)
      }
    }

    this._delayed(() => {
      this._updateModelSelection({ direction })
    })
  }

  _handleUpOrDownArrowKey (event) {
    event.stopPropagation()
    const doc = this.getDocument()
    const sel = this.getEditorSession().getSelection()
    const up = (event.keyCode === keys.UP)
    const down = !up
    const direction = up ? 'left' : 'right'

    if (sel && !sel.isNull()) {
      let containerPath = sel.containerPath
      // Don't react if we are at the boundary of the document
      if (sel.isNodeSelection()) {
        let nodeIds = doc.get(containerPath)
        let nodePos = getContainerPosition(doc, containerPath, sel.getNodeId())
        if ((up && nodePos === 0) || (down && nodePos === nodeIds.length - 1)) {
          event.preventDefault()
          return
        }
        // Unfortunately we need to navigate out of an isolated node
        // manually, as even Chrome on Win is not able to do it.
        let editorSession = this.getEditorSession()
        // TODO the following fixes the mentioned problem for
        // regular UP/DOWN (non expanding)
        // For SHIFT+DOWN it happens to work, and only SHIFT-UP when started as NodeSelection needs to be fixed
        if (!event.shiftKey) {
          event.preventDefault()
          if (up) {
            let prev = doc.get(nodeIds[nodePos - 1])
            selectionHelpers.setCursor(editorSession, prev, containerPath, 'after')
            return
          } else {
            let next = doc.get(nodeIds[nodePos + 1])
            selectionHelpers.setCursor(editorSession, next, containerPath, 'before')
            return
          }
        }
      }
    }

    this._delayed(() => {
      this._updateModelSelection({ direction })
    })
  }

  _handleTabKey (event) {
    const editorSession = this.getEditorSession()
    const sel = editorSession.getSelection()
    // EXPERIMENTAL: using TAB to enter an isolated node
    if (sel.isNodeSelection() && sel.isFull()) {
      const comp = this.refs[sel.getNodeId()]
      if (comp && selectionHelpers.stepIntoIsolatedNode(editorSession, comp)) {
        event.preventDefault()
        event.stopPropagation()
        return
      }
    }
    super._handleTabKey(event)
  }

  __handleTab (e) {
    e.preventDefault()
    if (e.shiftKey) {
      this.getEditorSession().transaction((tx) => {
        tx.dedent()
      }, { action: 'dedent' })
    } else {
      this.getEditorSession().transaction((tx) => {
        tx.indent()
      }, { action: 'indent' })
    }
  }

  // Used by Clipboard
  isContainerEditor () {
    return true
  }

  /**
    Returns the containerPath the editor is bound to
  */
  getContainerPath () {
    return this.containerPath
  }

  isEmpty () {
    let ids = this.getDocument().get(this.containerPath)
    return (!ids || ids.length === 0)
  }

  isEditable () {
    return super.isEditable.call(this) && !this.isEmpty()
  }

  // called by flow when subscribed resources have been updated
  _onContainerChanged (change) {
    let doc = this.getDocument()
    // first update the container
    let renderContext = RenderingEngine.createContext(this)
    let $$ = renderContext.$$
    let containerPath = this.getContainerPath()
    for (let i = 0; i < change.ops.length; i++) {
      let op = change.ops[i]
      if (isArrayEqual(op.path, containerPath)) {
        if (op.type === 'update') {
          let diff = op.diff
          if (diff.type === 'insert') {
            let nodeId = diff.getValue()
            let node = doc.get(nodeId)
            let nodeEl
            if (node) {
              nodeEl = this._renderNode($$, node)
            } else {
              // node does not exist anymore
              // so we insert a stub element, so that the number of child
              // elements is consistent
              nodeEl = $$('div')
            }
            this.insertAt(diff.getOffset(), nodeEl)
          } else if (diff.type === 'delete') {
            this.removeAt(diff.getOffset())
          }
        } else {
          this.empty()
          this.rerender()
        }
      }
    }
  }

  get _isContainerEditor () { return true }
}
<|endoftext|>


<commit_before>/* @flow */
/**
 *  Copyright (c) 2015, Facebook, Inc.
 *  All rights reserved.
 *
 *  This source code is licensed under the BSD-style license found in the
 *  LICENSE file in the root directory of this source tree. An additional grant
 *  of patent rights can be found in the PATENTS file in the same directory.
 */

import invariant from '../jsutils/invariant';
import { GraphQLError } from '../error';
import { visit, BREAK, getVisitFn } from '../language/visitor';
import * as Kind from '../language/kinds';
import type {
  Document,
  FragmentDefinition,
} from '../language/ast';
import type { GraphQLSchema } from '../type/schema';
import type {
  GraphQLInputType,
  GraphQLOutputType,
  GraphQLCompositeType,
  GraphQLFieldDefinition,
  GraphQLArgument
} from '../type/definition';
import type { GraphQLDirective } from '../type/directives';
import { TypeInfo } from '../utilities/TypeInfo';
import { allRules } from './allRules';

/**
 * Implements the "Validation" section of the spec.
 *
 * Validation runs synchronously, returning an array of encountered errors, or
 * an empty array if no errors were encountered and the document is valid.
 *
 * A list of specific validation rules may be provided. If not provided, the
 * default list of rules defined by the GraphQL specification will be used.
 *
 * Each validation rules is a function which returns a visitor
 * (see the language/visitor API). Visitor methods are expected to return
 * GraphQLErrors, or Arrays of GraphQLErrors when invalid.
 *
 * Visitors can also supply `visitSpreadFragments: true` which will alter the
 * behavior of the visitor to skip over top level defined fragments, and instead
 * visit those fragments at every point a spread is encountered.
 */
export function validate(
  schema: GraphQLSchema,
  ast: Document,
  rules?: Array<any>
): Array<GraphQLError> {
  invariant(schema, 'Must provide schema');
  invariant(ast, 'Must provide document');
  return visitUsingRules(schema, ast, rules || allRules);
}

/**
 * This uses a specialized visitor which runs multiple visitors in parallel,
 * while maintaining the visitor skip and break API.
 */
function visitUsingRules(
  schema: GraphQLSchema,
  documentAST: Document,
  rules: Array<any>
): Array<GraphQLError> {
  var typeInfo = new TypeInfo(schema);
  var context = new ValidationContext(schema, documentAST, typeInfo);
  var errors = [];

  function visitInstances(ast, instances) {
    var skipUntil = Array(instances.length);
    var skipCount = 0;
    visit(ast, {
      enter(node, key) {
        typeInfo.enter(node);
        for (var i = 0; i < instances.length; i++) {
          // Do not visit this instance if it returned false for a previous node
          if (skipUntil[i]) {
            continue;
          }

          var result;

          // Do not visit top level fragment definitions if this instance will
          // visit those fragments inline because it
          // provided `visitSpreadFragments`.
          if (
            node.kind === Kind.FRAGMENT_DEFINITION &&
            key !== undefined &&
            (instances[i]: any).visitSpreadFragments
          ) {
            result = false;
          } else {
            var enter = getVisitFn(instances[i], false, node.kind);
            result = enter ? enter.apply(instances[i], arguments) : undefined;
          }

          if (result === false) {
            skipUntil[i] = node;
            skipCount++;
            // If all instances are being skipped over, skip deeper traveral
            if (skipCount === instances.length) {
              for (var k = 0; k < instances.length; k++) {
                if (skipUntil[k] === node) {
                  skipUntil[k] = null;
                  skipCount--;
                }
              }
              return false;
            }
          } else if (result === BREAK) {
            instances[i] = null;
          } else if (result && isError(result)) {
            append(errors, result);
            for (var j = i - 1; j >= 0; j--) {
              var leaveFn = getVisitFn(instances[j], true, node.kind);
              if (leaveFn) {
                result = leaveFn.apply(instances[j], arguments);
                if (result === BREAK) {
                  instances[j] = null;
                } else if (isError(result)) {
                  append(errors, result);
                } else if (result !== undefined) {
                  throw new Error('Validator cannot edit document.');
                }
              }
            }
            typeInfo.leave(node);
            return false;
          } else if (result !== undefined) {
            throw new Error('Validator cannot edit document.');
          }
        }

        // If any validation instances provide the flag `visitSpreadFragments`
        // and this node is a fragment spread, validate the fragment from
        // this point.
        if (node.kind === Kind.FRAGMENT_SPREAD) {
          var fragment = context.getFragment(node.name.value);
          if (fragment) {
            var fragVisitingInstances = instances.filter(
              (inst: any, idx) => inst.visitSpreadFragments && !skipUntil[idx]
            );
            if (fragVisitingInstances.length > 0) {
              visitInstances(fragment, fragVisitingInstances);
            }
          }
        }
      },
      leave(node) {
        for (var i = instances.length - 1; i >= 0; i--) {
          if (skipUntil[i]) {
            if (skipUntil[i] === node) {
              skipUntil[i] = null;
              skipCount--;
            }
            continue;
          }
          var leaveFn = getVisitFn(instances[i], true, node.kind);
          if (leaveFn) {
            var result = leaveFn.apply(instances[i], arguments);
            if (result === BREAK) {
              instances[i] = null;
            } else if (isError(result)) {
              append(errors, result);
            } else if (result !== undefined && result !== false) {
              throw new Error('Validator cannot edit document.');
            }
          }
        }
        typeInfo.leave(node);
      }
    });
  }

  // Visit the whole document with instances of all provided rules.
  var allRuleInstances = rules.map(rule => rule(context));
  visitInstances(documentAST, allRuleInstances);

  return errors;
}

function isError(value) {
  return Array.isArray(value) ?
    value.every(item => item instanceof GraphQLError) :
    value instanceof GraphQLError;
}

function append(arr, items) {
  if (Array.isArray(items)) {
    arr.push.apply(arr, items);
  } else {
    arr.push(items);
  }
}

/**
 * An instance of this class is passed as the "this" context to all validators,
 * allowing access to commonly useful contextual information from within a
 * validation rule.
 */
export class ValidationContext {
  _schema: GraphQLSchema;
  _ast: Document;
  _typeInfo: TypeInfo;
  _fragments: {[name: string]: FragmentDefinition};

  constructor(schema: GraphQLSchema, ast: Document, typeInfo: TypeInfo) {
    this._schema = schema;
    this._ast = ast;
    this._typeInfo = typeInfo;
  }

  getSchema(): GraphQLSchema {
    return this._schema;
  }

  getDocument(): Document {
    return this._ast;
  }

  getFragment(name: string): ?FragmentDefinition {
    var fragments = this._fragments;
    if (!fragments) {
      this._fragments = fragments =
        this.getDocument().definitions.reduce((frags, statement) => {
          if (statement.kind === Kind.FRAGMENT_DEFINITION) {
            frags[statement.name.value] = statement;
          }
          return frags;
        }, {});
    }
    return fragments[name];
  }

  getType(): ?GraphQLOutputType {
    return this._typeInfo.getType();
  }

  getParentType(): ?GraphQLCompositeType {
    return this._typeInfo.getParentType();
  }

  getInputType(): ?GraphQLInputType {
    return this._typeInfo.getInputType();
  }

  getFieldDef(): ?GraphQLFieldDefinition {
    return this._typeInfo.getFieldDef();
  }

  getDirective(): ?GraphQLDirective {
    return this._typeInfo.getDirective();
  }

  getArgument(): ?GraphQLArgument {
    return this._typeInfo.getArgument();
  }
}
<commit_msg>Simplify validator<commit_after>/* @flow */
/**
 *  Copyright (c) 2015, Facebook, Inc.
 *  All rights reserved.
 *
 *  This source code is licensed under the BSD-style license found in the
 *  LICENSE file in the root directory of this source tree. An additional grant
 *  of patent rights can be found in the PATENTS file in the same directory.
 */

import invariant from '../jsutils/invariant';
import { GraphQLError } from '../error';
import { visit, getVisitFn } from '../language/visitor';
import * as Kind from '../language/kinds';
import type {
  Document,
  FragmentDefinition,
} from '../language/ast';
import type { GraphQLSchema } from '../type/schema';
import type {
  GraphQLInputType,
  GraphQLOutputType,
  GraphQLCompositeType,
  GraphQLFieldDefinition,
  GraphQLArgument
} from '../type/definition';
import type { GraphQLDirective } from '../type/directives';
import { TypeInfo } from '../utilities/TypeInfo';
import { allRules } from './allRules';

/**
 * Implements the "Validation" section of the spec.
 *
 * Validation runs synchronously, returning an array of encountered errors, or
 * an empty array if no errors were encountered and the document is valid.
 *
 * A list of specific validation rules may be provided. If not provided, the
 * default list of rules defined by the GraphQL specification will be used.
 *
 * Each validation rules is a function which returns a visitor
 * (see the language/visitor API). Visitor methods are expected to return
 * GraphQLErrors, or Arrays of GraphQLErrors when invalid.
 *
 * Visitors can also supply `visitSpreadFragments: true` which will alter the
 * behavior of the visitor to skip over top level defined fragments, and instead
 * visit those fragments at every point a spread is encountered.
 */
export function validate(
  schema: GraphQLSchema,
  ast: Document,
  rules?: Array<any>
): Array<GraphQLError> {
  invariant(schema, 'Must provide schema');
  invariant(ast, 'Must provide document');
  return visitUsingRules(schema, ast, rules || allRules);
}

/**
 * This uses a specialized visitor which runs multiple visitors in parallel,
 * while maintaining the visitor skip and break API.
 */
function visitUsingRules(
  schema: GraphQLSchema,
  documentAST: Document,
  rules: Array<any>
): Array<GraphQLError> {
  var typeInfo = new TypeInfo(schema);
  var context = new ValidationContext(schema, documentAST, typeInfo);
  var errors = [];

  function visitInstance(ast, instance) {
    visit(ast, {
      enter(node, key) {
        // Collect type information about the current position in the AST.
        typeInfo.enter(node);

        // Do not visit top level fragment definitions if this instance will
        // visit those fragments inline because it
        // provided `visitSpreadFragments`.
        var result;
        if (
          node.kind === Kind.FRAGMENT_DEFINITION &&
          key !== undefined &&
          instance.visitSpreadFragments
        ) {
          return false;
        }

        // Get the visitor function from the validation instance, and if it
        // exists, call it with the visitor arguments.
        var enter = getVisitFn(instance, false, node.kind);
        result = enter ? enter.apply(instance, arguments) : undefined;

        // If the visitor returned an error, log it and do not visit any
        // deeper nodes.
        if (result && isError(result)) {
          append(errors, result);
          result = false;
        }

        // If any validation instances provide the flag `visitSpreadFragments`
        // and this node is a fragment spread, visit the fragment definition
        // from this point.
        if (result === undefined &&
            instance.visitSpreadFragments &&
            node.kind === Kind.FRAGMENT_SPREAD) {
          var fragment = context.getFragment(node.name.value);
          if (fragment) {
            visitInstance(fragment, instance);
          }
        }

        // If the result is "false", we're not visiting any descendent nodes,
        // but need to update typeInfo.
        if (result === false) {
          typeInfo.leave(node);
        }

        return result;
      },
      leave(node) {
        // Get the visitor function from the validation instance, and if it
        // exists, call it with the visitor arguments.
        var leave = getVisitFn(instance, true, node.kind);
        var result = leave ? leave.apply(instance, arguments) : undefined;

        // If the visitor returned an error, log it and do not visit any
        // deeper nodes.
        if (result && isError(result)) {
          append(errors, result);
          result = false;
        }

        // Update typeInfo.
        typeInfo.leave(node);

        return result;
      }
    });
  }

  // Visit the whole document with each instance of all provided rules.
  var allRuleInstances = rules.map(rule => rule(context));
  allRuleInstances.forEach(instance => {
    visitInstance(documentAST, instance);
  });

  return errors;
}

function isError(value) {
  return Array.isArray(value) ?
    value.every(item => item instanceof GraphQLError) :
    value instanceof GraphQLError;
}

function append(arr, items) {
  if (Array.isArray(items)) {
    arr.push.apply(arr, items);
  } else {
    arr.push(items);
  }
}

/**
 * An instance of this class is passed as the "this" context to all validators,
 * allowing access to commonly useful contextual information from within a
 * validation rule.
 */
export class ValidationContext {
  _schema: GraphQLSchema;
  _ast: Document;
  _typeInfo: TypeInfo;
  _fragments: {[name: string]: FragmentDefinition};

  constructor(schema: GraphQLSchema, ast: Document, typeInfo: TypeInfo) {
    this._schema = schema;
    this._ast = ast;
    this._typeInfo = typeInfo;
  }

  getSchema(): GraphQLSchema {
    return this._schema;
  }

  getDocument(): Document {
    return this._ast;
  }

  getFragment(name: string): ?FragmentDefinition {
    var fragments = this._fragments;
    if (!fragments) {
      this._fragments = fragments =
        this.getDocument().definitions.reduce((frags, statement) => {
          if (statement.kind === Kind.FRAGMENT_DEFINITION) {
            frags[statement.name.value] = statement;
          }
          return frags;
        }, {});
    }
    return fragments[name];
  }

  getType(): ?GraphQLOutputType {
    return this._typeInfo.getType();
  }

  getParentType(): ?GraphQLCompositeType {
    return this._typeInfo.getParentType();
  }

  getInputType(): ?GraphQLInputType {
    return this._typeInfo.getInputType();
  }

  getFieldDef(): ?GraphQLFieldDefinition {
    return this._typeInfo.getFieldDef();
  }

  getDirective(): ?GraphQLDirective {
    return this._typeInfo.getDirective();
  }

  getArgument(): ?GraphQLArgument {
    return this._typeInfo.getArgument();
  }
}
<|endoftext|>


<commit_before>import SPELLS from 'common/SPELLS';
import COVENANTS from 'game/shadowlands/COVENANTS';
import ISSUE_IMPORTANCE from 'parser/core/ISSUE_IMPORTANCE';
import CoreAbilities from 'parser/core/modules/Abilities';

class Abilities extends CoreAbilities {
  spellbook() {
    const combatant = this.selectedCombatant;
    return [
      // Rotational
      {
        spell: SPELLS.MORTAL_STRIKE.id,
        category: Abilities.SPELL_CATEGORIES.ROTATIONAL,
        cooldown: (haste) => 6 / (1 + haste),
        gcd: {
          base: 1500,
        },
        castEfficiency: {
          suggestion: false, // Suggestions are in MortalStrike.js
          recommendedEfficiency: 0.8,
        },
      },
      {
        spell: SPELLS.OVERPOWER.id,
        category: Abilities.SPELL_CATEGORIES.ROTATIONAL,
        cooldown: 12,
        charges: 1 + (combatant.hasTalent(SPELLS.DREADNAUGHT_TALENT.id) ? 1 : 0),
        gcd: {
          base: 1500,
        },
        buffSpellId: SPELLS.OVERPOWER.id,
      },
      {
        spell: SPELLS.SLAM.id,
        category: Abilities.SPELL_CATEGORIES.ROTATIONAL,
        gcd: {
          base: 1500,
        },
      },
      {
        spell: SPELLS.REND_TALENT.id,
        category: Abilities.SPELL_CATEGORIES.ROTATIONAL,
        gcd: {
          base: 1500,
        },
        enabled: combatant.hasTalent(SPELLS.REND_TALENT.id),
      },
      {
        spell: SPELLS.COLOSSUS_SMASH.id,
        category: Abilities.SPELL_CATEGORIES.ROTATIONAL,
        cooldown: 45,
        gcd: {
          base: 1500,
        },
        castEfficiency: {
          suggestion: true,
          recommendedEfficiency: 0.9,
        },
        enabled: !combatant.hasTalent(SPELLS.WARBREAKER_TALENT.id),
      },
      {
        spell: SPELLS.SKULLSPLITTER_TALENT.id,
        category: Abilities.SPELL_CATEGORIES.ROTATIONAL,
        cooldown: (haste) => 21 / (1 + haste),
        gcd: {
          base: 1500,
        },
        enabled: combatant.hasTalent(SPELLS.SKULLSPLITTER_TALENT.id),
      },
      {
        spell: [SPELLS.EXECUTE.id, SPELLS.EXECUTE_GLYPHED.id],
        category: Abilities.SPELL_CATEGORIES.ROTATIONAL,
        gcd: {
          base: 1500,
        },
        enabled: !combatant.hasCovenant(COVENANTS.VENTHYR.id),
      },
      {
        spell: [SPELLS.CONDEMN.id, SPELLS.CONDEMN_MASSACRE.id],
        category: Abilities.SPELL_CATEGORIES.ROTATIONAL,
        gcd: {
          base: 1500,
        },
        enabled: combatant.hasCovenant(COVENANTS.VENTHYR.id),
      },
      // Rotational AOE
      {
        spell: SPELLS.WARBREAKER_TALENT.id,
        category: Abilities.SPELL_CATEGORIES.ROTATIONAL_AOE,
        cooldown: 45,
        gcd: {
          base: 1500,
        },
        castEfficiency: {
          suggestion: true,
          recommendedEfficiency: 0.9,
        },
        enabled: combatant.hasTalent(SPELLS.WARBREAKER_TALENT.id), // replaces Colussus Smash
      },
      {
        spell: SPELLS.CLEAVE_TALENT.id,
        category: Abilities.SPELL_CATEGORIES.ROTATIONAL_AOE,
        cooldown: (haste) => 9 / (1 + haste),
        gcd: {
          base: 1500,
        },
        castEfficiency: {
          suggestion: true,
          recommendedEfficiency: 0.6, // I don't know at what efficiency this talent becomes worth it so I'm keeping it save
        },
        enabled: combatant.hasTalent(SPELLS.CLEAVE_TALENT.id),
      },
      {
        spell: SPELLS.SWEEPING_STRIKES.id,
        category: Abilities.SPELL_CATEGORIES.ROTATIONAL_AOE,
        cooldown: 30,
        gcd: {
          base: 1500,
        },
        buffSpellId: SPELLS.SWEEPING_STRIKES.id,
      },
      {
        spell: SPELLS.WHIRLWIND.id,
        category: Abilities.SPELL_CATEGORIES.ROTATIONAL_AOE,
        gcd: {
          base: 1500,
        },
      },
      {
        spell: SPELLS.BLADESTORM.id,
        category: Abilities.SPELL_CATEGORIES.ROTATIONAL_AOE,
        cooldown: 90,
        gcd: {
          base: 1500,
        },
        castEfficiency: {
          suggestion: true,
          recommendedEfficiency: 0.7,
        },
        enabled: !combatant.hasTalent(SPELLS.RAVAGER_TALENT_ARMS.id),
        buffSpellId: SPELLS.BLADESTORM.id,
      },
      {
        spell: SPELLS.RAVAGER_TALENT_ARMS.id,
        category: Abilities.SPELL_CATEGORIES.ROTATIONAL_AOE,
        cooldown: 60,
        gcd: {
          base: 1500,
        },
        castEfficiency: {
          suggestion: true,
          recommendedEfficiency: 0.7,
        },
        enabled: combatant.hasTalent(SPELLS.RAVAGER_TALENT_ARMS.id), // Replaces Bladestorm
      },
      {
        spell: SPELLS.ANCIENT_AFTERSHOCK.id,
        category: Abilities.SPELL_CATEGORIES.ROTATIONAL_AOE,
        cooldown: 90,
        gcd: {
          base: 1500,
        },
        enabled: combatant.hasCovenant(COVENANTS.NIGHT_FAE.id),
      },
      {
        spell: SPELLS.SPEAR_OF_BASTION.id,
        category: Abilities.SPELL_CATEGORIES.ROTATIONAL_AOE,
        cooldown: 60,
        gcd: {
          base: 1500,
        },
        enabled: combatant.hasCovenant(COVENANTS.KYRIAN.id),
      },
      // Others
      {
        spell: SPELLS.VICTORY_RUSH.id,
        category: Abilities.SPELL_CATEGORIES.OTHERS,
        gcd: {
          base: 1500,
        },
        enabled: !combatant.hasTalent(SPELLS.IMPENDING_VICTORY_TALENT.id),
      },
      {
        spell: SPELLS.IMPENDING_VICTORY_TALENT.id,
        category: Abilities.SPELL_CATEGORIES.DEFENSIVE,
        cooldown: 30,
        gcd: {
          base: 1500,
        },
        enabled: combatant.hasTalent(SPELLS.IMPENDING_VICTORY_TALENT.id), // Replaces Victory Rush
      },
      // Cooldowns
      {
        spell: SPELLS.AVATAR_TALENT.id,
        category: Abilities.SPELL_CATEGORIES.COOLDOWNS,
        cooldown: 90,
        gcd: {
          base: 1500,
        },
        castEfficiency: {
          suggestion: true,
          recommendedEfficiency: 0.9,
        },
        enabled: combatant.hasTalent(SPELLS.AVATAR_TALENT.id),
      },
      {
        spell: SPELLS.DEADLY_CALM_TALENT.id,
        category: Abilities.SPELL_CATEGORIES.COOLDOWNS,
        cooldown: 60,
        gcd: null,
        castEfficiency: {
          suggestion: true,
          recommendedEfficiency: 0.9,
        },
        enabled: combatant.hasTalent(SPELLS.DEADLY_CALM_TALENT.id),
        buffSpellId: SPELLS.DEADLY_CALM_TALENT.id,
      },
      {
        spell: SPELLS.CONQUERORS_BANNER.id,
        category: Abilities.SPELL_CATEGORIES.COOLDOWNS,
        cooldown: 180,
        gcd: {
          base: 1500,
        },
        enabled: combatant.hasCovenant(COVENANTS.NECROLORD.id),
      },
      // Defensive
      {
        spell: SPELLS.DEFENSIVE_STANCE_TALENT.id,
        category: Abilities.SPELL_CATEGORIES.DEFENSIVE,
        cooldown: 6,
        gcd: null,
        enabled: combatant.hasTalent(SPELLS.DEFENSIVE_STANCE_TALENT.id),
        buffSpellId: SPELLS.DEFENSIVE_STANCE_TALENT.id,
      },
      {
        spell: SPELLS.DIE_BY_THE_SWORD.id,
        category: Abilities.SPELL_CATEGORIES.DEFENSIVE,
        cooldown: 180,
        gcd: null,
        castEfficiency: {
          suggestion: false,
          recommendedEfficiency: 0.4,
        },
        buffSpellId: SPELLS.DIE_BY_THE_SWORD.id,
      },
      {
        spell: SPELLS.RALLYING_CRY.id,
        category: Abilities.SPELL_CATEGORIES.DEFENSIVE,
        cooldown: 180,
        gcd: {
          base: 1500,
        },
        castEfficiency: {
          suggestion: true,
          recommendedEfficiency: 0.6,
          importance: ISSUE_IMPORTANCE.MINOR,
          extraSuggestion:
            'Use it preemptively as a buffer against large AOE, or reactively if you notice your raid is getting dangerously low on health.',
        },
        buffSpellId: SPELLS.RALLYING_CRY_BUFF.id,
      },
      // Utility
      {
        spell: SPELLS.CHARGE.id,
        category: Abilities.SPELL_CATEGORIES.UTILITY,
        cooldown: 20 - (combatant.hasTalent(SPELLS.DOUBLE_TIME_TALENT.id) ? 3 : 0),
        charges: 1 + (combatant.hasTalent(SPELLS.DOUBLE_TIME_TALENT.id) ? 1 : 0),
        gcd: null, // Off gcd since 8.1
        castEfficiency: {
          suggestion: true,
          recommendedEfficiency: 0.01,
          extraSuggestion:
            'Use it to get back into range after moving out to avoid mechanics. Not only does this allow you to get back to DPSing faster, it also generates rage for you to DPS with.',
        },
      },
      {
        spell: SPELLS.HEROIC_LEAP.id,
        category: Abilities.SPELL_CATEGORIES.UTILITY,
        buffSpellId: SPELLS.BOUNDING_STRIDE_BUFF.id,
        cooldown: 45 - (combatant.hasTalent(SPELLS.BOUNDING_STRIDE_TALENT.id) ? 15 : 0),
        gcd: null,
        castEfficiency: {
          suggestion: true,
          recommendedEfficiency: 0.01,
          extraSuggestion:
            'Use it to escape mechanics at the last moment, allowing you more time to DPS.',
        },
      },
      {
        spell: SPELLS.STORM_BOLT_TALENT.id,
        category: Abilities.SPELL_CATEGORIES.UTILITY,
        cooldown: 30,
        gcd: {
          base: 1500,
        },
        castEfficiency: {
          suggestion: true,
          recommendedEfficiency: 0.4,
          extraSuggestion:
            "If you're picking a utility talent over something that increases your mobility or survivability, you better use it.",
        },
        enabled: combatant.hasTalent(SPELLS.STORM_BOLT_TALENT.id),
      },
      {
        spell: SPELLS.PUMMEL.id,
        category: Abilities.SPELL_CATEGORIES.UTILITY,
        cooldown: 15,
        gcd: null,
      },
      {
        spell: SPELLS.BERSERKER_RAGE.id,
        category: Abilities.SPELL_CATEGORIES.UTILITY,
        cooldown: 60,
        gcd: null,
        buffSpellId: SPELLS.BERSERKER_RAGE.id,
      },
      {
        spell: SPELLS.HEROIC_THROW.id,
        category: Abilities.SPELL_CATEGORIES.UTILITY,
        cooldown: 6,
        gcd: {
          base: 1500,
        },
      },
      {
        spell: SPELLS.INTIMIDATING_SHOUT.id,
        category: Abilities.SPELL_CATEGORIES.UTILITY,
        cooldown: 90,
        gcd: {
          base: 1500,
        },
      },
      {
        spell: SPELLS.HAMSTRING.id,
        category: Abilities.SPELL_CATEGORIES.UTILITY,
        gcd: {
          base: 1500,
        },
      },
      {
        spell: SPELLS.BATTLE_SHOUT.id,
        category: Abilities.SPELL_CATEGORIES.UTILITY,
        cooldown: 15,
        gcd: {
          base: 1500,
        },
      },
      {
        spell: SPELLS.TAUNT.id,
        category: Abilities.SPELL_CATEGORIES.UTILITY,
        cooldown: 8,
        gcd: null,
      },
    ];
  }
}

export default Abilities;
<commit_msg>fixed avatar and sweeping strikes gcd<commit_after>import SPELLS from 'common/SPELLS';
import COVENANTS from 'game/shadowlands/COVENANTS';
import ISSUE_IMPORTANCE from 'parser/core/ISSUE_IMPORTANCE';
import CoreAbilities from 'parser/core/modules/Abilities';

class Abilities extends CoreAbilities {
  spellbook() {
    const combatant = this.selectedCombatant;
    return [
      // Rotational
      {
        spell: SPELLS.MORTAL_STRIKE.id,
        category: Abilities.SPELL_CATEGORIES.ROTATIONAL,
        cooldown: (haste) => 6 / (1 + haste),
        gcd: {
          base: 1500,
        },
        castEfficiency: {
          suggestion: false, // Suggestions are in MortalStrike.js
          recommendedEfficiency: 0.8,
        },
      },
      {
        spell: SPELLS.OVERPOWER.id,
        category: Abilities.SPELL_CATEGORIES.ROTATIONAL,
        cooldown: 12,
        charges: 1 + (combatant.hasTalent(SPELLS.DREADNAUGHT_TALENT.id) ? 1 : 0),
        gcd: {
          base: 1500,
        },
        buffSpellId: SPELLS.OVERPOWER.id,
      },
      {
        spell: SPELLS.SLAM.id,
        category: Abilities.SPELL_CATEGORIES.ROTATIONAL,
        gcd: {
          base: 1500,
        },
      },
      {
        spell: SPELLS.REND_TALENT.id,
        category: Abilities.SPELL_CATEGORIES.ROTATIONAL,
        gcd: {
          base: 1500,
        },
        enabled: combatant.hasTalent(SPELLS.REND_TALENT.id),
      },
      {
        spell: SPELLS.COLOSSUS_SMASH.id,
        category: Abilities.SPELL_CATEGORIES.ROTATIONAL,
        cooldown: 45,
        gcd: {
          base: 1500,
        },
        castEfficiency: {
          suggestion: true,
          recommendedEfficiency: 0.9,
        },
        enabled: !combatant.hasTalent(SPELLS.WARBREAKER_TALENT.id),
      },
      {
        spell: SPELLS.SKULLSPLITTER_TALENT.id,
        category: Abilities.SPELL_CATEGORIES.ROTATIONAL,
        cooldown: (haste) => 21 / (1 + haste),
        gcd: {
          base: 1500,
        },
        enabled: combatant.hasTalent(SPELLS.SKULLSPLITTER_TALENT.id),
      },
      {
        spell: [SPELLS.EXECUTE.id, SPELLS.EXECUTE_GLYPHED.id],
        category: Abilities.SPELL_CATEGORIES.ROTATIONAL,
        gcd: {
          base: 1500,
        },
        enabled: !combatant.hasCovenant(COVENANTS.VENTHYR.id),
      },
      {
        spell: [SPELLS.CONDEMN.id, SPELLS.CONDEMN_MASSACRE.id],
        category: Abilities.SPELL_CATEGORIES.ROTATIONAL,
        gcd: {
          base: 1500,
        },
        enabled: combatant.hasCovenant(COVENANTS.VENTHYR.id),
      },
      // Rotational AOE
      {
        spell: SPELLS.WARBREAKER_TALENT.id,
        category: Abilities.SPELL_CATEGORIES.ROTATIONAL_AOE,
        cooldown: 45,
        gcd: {
          base: 1500,
        },
        castEfficiency: {
          suggestion: true,
          recommendedEfficiency: 0.9,
        },
        enabled: combatant.hasTalent(SPELLS.WARBREAKER_TALENT.id), // replaces Colussus Smash
      },
      {
        spell: SPELLS.CLEAVE_TALENT.id,
        category: Abilities.SPELL_CATEGORIES.ROTATIONAL_AOE,
        cooldown: (haste) => 9 / (1 + haste),
        gcd: {
          base: 1500,
        },
        castEfficiency: {
          suggestion: true,
          recommendedEfficiency: 0.6, // I don't know at what efficiency this talent becomes worth it so I'm keeping it save
        },
        enabled: combatant.hasTalent(SPELLS.CLEAVE_TALENT.id),
      },
      {
        spell: SPELLS.SWEEPING_STRIKES.id,
        category: Abilities.SPELL_CATEGORIES.ROTATIONAL_AOE,
        cooldown: 30,
        gcd: {
          base: 750,
        },
        buffSpellId: SPELLS.SWEEPING_STRIKES.id,
      },
      {
        spell: SPELLS.WHIRLWIND.id,
        category: Abilities.SPELL_CATEGORIES.ROTATIONAL_AOE,
        gcd: {
          base: 1500,
        },
      },
      {
        spell: SPELLS.BLADESTORM.id,
        category: Abilities.SPELL_CATEGORIES.ROTATIONAL_AOE,
        cooldown: 90,
        gcd: {
          base: 1500,
        },
        castEfficiency: {
          suggestion: true,
          recommendedEfficiency: 0.7,
        },
        enabled: !combatant.hasTalent(SPELLS.RAVAGER_TALENT_ARMS.id),
        buffSpellId: SPELLS.BLADESTORM.id,
      },
      {
        spell: SPELLS.RAVAGER_TALENT_ARMS.id,
        category: Abilities.SPELL_CATEGORIES.ROTATIONAL_AOE,
        cooldown: 60,
        gcd: {
          base: 1500,
        },
        castEfficiency: {
          suggestion: true,
          recommendedEfficiency: 0.7,
        },
        enabled: combatant.hasTalent(SPELLS.RAVAGER_TALENT_ARMS.id), // Replaces Bladestorm
      },
      {
        spell: SPELLS.ANCIENT_AFTERSHOCK.id,
        category: Abilities.SPELL_CATEGORIES.ROTATIONAL_AOE,
        cooldown: 90,
        gcd: {
          base: 1500,
        },
        enabled: combatant.hasCovenant(COVENANTS.NIGHT_FAE.id),
      },
      {
        spell: SPELLS.SPEAR_OF_BASTION.id,
        category: Abilities.SPELL_CATEGORIES.ROTATIONAL_AOE,
        cooldown: 60,
        gcd: {
          base: 1500,
        },
        enabled: combatant.hasCovenant(COVENANTS.KYRIAN.id),
      },
      // Others
      {
        spell: SPELLS.VICTORY_RUSH.id,
        category: Abilities.SPELL_CATEGORIES.OTHERS,
        gcd: {
          base: 1500,
        },
        enabled: !combatant.hasTalent(SPELLS.IMPENDING_VICTORY_TALENT.id),
      },
      {
        spell: SPELLS.IMPENDING_VICTORY_TALENT.id,
        category: Abilities.SPELL_CATEGORIES.DEFENSIVE,
        cooldown: 30,
        gcd: {
          base: 1500,
        },
        enabled: combatant.hasTalent(SPELLS.IMPENDING_VICTORY_TALENT.id), // Replaces Victory Rush
      },
      // Cooldowns
      {
        spell: SPELLS.AVATAR_TALENT.id,
        category: Abilities.SPELL_CATEGORIES.COOLDOWNS,
        cooldown: 90,
        gcd: null,
        castEfficiency: {
          suggestion: true,
          recommendedEfficiency: 0.9,
        },
        enabled: combatant.hasTalent(SPELLS.AVATAR_TALENT.id),
      },
      {
        spell: SPELLS.DEADLY_CALM_TALENT.id,
        category: Abilities.SPELL_CATEGORIES.COOLDOWNS,
        cooldown: 60,
        gcd: null,
        castEfficiency: {
          suggestion: true,
          recommendedEfficiency: 0.9,
        },
        enabled: combatant.hasTalent(SPELLS.DEADLY_CALM_TALENT.id),
        buffSpellId: SPELLS.DEADLY_CALM_TALENT.id,
      },
      {
        spell: SPELLS.CONQUERORS_BANNER.id,
        category: Abilities.SPELL_CATEGORIES.COOLDOWNS,
        cooldown: 180,
        gcd: {
          base: 1500,
        },
        enabled: combatant.hasCovenant(COVENANTS.NECROLORD.id),
      },
      // Defensive
      {
        spell: SPELLS.DEFENSIVE_STANCE_TALENT.id,
        category: Abilities.SPELL_CATEGORIES.DEFENSIVE,
        cooldown: 6,
        gcd: null,
        enabled: combatant.hasTalent(SPELLS.DEFENSIVE_STANCE_TALENT.id),
        buffSpellId: SPELLS.DEFENSIVE_STANCE_TALENT.id,
      },
      {
        spell: SPELLS.DIE_BY_THE_SWORD.id,
        category: Abilities.SPELL_CATEGORIES.DEFENSIVE,
        cooldown: 180,
        gcd: null,
        castEfficiency: {
          suggestion: false,
          recommendedEfficiency: 0.4,
        },
        buffSpellId: SPELLS.DIE_BY_THE_SWORD.id,
      },
      {
        spell: SPELLS.RALLYING_CRY.id,
        category: Abilities.SPELL_CATEGORIES.DEFENSIVE,
        cooldown: 180,
        gcd: {
          base: 1500,
        },
        castEfficiency: {
          suggestion: true,
          recommendedEfficiency: 0.6,
          importance: ISSUE_IMPORTANCE.MINOR,
          extraSuggestion:
            'Use it preemptively as a buffer against large AOE, or reactively if you notice your raid is getting dangerously low on health.',
        },
        buffSpellId: SPELLS.RALLYING_CRY_BUFF.id,
      },
      // Utility
      {
        spell: SPELLS.CHARGE.id,
        category: Abilities.SPELL_CATEGORIES.UTILITY,
        cooldown: 20 - (combatant.hasTalent(SPELLS.DOUBLE_TIME_TALENT.id) ? 3 : 0),
        charges: 1 + (combatant.hasTalent(SPELLS.DOUBLE_TIME_TALENT.id) ? 1 : 0),
        gcd: null, // Off gcd since 8.1
        castEfficiency: {
          suggestion: true,
          recommendedEfficiency: 0.01,
          extraSuggestion:
            'Use it to get back into range after moving out to avoid mechanics. Not only does this allow you to get back to DPSing faster, it also generates rage for you to DPS with.',
        },
      },
      {
        spell: SPELLS.HEROIC_LEAP.id,
        category: Abilities.SPELL_CATEGORIES.UTILITY,
        buffSpellId: SPELLS.BOUNDING_STRIDE_BUFF.id,
        cooldown: 45 - (combatant.hasTalent(SPELLS.BOUNDING_STRIDE_TALENT.id) ? 15 : 0),
        gcd: null,
        castEfficiency: {
          suggestion: true,
          recommendedEfficiency: 0.01,
          extraSuggestion:
            'Use it to escape mechanics at the last moment, allowing you more time to DPS.',
        },
      },
      {
        spell: SPELLS.STORM_BOLT_TALENT.id,
        category: Abilities.SPELL_CATEGORIES.UTILITY,
        cooldown: 30,
        gcd: {
          base: 1500,
        },
        castEfficiency: {
          suggestion: true,
          recommendedEfficiency: 0.4,
          extraSuggestion:
            "If you're picking a utility talent over something that increases your mobility or survivability, you better use it.",
        },
        enabled: combatant.hasTalent(SPELLS.STORM_BOLT_TALENT.id),
      },
      {
        spell: SPELLS.PUMMEL.id,
        category: Abilities.SPELL_CATEGORIES.UTILITY,
        cooldown: 15,
        gcd: null,
      },
      {
        spell: SPELLS.BERSERKER_RAGE.id,
        category: Abilities.SPELL_CATEGORIES.UTILITY,
        cooldown: 60,
        gcd: null,
        buffSpellId: SPELLS.BERSERKER_RAGE.id,
      },
      {
        spell: SPELLS.HEROIC_THROW.id,
        category: Abilities.SPELL_CATEGORIES.UTILITY,
        cooldown: 6,
        gcd: {
          base: 1500,
        },
      },
      {
        spell: SPELLS.INTIMIDATING_SHOUT.id,
        category: Abilities.SPELL_CATEGORIES.UTILITY,
        cooldown: 90,
        gcd: {
          base: 1500,
        },
      },
      {
        spell: SPELLS.HAMSTRING.id,
        category: Abilities.SPELL_CATEGORIES.UTILITY,
        gcd: {
          base: 1500,
        },
      },
      {
        spell: SPELLS.BATTLE_SHOUT.id,
        category: Abilities.SPELL_CATEGORIES.UTILITY,
        cooldown: 15,
        gcd: {
          base: 1500,
        },
      },
      {
        spell: SPELLS.TAUNT.id,
        category: Abilities.SPELL_CATEGORIES.UTILITY,
        cooldown: 8,
        gcd: null,
      },
    ];
  }
}

export default Abilities;
<|endoftext|>


<commit_before>/* @flow strict */

import * as React from "react";
import { selectors } from "@nteract/core";
import { NewNotebookNavigation } from "@nteract/connected-components";
import {
  Entry,
  Listing,
  Icon,
  Name,
  LastSaved
} from "@nteract/directory-listing";
import type {
  AppState,
  KernelspecRecord,
  KernelspecProps,
  JupyterHostRecord,
  ContentRef,
  DirectoryContentRecord
} from "@nteract/core";
import { connect } from "react-redux";

import { Nav, NavSection } from "../components/nav";
import { openNotebook } from "../triggers/open-notebook";
import { ThemedLogo } from "../components/themed-logo.js";

const urljoin = require("url-join");

type DirectoryProps = {
  content: DirectoryContentRecord,
  host: JupyterHostRecord,
  appVersion: string,
  contentRef: ContentRef,
  contents: Array<{
    path: string,
    type: NotebookTypes,
    name: string,
    last_modified: ?Date
  }>
};

export class DirectoryApp extends React.PureComponent<DirectoryProps, null> {
  openNotebook = (ks: KernelspecRecord | KernelspecProps) => {
    openNotebook(this.props.host, ks, {
      appVersion: this.props.appVersion,
      // Since we're looking at a directory, the base dir is the directory we are in
      baseDir: this.props.content.filepath,
      appPath: this.props.host.basePath
    });
  };

  render() {
    const atRoot = this.props.content.filepath === "/";

    const dotdothref = urljoin(
      this.props.host.basePath,
      "/nteract/edit/",
      urljoin(this.props.content.filepath, "..")
    );
    const basePath = this.props.host.basePath;
    const dotdotlink = {".."};
    return (
      <React.Fragment>

          <NavSection>

              <ThemedLogo />

            {this.props.content.filepath.split("/").pop()}
          </NavSection>

        <NewNotebookNavigation onClick={this.openNotebook} />

          <Listing>
            {atRoot ? null : (
              // TODO: Create a contentRef for `..`, even though it's a placeholder
              // When we're not at the root of the tree, show `..`
              <Entry>
                <Icon fileType={"directory"} />
                <Name>{dotdotlink}</Name>
              </Entry>
            )}
            {this.props.contents.map((entry, index) => {
              const link = (

                  {entry.name}

              );
              return (
                <Entry key={index}>
                  <Icon fileType={entry.type} />
                  <Name>{link}</Name>
                  <LastSaved lastModified={entry.last_modified} />
                </Entry>
              );
            })}
          </Listing>

      </React.Fragment>
    );
  }
}

const mapStateToDirectoryProps = (
  state: AppState,
  ownProps: { contentRef: ContentRef }
): DirectoryProps => {
  const host = selectors.currentHost(state);
  const content = selectors.content(state, ownProps);

  if (host.type !== "jupyter") {
    throw new Error("This component only works with jupyter servers");
  }

  if (!content || content.type !== "directory") {
    throw new Error(
      "The directory component should only be used with directory contents"
    );
  }

  let contents = [];
  content.model.items.map(x => {
    const row = selectors.content(state, { contentRef: x });
    if (!row) {
      return {
        last_modified: new Date(),
        name: "",
        path: "",
        type: ""
      };
    }

    if (row.type !== "dummy") {
      return null;
    }

    contents.push({
      last_modified: row.lastSaved,
      name: row.filepath,
      path: row.filepath,
      type: row.assumedType
    });
  });

  return {
    appVersion: selectors.appVersion(state),
    content,
    contentRef,
    host,
    contents
  };
};

export const ConnectedDirectory = connect(mapStateToDirectoryProps)(
  DirectoryApp
);

export default ConnectedDirectory;
<commit_msg>add const for contentRef<commit_after>/* @flow strict */

import * as React from "react";
import { selectors } from "@nteract/core";
import { NewNotebookNavigation } from "@nteract/connected-components";
import {
  Entry,
  Listing,
  Icon,
  Name,
  LastSaved
} from "@nteract/directory-listing";
import type {
  AppState,
  KernelspecRecord,
  KernelspecProps,
  JupyterHostRecord,
  ContentRef,
  DirectoryContentRecord
} from "@nteract/core";
import { connect } from "react-redux";

import { Nav, NavSection } from "../components/nav";
import { openNotebook } from "../triggers/open-notebook";
import { ThemedLogo } from "../components/themed-logo.js";

const urljoin = require("url-join");

type DirectoryProps = {
  content: DirectoryContentRecord,
  host: JupyterHostRecord,
  appVersion: string,
  contentRef: ContentRef,
  contents: Array<{
    path: string,
    type: NotebookTypes,
    name: string,
    last_modified: ?Date
  }>
};

export class DirectoryApp extends React.PureComponent<DirectoryProps, null> {
  openNotebook = (ks: KernelspecRecord | KernelspecProps) => {
    openNotebook(this.props.host, ks, {
      appVersion: this.props.appVersion,
      // Since we're looking at a directory, the base dir is the directory we are in
      baseDir: this.props.content.filepath,
      appPath: this.props.host.basePath
    });
  };

  render() {
    const atRoot = this.props.content.filepath === "/";

    const dotdothref = urljoin(
      this.props.host.basePath,
      "/nteract/edit/",
      urljoin(this.props.content.filepath, "..")
    );
    const basePath = this.props.host.basePath;
    const dotdotlink = {".."};
    return (
      <React.Fragment>

          <NavSection>

              <ThemedLogo />

            {this.props.content.filepath.split("/").pop()}
          </NavSection>

        <NewNotebookNavigation onClick={this.openNotebook} />

          <Listing>
            {atRoot ? null : (
              // TODO: Create a contentRef for `..`, even though it's a placeholder
              // When we're not at the root of the tree, show `..`
              <Entry>
                <Icon fileType={"directory"} />
                <Name>{dotdotlink}</Name>
              </Entry>
            )}
            {this.props.contents.map((entry, index) => {
              const link = (

                  {entry.name}

              );
              return (
                <Entry key={index}>
                  <Icon fileType={entry.type} />
                  <Name>{link}</Name>
                  <LastSaved lastModified={entry.last_modified} />
                </Entry>
              );
            })}
          </Listing>

      </React.Fragment>
    );
  }
}

const mapStateToDirectoryProps = (
  state: AppState,
  ownProps: { contentRef: ContentRef }
): DirectoryProps => {
  const host = selectors.currentHost(state);
  const content = selectors.content(state, ownProps);
  const contentRef = ownProps.contentRef;

  if (host.type !== "jupyter") {
    throw new Error("This component only works with jupyter servers");
  }

  if (!content || content.type !== "directory") {
    throw new Error(
      "The directory component should only be used with directory contents"
    );
  }

  let contents = [];
  content.model.items.map(x => {
    const row = selectors.content(state, { contentRef: x });
    if (!row) {
      return {
        last_modified: new Date(),
        name: "",
        path: "",
        type: ""
      };
    }

    if (row.type !== "dummy") {
      return null;
    }

    contents.push({
      last_modified: row.lastSaved,
      name: row.filepath,
      path: row.filepath,
      type: row.assumedType
    });
  });

  return {
    appVersion: selectors.appVersion(state),
    content,
    contentRef,
    host,
    contents
  };
};

export const ConnectedDirectory = connect(mapStateToDirectoryProps)(
  DirectoryApp
);

export default ConnectedDirectory;
<|endoftext|>


<commit_before>(function() {

    // set the entries { title: url }
    var entries = {
      "About FL": "http://focallocal.org",
      "Profile": "http://focallocal.org",
      "FL News": "http://focallocal.org",
      "Events": "http://focallocal.org",
      "Shop": "http://focallocal.org",
    }

  var loadCss = function(cssPath) {
    link = document.createElement( "link" );
    link.href = cssPath;
    link.type = "text/css";
    link.rel = "stylesheet";
    link.media = "screen,print";
    document.getElementsByTagName( "head" )[0].appendChild( link );
  }

   /*

Home

About

Projects

Contact

  */
  var createEntry = function(title, href) {
    var listElement = document.createElement("li");
    var link = document.createElement("a");
    link.href = href;
    link.innerHTML = title;
    listElement.appendChild(link);
    return listElement;
  }

  var wrapper = document.createElement("div");
  var menu = document.createElement("nav");
  var container = document.createElement("ul");
  var logo = document.createElement("a");

  wrapper.id = "fl-menu";
  logo.className = "menu-logo";
  logo.innerHTML = "Focallocal";
  menu.className = "nav-collapse";

  wrapper.appendChild(logo);
  document.body.insertBefore(wrapper, document.body.firstChild); // add the fl-menu to the body as first child
  menu.appendChild(container);
  wrapper.appendChild(menu);

  // add entries to container
  for(var title in entries) {
    if(entries.hasOwnProperty(title)) {
      var url = entries[title];
      var entry = createEntry(title, url);
      entry.className = "";
      container.appendChild(entry);
    }
  }

  // las entry gets special css treament
  container.lastChild.className += " last-child";

  // settings for responsiveNav
  var responsiveNavSetting = {
  }

  // make navigation responsive
  var nav = responsiveNav(".nav-collapse", responsiveNavSetting);

  /* LOAD CSS */
  // get URL to folder, from which this script was loaded and include fl-menu.css
  var scriptEls = document.getElementsByTagName( 'script' );
  var thisScriptEl = scriptEls[scriptEls.length - 1];
  var scriptPath = thisScriptEl.src;
  var scriptFolder = scriptPath.substr(0, scriptPath.lastIndexOf( '/' )+1 );
  loadCss(scriptFolder + "fl-menu.all.css");

})();
<commit_msg>fl-menu hides on scrolldown and reappears on scrollup<commit_after>(function() {

    // set the entries { title: url }
    var entries = {
      "About FL": "http://focallocal.org",
      "Profile": "http://focallocal.org",
      "FL News": "http://focallocal.org",
      "Events": "http://focallocal.org",
      "Shop": "http://focallocal.org",
    }

  var loadCss = function(cssPath) {
    link = document.createElement( "link" );
    link.href = cssPath;
    link.type = "text/css";
    link.rel = "stylesheet";
    link.media = "screen,print";
    document.getElementsByTagName( "head" )[0].appendChild( link );
  }

   /*

Home

About

Projects

Contact

  */
  var createEntry = function(title, href) {
    var listElement = document.createElement("li");
    var link = document.createElement("a");
    link.href = href;
    link.innerHTML = title;
    listElement.appendChild(link);
    return listElement;
  }

  var wrapper = document.createElement("div");
  var menu = document.createElement("nav");
  var container = document.createElement("ul");
  var logo = document.createElement("a");

  wrapper.id = "fl-menu";
  logo.className = "menu-logo";
  logo.innerHTML = "Focallocal";
  menu.className = "nav-collapse";

  wrapper.appendChild(logo);
  document.body.insertBefore(wrapper, document.body.firstChild); // add the fl-menu to the body as first child
  menu.appendChild(container);
  wrapper.appendChild(menu);

  // add entries to container
  for(var title in entries) {
    if(entries.hasOwnProperty(title)) {
      var url = entries[title];
      var entry = createEntry(title, url);
      entry.className = "";
      container.appendChild(entry);
    }
  }

  // las entry gets special css treament
  container.lastChild.className += " last-child";

  // settings for responsiveNav
  var responsiveNavSetting = {
  }

  // make navigation responsive
  var nav = responsiveNav(".nav-collapse", responsiveNavSetting);

  /* LOAD CSS */
  // get URL to folder, from which this script was loaded and include fl-menu.css
  var scriptEls = document.getElementsByTagName( 'script' );
  var thisScriptEl = scriptEls[scriptEls.length - 1];
  var scriptPath = thisScriptEl.src;
  var scriptFolder = scriptPath.substr(0, scriptPath.lastIndexOf( '/' )+1 );
  loadCss(scriptFolder + "fl-menu.all.css");

  /* Scrolling functionality
   * hide when scrolling down
   * show when scrolling up again (like top bar on google plus)
   *
   * @todo optimize function calls
   */
  var oldScrollPosition = document.body.scrollTop;
  document.onscroll = function() {
    // scrollind down
    if(document.body.scrollTop > oldScrollPosition) {
      wrapper.style.display = 'none';
    } else {
      wrapper.style.display = '';
    }
    // set
    oldScrollPosition = document.body.scrollTop;
  }

})();
<|endoftext|>


<commit_before>function checnum(as)
{
var dd = as.value;
if(isNaN(dd))
{
dd = dd.substring(0,(dd.length-1));
as.value = dd;
}
} function loan() {
var a = document.first.aa.value;
var b = document.first.bb.value;
var c = document.first.cc.value;
var n = c * 12;
var r = b/(12*100);
var p = (a * r *Math.pow((1+r),n))/(Math.pow((1+r),n)-1);
var prin = Math.round(p*100)/100;
document.first.monthlyPaymentField.value = prin;
var mon = Math.round(((n * prin) - a)*100)/100;
document.first.monthlyAverageInterestField.value = mon;
var tot = Math.round((mon/n)*100)/100;
document.first.monthlyInterestField.value = tot;
for(var i=0;i<n;i++)
{
var z = a * r * 1;
var q = Math.round(z*100)/100;
var t = p - z;
var w = Math.round(t*100)/100;
var e = a-t;
var l = Math.round(e*100)/100;
a=e;
} }

<commit_msg>Mortgage JS edit<commit_after>function checnum(as)
{
var dd = as.value;
if(isNaN(dd))
{
dd = dd.substring(0,(dd.length-1));
as.value = dd;
}

} function loan() {
var a = document.first.aa.value;
var b = document.first.bb.value;
var c = document.first.cc.value;
var n = c * 12;
var r = b/(12*100);
var p = (a * r *Math.pow((1+r),n))/(Math.pow((1+r),n)-1);
var prin = Math.round(p*100)/100;
document.first.monthlyPaymentField.value = prin;
var mon = Math.round(((n * prin) - a)*100)/100;
document.first.monthlyAverageInterestField.value = mon;
var tot = Math.round((mon/n)*100)/100;
document.first.monthlyInterestField.value = tot;
for(var i=0;i<n;i++)
{
var z = a * r * 1;
var q = Math.round(z*100)/100;
var t = p - z;
var w = Math.round(t*100)/100;
var e = a-t;
var l = Math.round(e*100)/100;
a=e;
} }

<|endoftext|>


<commit_before>"use strict";

var childProcess = require("child_process");
var EventEmitter = require("events");

var spawn = require("../../../../lib/spawn");

var base = require("../base.spec");

describe("lib/spawn", function () {
  beforeEach(function () {
    base.sandbox.stub(childProcess, "spawn", function () {
      var proc = new EventEmitter();
      base.sandbox.spy(proc, "on");
      return proc;
    });
  });

  it("calls child_process.spawn", function () {
    spawn("echo", ["hello"], {});
    expect(childProcess.spawn).to.be.calledWithMatch("echo", ["hello"], {});
  });

  it("listens to error and close events", function () {
    var proc = spawn("echo", ["hello"], {});
    expect(proc.on).to.be.calledWith("error");
    expect(proc.on).to.be.calledWith("close");
  });

  it("calls the given callback upon close", function () {
    var callback = base.sandbox.spy();
    var proc = spawn("echo", ["hello"], {}, callback);
    proc.emit("close", 0, null);
    expect(callback).to.be.calledWith(null);
  });

  it("passes error to the callback upon error", function () {
    var callback = base.sandbox.spy();
    var proc = spawn("echo", ["hello"], {}, callback);
    proc.emit("error", { message: "uh-oh" });
    expect(callback).to.not.be.called;
    proc.emit("close", 0, null);
    expect(callback).to.be.calledWithMatch({ message: "uh-oh" });
  });

  it("passes error to the callback upon non-zero exit", function () {
    var callback = base.sandbox.spy();
    var proc = spawn("echo", ["hello"], {}, callback);
    expect(callback).to.not.be.called;
    proc.emit("close", 1, null);
    expect(callback).to.be.calledWithMatch({ code: 1 });
  });
});
<commit_msg>Fix node 0.10 EventEmitter import<commit_after>"use strict";

var childProcess = require("child_process");
var EventEmitter = require("events").EventEmitter;

var spawn = require("../../../../lib/spawn");

var base = require("../base.spec");

describe("lib/spawn", function () {
  beforeEach(function () {
    base.sandbox.stub(childProcess, "spawn", function () {
      var proc = new EventEmitter();
      base.sandbox.spy(proc, "on");
      return proc;
    });
  });

  it("calls child_process.spawn", function () {
    spawn("echo", ["hello"], {});
    expect(childProcess.spawn).to.be.calledWithMatch("echo", ["hello"], {});
  });

  it("listens to error and close events", function () {
    var proc = spawn("echo", ["hello"], {});
    expect(proc.on).to.be.calledWith("error");
    expect(proc.on).to.be.calledWith("close");
  });

  it("calls the given callback upon close", function () {
    var callback = base.sandbox.spy();
    var proc = spawn("echo", ["hello"], {}, callback);
    proc.emit("close", 0, null);
    expect(callback).to.be.calledWith(null);
  });

  it("passes error to the callback upon error", function () {
    var callback = base.sandbox.spy();
    var proc = spawn("echo", ["hello"], {}, callback);
    proc.emit("error", { message: "uh-oh" });
    expect(callback).to.not.be.called;
    proc.emit("close", 0, null);
    expect(callback).to.be.calledWithMatch({ message: "uh-oh" });
  });

  it("passes error to the callback upon non-zero exit", function () {
    var callback = base.sandbox.spy();
    var proc = spawn("echo", ["hello"], {}, callback);
    expect(callback).to.not.be.called;
    proc.emit("close", 1, null);
    expect(callback).to.be.calledWithMatch({ code: 1 });
  });
});
<|endoftext|>


<commit_before>// ==UserScript==
// @name           Display price history on Amazon.co.jp
// @namespace      curipha
// @description    Import price history from Keepa.com
// @include        https://www.amazon.co.jp/gp/product/*
// @include        https://www.amazon.co.jp/dp/*
// @include        https://www.amazon.co.jp/*/dp/*
// @exclude        https://www.amazon.co.jp/ap/*
// @exclude        https://www.amazon.co.jp/hz/*
// @exclude        https://www.amazon.co.jp/clouddrive*
// @version        0.1.4
// @grant          none
// @noframes
// ==/UserScript==

(function() {
  'use strict';

  const width = 580;
  const height = 240;

  let current_asin = '';

  const get_asin = function() {
    const asinform = document.getElementById('ASIN'); // or location.pathname.match(/\b[0-9A-Z]{10}\b/)
    return asinform ? asinform.value : null;
  };

  const insert_graph = function() {
    const pricediv = document.getElementById('unifiedPrice_feature_div');
    if (pricediv) {
      const asin = get_asin();
      if (asin) {
        current_asin = asin; // Store ASIN code to check variation change

        const history = document.createElement('div');
        history.className = 'a-section a-spacing-small';

        const anchor = document.createElement('a');
        anchor.href = `https://keepa.com/#!product/5-${asin}`;
        anchor.rel = 'noopener noreferrer';
        anchor.referrerPolicy = 'no-referrer';

        const image = document.createElement('img');
        image.src = `https://graph.keepa.com/pricehistory.png?domain=co.jp&asin=${asin}&width=${width}&height=${height}`;
        image.alt = 'Price history';
        image.width = width;
        image.height = height;
        image.referrerPolicy = 'no-referrer';

        anchor.appendChild(image);
        history.appendChild(anchor);
        pricediv.appendChild(history);
      }
    }
  };

  insert_graph();

  // Update the graph when changing product variations
  const target = document.getElementById('desktop_buybox') || document.getElementById('buybox_feature_div');
  if (target) {
    const update_graph = (mutations) => {
      mutations.forEach((mutation) => {
        if (mutation.addedNodes.length > 0 && get_asin() !== current_asin) {
          insert_graph();
        }
      });
    };

    const mo = new MutationObserver(update_graph);
    mo.observe(target, { childList: true });
  }
})();
<commit_msg>Currently noopener is default in modern browsers<commit_after>// ==UserScript==
// @name           Display price history on Amazon.co.jp
// @namespace      curipha
// @description    Import price history from Keepa.com
// @include        https://www.amazon.co.jp/gp/product/*
// @include        https://www.amazon.co.jp/dp/*
// @include        https://www.amazon.co.jp/*/dp/*
// @exclude        https://www.amazon.co.jp/ap/*
// @exclude        https://www.amazon.co.jp/hz/*
// @exclude        https://www.amazon.co.jp/clouddrive*
// @version        0.1.4
// @grant          none
// @noframes
// ==/UserScript==

(function() {
  'use strict';

  const width = 580;
  const height = 240;

  let current_asin = '';

  const get_asin = function() {
    const asinform = document.getElementById('ASIN'); // or location.pathname.match(/\b[0-9A-Z]{10}\b/)
    return asinform ? asinform.value : null;
  };

  const insert_graph = function() {
    const pricediv = document.getElementById('unifiedPrice_feature_div');
    if (pricediv) {
      const asin = get_asin();
      if (asin) {
        current_asin = asin; // Store ASIN code to check variation change

        const history = document.createElement('div');
        history.className = 'a-section a-spacing-small';

        const anchor = document.createElement('a');
        anchor.href = `https://keepa.com/#!product/5-${asin}`;
        anchor.rel = 'noreferrer';
        anchor.referrerPolicy = 'no-referrer';

        const image = document.createElement('img');
        image.src = `https://graph.keepa.com/pricehistory.png?domain=co.jp&asin=${asin}&width=${width}&height=${height}`;
        image.alt = 'Price history';
        image.width = width;
        image.height = height;
        image.referrerPolicy = 'no-referrer';

        anchor.appendChild(image);
        history.appendChild(anchor);
        pricediv.appendChild(history);
      }
    }
  };

  insert_graph();

  // Update the graph when changing product variations
  const target = document.getElementById('desktop_buybox') || document.getElementById('buybox_feature_div');
  if (target) {
    const update_graph = (mutations) => {
      mutations.forEach((mutation) => {
        if (mutation.addedNodes.length > 0 && get_asin() !== current_asin) {
          insert_graph();
        }
      });
    };

    const mo = new MutationObserver(update_graph);
    mo.observe(target, { childList: true });
  }
})();
<|endoftext|>


<commit_before>/*
 * Andraia
 * A lightweight javascript MVC framework for building simple mobile apps.
 * https://github.com/grevory/andraia-js-mobile-framework
 *
 * Copyright (c) 2014 Gregory Pike
 * Licensed under the MIT license.
 */

/* global $:false */
/* global console:false */
/* global window:false */
/* global PageSlider:false */
/* global _:false */
/* global Handlebars:false */
/* global Mustache:false */
/* global EJS:false */
/* global FastClick:false */

function Andraia(elementContainerId, userSettings) {

  var self = this,
      defaultSettings = {},
      settings = {},
      getElementId = null,
      slider = null,
      loadTemplate = null,
      _templateEngine = null,
      _templateHeader = '',
      _templateFooter = '',
      _loadedTemplate = '',
      _controller = null,
      error = null,
      debugError = null;

  defaultSettings = {
    'templateDirectory': 'templates/',

    'enablePageslider': true,
    'enableFastclick': true,
    'enableRouter': true,

    'templateEngine': '',

    'pageTransitionSpeed': 0.25
  };

  $.extend(settings, defaultSettings, userSettings);

  // Data in memory
  this.controllers = {};
  this.models = {};
  this.helpers = {};
  this.templateData = {};

  // Adds a hash to an element ID if it is not there for jQuery selectors
  getElementId = function(id) {
    if (id.charAt(0) !== '#') {
      return '#' + id;
    }
    return id;
  };

  // Handle errors in the app
  this.error = function(errorMessage, debugMessage) {
    if (!errorMessage) {
      return error;
    }
    error = errorMessage;
    debugError = debugMessage;
    console.error(debugMessage || errorMessage);
    // alert(errorMessage);
  };

  // Model
  // A generic function for handling models
  this.model = function(modelName, modelFunction) {

    var modelLoaded = null;

    if ($.isFunction(modelFunction)) {
      self.models[modelName] = modelFunction;
    }

    if (!modelFunction && $.isFunction(self.models[modelName])) {
      modelLoaded = new self.models[modelName](self.helpers);
      return modelLoaded;
    }
  };

  // Add a model to memory
  // Shortcut to model()
  this.registerModel = function(modelName, modelFunction) {
    return self.model(modelName, modelFunction);
  };

  // Grab the model for your app
  this.loadModel = function(modelName) {
    return self.model(modelName);
  };

  // Check to see if a model is already loaded
  this.hasModel = function(modelName) {
    return !!self.models[modelName];
  };

  // Add helpers to memory for reusable functions
  this.registerHelper = function(name, helperFunction) {
    self.helpers[name] = helperFunction;
  };

  // This is where we store laoded templates
  loadTemplate = function(id) {
    var _elementId = getElementId(id),
        deferred = $.Deferred();

    if ($(_elementId).size() > 0) {
      // Load the template into memory
      _loadedTemplate = $(_elementId).html();

      deferred.resolve();
      return deferred;
    }

    // Example URL: templates/newView.html
    $.get(settings.templateDirectory + _elementId.substr(1) + '.html', function(html) {
      _loadedTemplate = html;
      $('body').append('');
      deferred.resolve();
    });

    return deferred;
  };

  this.registerView = function(viewName, controllerFunction, data) {

    viewName = getElementId(viewName);

    // If the action item is a function then it must be a controller.
    // Add the controller function to memory
    if ($.isFunction(controllerFunction)){ // && ($(self.controllers).size() < 1 || !$.isFunction(self.controllers[viewName]))) {
      self.controllers[viewName] = controllerFunction;
    }

    if (!!data) {
      self.templateData[viewName] = data;
    }
  };

  // The generic view method for loading views and storing controllers
  this.view = function(viewName, controllerFunction, data) {

    var _template;

    viewName = getElementId(viewName);

    // If the action item is a function then it must be a controller.
    // Add the controller function to memory
    if ($.isFunction(controllerFunction)){ // && ($(self.controllers).size() < 1 || !$.isFunction(self.controllers[viewName]))) {
      self.controllers[viewName] = controllerFunction;
    }

    if (!!data) {
      self.templateData[viewName] = data;
    }

    // Load the template. When the template is loaded we will apply any
    // templating as necessary and load the controller for the view
    loadTemplate(viewName).done(function(){
      // Fetch the template for this view from memory
      // _template = self.templates[viewName];
      // Run the templating engine on the template using any user-defined data
      _loadedTemplate = self.template(_loadedTemplate, self.templateData[viewName]);
      // Slide the page to this view
      slider.slidePage($(_loadedTemplate), "left");
      // If there is a controller for this view, load it
      if ($(self.controllers).size() > 0 && $.isFunction(self.controllers[viewName])) {
        _controller = new self.controllers[viewName](self.helpers);
      }
    });
  };

  // Shortcut to view specifically for loading a view
  this.loadView = function(viewName) {
    viewName = getElementId(viewName);

    if (settings.enableRouter && !self.router.currentPage || self.router.currentPage !== viewName) {
      window.location.hash = viewName;
    }

    if (settings.enablePageslider && $.isFunction(PageSlider) && !slider) {
      slider = new PageSlider($(elementContainerId));
    }

    // Load the template. When the template is loaded we will apply any
    // templating as necessary and load the controller for the view
    loadTemplate(viewName).done(function(){
      // Run the templating engine on the template using any user-defined data
      _loadedTemplate = self.template(_loadedTemplate, self.templateData[viewName]);

      // Slide the page to this view
      if (!!slider) {
        slider.slidePage($(_loadedTemplate), "left");
      } else {
        $(elementContainerId).html(_loadedTemplate);
      }

      // If there is a controller for this view, load it
      if ($(self.controllers).size() > 0 && $.isFunction(self.controllers[viewName])) {
        _controller = new self.controllers[viewName](self.helpers);
      }
    });
  };

  // Define how the template is to be rendered.
  _templateEngine = function(template, data) {

    if (!data) {
      return template;
    }

    var compiled;

    function isEngine(engineType) {
      // Get the lowercase version of the template engine name (for consistency)
      var usersTemplateEngine = settings.templateEngine.toLowerCase();
      // Check for this engine type in the users selected template engine
      // E.g. search for "mustache in Mustache.js"
      return usersTemplateEngine.indexOf(engineType) >= 0;
    }

    // Use Underscore's templating
    if (typeof _ !== "undefined" && $.isFunction(_) && isEngine('underscore')) {
      compiled = _.template(template);
      return compiled(data);
    }

    // Use Handlebar's templating
    if (typeof Handlebars !== "undefined" && isEngine('handlebars')) {
      compiled = Handlebars.compile(template);
      template = compiled(data);
    }

    // Use Mustache's templating
    if (typeof Mustache !== "undefined" && isEngine('mustache')) {
      template = Mustache.render(template, data);
    }

    if (typeof EJS !== "undefined" && isEngine('ejs')) {
      template = new EJS({text: template}).render(data);
    }

    return template;
  };

  // A generic function for handling templates
  this.template = function(template, data) {
    // Set the template engine if a function is passed
    if ($.isFunction(template)) {
      _templateEngine = template;
      return;
    }

    if (!_templateEngine) {
      self.error('No template engine loaded');
    }

    if (!template) {
      return '';
    }

    template = '
' + _templateHeader + _templateEngine(template) + _templateFooter + '
';

    if (!data) {
      return template;
    }

    return _templateEngine(template, data);
  };

  // A shortcut for adding templating to the app
  this.registerTemplating = function(templateFunction) {
    return self.template(templateFunction);
  };

  var extractTemplate = function(htmlString) {
    // Convert header HTML to string
    htmlString = "" + htmlString;

    if (htmlString.indexOf('.html') > -1) {
      self.error('This option is not yet available. Cannot load header or footer template from external file.');
      return console.log('Go get template');
    }

    // If there are no HTML tags and the headerHtml string is the id of an element let's get the HTML of that element
    if (htmlString.indexOf('<') < 0 && $(getElementId(htmlString)).size() > 0) {
      htmlString = $(getElementId(htmlString)).html();
    }

    return htmlString;
  };

  this.registerTemplateHeader = function(headerHtml) {
    _templateHeader = extractTemplate(headerHtml);
  };

  this.registerTemplateFooter = function(footerHtml) {
    _templateFooter = extractTemplate(footerHtml);
  };

  if (!elementContainerId) {
    elementContainerId = 'body';
  }
  else {
    elementContainerId = getElementId(elementContainerId);
  }

  if (settings.enableFastclick) {
    window.addEventListener('load', function () {
      new FastClick(window.document.body);
    }, false);
  }

  this.router = {
    currentPage: window.location.hash,
    changePage: function (pageId) {
      pageId = getElementId(pageId);

      // If there is no current page we do not want to load the view until the app is ready.
      if (!!self.router.currentPage) {
        self.loadView(pageId);
      }

      if (!self.router.currentPage || self.router.currentPage === pageId) {
        window.location.hash = pageId;
      }

      self.router.currentPage = pageId;
    }
  };

  if (settings.enableRouter) {
    var pageHash = '';

    window.addEventListener('hashchange', function () {
      var changePage = self.router.changePage;
      pageHash = window.location.hash;
      if (!!pageHash) {
        changePage(pageHash);
      }
    });
  }

  if (settings.pageTransitionSpeed !== defaultSettings.pageTransitionSpeed) {
    // Add styles to overwrite the page transition speed at the end of the head
    // The default is 0.25.
    // Don't bother with the overwrite if the user is using the default
    $('head').append(' ' +
      '');
  }
}
<commit_msg>Validate helpers<commit_after>/*
 * Andraia
 * A lightweight javascript MVC framework for building simple mobile apps.
 * https://github.com/grevory/andraia-js-mobile-framework
 *
 * Copyright (c) 2014 Gregory Pike
 * Licensed under the MIT license.
 */

/* global $:false */
/* global console:false */
/* global window:false */
/* global PageSlider:false */
/* global _:false */
/* global Handlebars:false */
/* global Mustache:false */
/* global EJS:false */
/* global FastClick:false */

function Andraia(elementContainerId, userSettings) {

  var self = this,
      defaultSettings = {},
      settings = {},
      getElementId = null,
      slider = null,
      loadTemplate = null,
      _templateEngine = null,
      _templateHeader = '',
      _templateFooter = '',
      _loadedTemplate = '',
      _controller = null,
      error = null,
      debugError = null;

  defaultSettings = {
    'templateDirectory': 'templates/',

    'enablePageslider': true,
    'enableFastclick': true,
    'enableRouter': true,

    'templateEngine': '',

    'pageTransitionSpeed': 0.25
  };

  $.extend(settings, defaultSettings, userSettings);

  // Data in memory
  this.controllers = {};
  this.models = {};
  this.helpers = {};
  this.templateData = {};

  // Adds a hash to an element ID if it is not there for jQuery selectors
  getElementId = function(id) {
    if (id.charAt(0) !== '#') {
      return '#' + id;
    }
    return id;
  };

  // Handle errors in the app
  this.error = function(errorMessage, debugMessage) {
    if (!errorMessage) {
      return error;
    }
    error = errorMessage;
    debugError = debugMessage;
    console.error(debugMessage || errorMessage);
    // alert(errorMessage);
  };

  // Model
  // A generic function for handling models
  this.model = function(modelName, modelFunction) {

    var modelLoaded = null;

    if ($.isFunction(modelFunction)) {
      self.models[modelName] = modelFunction;
    }

    if (!modelFunction && $.isFunction(self.models[modelName])) {
      modelLoaded = new self.models[modelName](self.helpers);
      return modelLoaded;
    }
  };

  // Add a model to memory
  // Shortcut to model()
  this.registerModel = function(modelName, modelFunction) {
    return self.model(modelName, modelFunction);
  };

  // Grab the model for your app
  this.loadModel = function(modelName) {
    return self.model(modelName);
  };

  // Check to see if a model is already loaded
  this.hasModel = function(modelName) {
    return !!self.models[modelName];
  };

  // Add helpers to memory for reusable functions
  this.registerHelper = function(name, helperFunction) {
    if ($.type(name) !== "string") {
      return self.error('Could not register helper. The name is not a string.');
    }

    if ($.type(helperFunction) !== "function") {
      return self.error('Could not register helper. The function is not a function.');
    }

    self.helpers[name] = helperFunction;
  };

  // This is where we store laoded templates
  loadTemplate = function(id) {
    var _elementId = getElementId(id),
        deferred = $.Deferred();

    if ($(_elementId).size() > 0) {
      // Load the template into memory
      _loadedTemplate = $(_elementId).html();

      deferred.resolve();
      return deferred;
    }

    // Example URL: templates/newView.html
    $.get(settings.templateDirectory + _elementId.substr(1) + '.html', function(html) {
      _loadedTemplate = html;
      $('body').append('');
      deferred.resolve();
    });

    return deferred;
  };

  this.registerView = function(viewName, controllerFunction, data) {

    viewName = getElementId(viewName);

    // If the action item is a function then it must be a controller.
    // Add the controller function to memory
    if ($.isFunction(controllerFunction)){ // && ($(self.controllers).size() < 1 || !$.isFunction(self.controllers[viewName]))) {
      self.controllers[viewName] = controllerFunction;
    }

    if (!!data) {
      self.templateData[viewName] = data;
    }
  };

  // The generic view method for loading views and storing controllers
  this.view = function(viewName, controllerFunction, data) {

    var _template;

    viewName = getElementId(viewName);

    // If the action item is a function then it must be a controller.
    // Add the controller function to memory
    if ($.isFunction(controllerFunction)){ // && ($(self.controllers).size() < 1 || !$.isFunction(self.controllers[viewName]))) {
      self.controllers[viewName] = controllerFunction;
    }

    if (!!data) {
      self.templateData[viewName] = data;
    }

    // Load the template. When the template is loaded we will apply any
    // templating as necessary and load the controller for the view
    loadTemplate(viewName).done(function(){
      // Fetch the template for this view from memory
      // _template = self.templates[viewName];
      // Run the templating engine on the template using any user-defined data
      _loadedTemplate = self.template(_loadedTemplate, self.templateData[viewName]);
      // Slide the page to this view
      slider.slidePage($(_loadedTemplate), "left");
      // If there is a controller for this view, load it
      if ($(self.controllers).size() > 0 && $.isFunction(self.controllers[viewName])) {
        _controller = new self.controllers[viewName](self.helpers);
      }
    });
  };

  // Shortcut to view specifically for loading a view
  this.loadView = function(viewName) {
    viewName = getElementId(viewName);

    if (settings.enableRouter && !self.router.currentPage || self.router.currentPage !== viewName) {
      window.location.hash = viewName;
    }

    if (settings.enablePageslider && $.isFunction(PageSlider) && !slider) {
      slider = new PageSlider($(elementContainerId));
    }

    // Load the template. When the template is loaded we will apply any
    // templating as necessary and load the controller for the view
    loadTemplate(viewName).done(function(){
      // Run the templating engine on the template using any user-defined data
      _loadedTemplate = self.template(_loadedTemplate, self.templateData[viewName]);

      // Slide the page to this view
      if (!!slider) {
        slider.slidePage($(_loadedTemplate), "left");
      } else {
        $(elementContainerId).html(_loadedTemplate);
      }

      // If there is a controller for this view, load it
      if ($(self.controllers).size() > 0 && $.isFunction(self.controllers[viewName])) {
        _controller = new self.controllers[viewName](self.helpers);
      }
    });
  };

  // Define how the template is to be rendered.
  _templateEngine = function(template, data) {

    if (!data) {
      return template;
    }

    var compiled;

    function isEngine(engineType) {
      // Get the lowercase version of the template engine name (for consistency)
      var usersTemplateEngine = settings.templateEngine.toLowerCase();
      // Check for this engine type in the users selected template engine
      // E.g. search for "mustache in Mustache.js"
      return usersTemplateEngine.indexOf(engineType) >= 0;
    }

    // Use Underscore's templating
    if (typeof _ !== "undefined" && $.isFunction(_) && isEngine('underscore')) {
      compiled = _.template(template);
      return compiled(data);
    }

    // Use Handlebar's templating
    if (typeof Handlebars !== "undefined" && isEngine('handlebars')) {
      compiled = Handlebars.compile(template);
      template = compiled(data);
    }

    // Use Mustache's templating
    if (typeof Mustache !== "undefined" && isEngine('mustache')) {
      template = Mustache.render(template, data);
    }

    if (typeof EJS !== "undefined" && isEngine('ejs')) {
      template = new EJS({text: template}).render(data);
    }

    return template;
  };

  // A generic function for handling templates
  this.template = function(template, data) {
    // Set the template engine if a function is passed
    if ($.isFunction(template)) {
      _templateEngine = template;
      return;
    }

    if (!_templateEngine) {
      self.error('No template engine loaded');
    }

    if (!template) {
      return '';
    }

    template = '
' + _templateHeader + _templateEngine(template) + _templateFooter + '
';

    if (!data) {
      return template;
    }

    return _templateEngine(template, data);
  };

  // A shortcut for adding templating to the app
  this.registerTemplating = function(templateFunction) {
    return self.template(templateFunction);
  };

  var extractTemplate = function(htmlString) {
    // Convert header HTML to string
    htmlString = "" + htmlString;

    if (htmlString.indexOf('.html') > -1) {
      self.error('This option is not yet available. Cannot load header or footer template from external file.');
      return console.log('Go get template');
    }

    // If there are no HTML tags and the headerHtml string is the id of an element let's get the HTML of that element
    if (htmlString.indexOf('<') < 0 && $(getElementId(htmlString)).size() > 0) {
      htmlString = $(getElementId(htmlString)).html();
    }

    return htmlString;
  };

  this.registerTemplateHeader = function(headerHtml) {
    _templateHeader = extractTemplate(headerHtml);
  };

  this.registerTemplateFooter = function(footerHtml) {
    _templateFooter = extractTemplate(footerHtml);
  };

  if (!elementContainerId) {
    elementContainerId = 'body';
  }
  else {
    elementContainerId = getElementId(elementContainerId);
  }

  if (settings.enableFastclick) {
    window.addEventListener('load', function () {
      new FastClick(window.document.body);
    }, false);
  }

  this.router = {
    currentPage: window.location.hash,
    changePage: function (pageId) {
      pageId = getElementId(pageId);

      // If there is no current page we do not want to load the view until the app is ready.
      if (!!self.router.currentPage) {
        self.loadView(pageId);
      }

      if (!self.router.currentPage || self.router.currentPage === pageId) {
        window.location.hash = pageId;
      }

      self.router.currentPage = pageId;
    }
  };

  if (settings.enableRouter) {
    var pageHash = '';

    window.addEventListener('hashchange', function () {
      var changePage = self.router.changePage;
      pageHash = window.location.hash;
      if (!!pageHash) {
        changePage(pageHash);
      }
    });
  }

  if (settings.pageTransitionSpeed !== defaultSettings.pageTransitionSpeed) {
    // Add styles to overwrite the page transition speed at the end of the head
    // The default is 0.25.
    // Don't bother with the overwrite if the user is using the default
    $('head').append(' ' +
      '');
  }
}
<|endoftext|>