<commit_before>// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import {RemoteObjectPreviewFormatter} from './RemoteObjectPreviewFormatter.js';

export class JavaScriptREPL {
  /**
   * @param {string} code
   * @return {string}
   */
  static wrapObjectLiteral(code) {
    // Only parenthesize what appears to be an object literal.
    if (!(/^\s*\{/.test(code) && /\}\s*$/.test(code))) {
      return code;
    }

    const parse = (async () => 0).constructor;
    try {
      // Check if the code can be interpreted as an expression.
      parse('return ' + code + ';');

      // No syntax error! Does it work parenthesized?
      const wrappedCode = '(' + code + ')';
      parse(wrappedCode);

      return wrappedCode;
    } catch (e) {
      return code;
    }
  }

  /**
   * @param {string} text
   * @return {string}
   */
  static preprocessExpression(text) {
    return JavaScriptREPL.wrapObjectLiteral(text);
  }

  /**
   * @param {string} text
   * @param {boolean} throwOnSideEffect
   * @param {number=} timeout
   * @param {boolean=} allowErrors
   * @param {string=} objectGroup
   * @return {!Promise<!{preview: !DocumentFragment, result: ?SDK.RuntimeModel.EvaluationResult}>}
   */
  static async evaluateAndBuildPreview(text, throwOnSideEffect, timeout, allowErrors, objectGroup) {
    const executionContext = UI.context.flavor(SDK.ExecutionContext);
    const maxLength = typeof self.ObjectUI.JavaScriptREPL._MaxLengthForEvaluation !== 'undefined' ?
        self.ObjectUI.JavaScriptREPL._MaxLengthForEvaluation :
        MaxLengthForEvaluation;
    const isTextLong = text.length > maxLength;
    if (!text || !executionContext || (throwOnSideEffect && isTextLong)) {
      return {preview: createDocumentFragment(), result: null};
    }

    const expression = JavaScriptREPL.preprocessExpression(text);
    const options = {
      expression: expression,
      generatePreview: true,
      includeCommandLineAPI: true,
      throwOnSideEffect: throwOnSideEffect,
      timeout: timeout,
      objectGroup: objectGroup,
      disableBreaks: true
    };
    const result = await executionContext.evaluate(options, false /* userGesture */, false /* awaitPromise */);
    const preview = JavaScriptREPL._buildEvaluationPreview(result, allowErrors);
    return {preview, result};
  }

  /**
   * @param {!SDK.RuntimeModel.EvaluationResult} result
   * @param {boolean=} allowErrors
   * @return {!DocumentFragment}
   */
  static _buildEvaluationPreview(result, allowErrors) {
    const fragment = createDocumentFragment();
    if (result.error) {
      return fragment;
    }

    if (result.exceptionDetails && result.exceptionDetails.exception && result.exceptionDetails.exception.description) {
      const exception = result.exceptionDetails.exception.description;
      if (exception.startsWith('TypeError: ') || allowErrors) {
        fragment.createChild('span').textContent = result.exceptionDetails.text + ' ' + exception;
      }
      return fragment;
    }

    const formatter = new RemoteObjectPreviewFormatter();
    const {preview, type, subtype, description} = result.object;
    if (preview && type === 'object' && subtype !== 'node') {
      formatter.appendObjectPreview(fragment, preview, false /* isEntry */);
    } else {
      const nonObjectPreview = formatter.renderPropertyPreview(type, subtype, description.trimEndWithMaxLength(400));
      fragment.appendChild(nonObjectPreview);
    }
    return fragment;
  }
}

/**
 * @const
 * @type {number}
 */
export const MaxLengthForEvaluation = 2000;
<commit_msg>Eager console evaluation that builds the preview must use REPL mode<commit_after>// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import {RemoteObjectPreviewFormatter} from './RemoteObjectPreviewFormatter.js';

export class JavaScriptREPL {
  /**
   * @param {string} code
   * @return {string}
   */
  static wrapObjectLiteral(code) {
    // Only parenthesize what appears to be an object literal.
    if (!(/^\s*\{/.test(code) && /\}\s*$/.test(code))) {
      return code;
    }

    const parse = (async () => 0).constructor;
    try {
      // Check if the code can be interpreted as an expression.
      parse('return ' + code + ';');

      // No syntax error! Does it work parenthesized?
      const wrappedCode = '(' + code + ')';
      parse(wrappedCode);

      return wrappedCode;
    } catch (e) {
      return code;
    }
  }

  /**
   * @param {string} text
   * @return {string}
   */
  static preprocessExpression(text) {
    return JavaScriptREPL.wrapObjectLiteral(text);
  }

  /**
   * @param {string} text
   * @param {boolean} throwOnSideEffect
   * @param {number=} timeout
   * @param {boolean=} allowErrors
   * @param {string=} objectGroup
   * @return {!Promise<!{preview: !DocumentFragment, result: ?SDK.RuntimeModel.EvaluationResult}>}
   */
  static async evaluateAndBuildPreview(text, throwOnSideEffect, timeout, allowErrors, objectGroup) {
    const executionContext = UI.context.flavor(SDK.ExecutionContext);
    const maxLength = typeof self.ObjectUI.JavaScriptREPL._MaxLengthForEvaluation !== 'undefined' ?
        self.ObjectUI.JavaScriptREPL._MaxLengthForEvaluation :
        MaxLengthForEvaluation;
    const isTextLong = text.length > maxLength;
    if (!text || !executionContext || (throwOnSideEffect && isTextLong)) {
      return {preview: createDocumentFragment(), result: null};
    }

    const expression = JavaScriptREPL.preprocessExpression(text);
    const options = {
      expression: expression,
      generatePreview: true,
      includeCommandLineAPI: true,
      throwOnSideEffect: throwOnSideEffect,
      timeout: timeout,
      objectGroup: objectGroup,
      disableBreaks: true,
      replMode: true
    };
    const result = await executionContext.evaluate(options, false /* userGesture */, false /* awaitPromise */);
    const preview = JavaScriptREPL._buildEvaluationPreview(result, allowErrors);
    return {preview, result};
  }

  /**
   * @param {!SDK.RuntimeModel.EvaluationResult} result
   * @param {boolean=} allowErrors
   * @return {!DocumentFragment}
   */
  static _buildEvaluationPreview(result, allowErrors) {
    const fragment = createDocumentFragment();
    if (result.error) {
      return fragment;
    }

    if (result.exceptionDetails && result.exceptionDetails.exception && result.exceptionDetails.exception.description) {
      const exception = result.exceptionDetails.exception.description;
      if (exception.startsWith('TypeError: ') || allowErrors) {
        fragment.createChild('span').textContent = result.exceptionDetails.text + ' ' + exception;
      }
      return fragment;
    }

    const formatter = new RemoteObjectPreviewFormatter();
    const {preview, type, subtype, description} = result.object;
    if (preview && type === 'object' && subtype !== 'node') {
      formatter.appendObjectPreview(fragment, preview, false /* isEntry */);
    } else {
      const nonObjectPreview = formatter.renderPropertyPreview(type, subtype, description.trimEndWithMaxLength(400));
      fragment.appendChild(nonObjectPreview);
    }
    return fragment;
  }
}

/**
 * @const
 * @type {number}
 */
export const MaxLengthForEvaluation = 2000;
<|endoftext|>


<commit_before>import { COLORS } from './colors';
import { DISPLAY_MODES } from './displayModes';

/**
 * Cursor implements low-level API to terminal control codes.
 *
 * @see http://www.termsys.demon.co.uk/vtansi.htm
 * @see http://misc.flogisoft.com/bash/tip_colors_and_formatting
 * @see http://man7.org/linux/man-pages/man4/console_codes.4.html
 * @see http://www.x.org/docs/xterm/ctlseqs.pdf
 * @since 1.0.0
 */
export default class Cursor {
  /**
   * Creates cursor that writes direct to `stdout`.
   *
   * @constructor
   */
  constructor() {
    this._width = process.stdout.columns;
    this._height = process.stdout.rows;

    this._x = 0;
    this._y = 0;

    this._background = false;
    this._foreground = false;
    this._display = false;

    this._buffer = Array.from({length: this._width * this._height}).fill(' ');
    this._renderedBuffer = new Set(this._buffer);
  }

  /**
   * Write to the buffer.
   * Usually it's just a text that you want to print out but also can be a Buffer with control codes.
   * Cursor has a feature to buffering data, so when you will be ready to push to the stream, call {@link flush} method.
   *
   * @param {Buffer|String} data Data to write to the stream
   * @returns {Cursor}
   */
  write(data) {
    const background = this._background;
    const foreground = this._foreground;
    const display = this._display;

    data.split('').forEach(char => {
      const [x, y] = [this._x, this._y];
      const pointer = this.getPointerFromXY(x, y);

      if (0 <= x && x < this._width && 0 <= y && y < this._height) {
        this._buffer[pointer] = Cursor.wrap(char, {x, y, background, foreground, display});
      }

      this._x++;
    });

    return this;
  }

  /**
   * Write from the buffer to stream and clear it up.
   *
   * @returns {Cursor}
   */
  flush() {
    process.stdout.write(this._buffer.filter(item => !this._renderedBuffer.has(item)).join(''));
    this._renderedBuffer = new Set(this._buffer);

    return this;
  }

  /**
   * Get index of the buffer from (x, y) coordinates.
   *
   * @param {Number} [x] X coordinate on the terminal
   * @param {Number} [y] Y coordinate on the terminal
   * @returns {Number} Returns index in the buffer array
   */
  getPointerFromXY(x = this._x, y = this._y) {
    return y * this._width + x;
  }

  /**
   * Get (x, y) coordinate from the buffer pointer.
   *
   * @param {Number} index Index in the buffer
   * @returns {Array} Returns an array [x, y]
   */
  getXYFromPointer(index) {
    return [index - (Math.floor(index / this._width) * this._width), Math.floor(index / this._width)];
  }

  /**
   * Move the cursor up.
   *
   * @param {Number} [y=1]
   * @returns {Cursor}
   */
  up(y = 1) {
    this._y -= Math.floor(y);
    return this;
  }

  /**
   * Move the cursor down.
   *
   * @param {Number} [y=1]
   * @returns {Cursor}
   */
  down(y = 1) {
    this._y += Math.floor(y);
    return this;
  }

  /**
   * Move the cursor right.
   *
   * @param {Number} [x=1]
   * @returns {Cursor}
   */
  right(x = 1) {
    this._x += Math.floor(x);
    return this;
  }

  /**
   * Move the cursor left.
   *
   * @param {Number} [x=1]
   * @returns {Cursor}
   */
  left(x = 1) {
    this._x -= Math.floor(x);
    return this;
  }

  /**
   * Move the cursor position relative current coordinates.
   *
   * @param {Number} x Offset by X coordinate
   * @param {Number} y Offset by Y coordinate
   * @returns {Cursor}
   */
  moveBy(x, y) {
    if (x < 0) this.left(-x);
    if (x > 0) this.right(x);

    if (y < 0) this.up(-y);
    if (y > 0) this.down(y);

    return this;
  }

  /**
   * Set the cursor position by absolute coordinates.
   *
   * @param {Number} x X coordinate
   * @param {Number} y Y coordinate
   * @returns {Cursor}
   */
  moveTo(x, y) {
    this._x = Math.floor(x);
    this._y = Math.floor(y);

    return this;
  }

  /**
   * Set the foreground color.
   * This color is used when text is rendering.
   *
   * @param {String} color Color name
   * @returns {Cursor}
   */
  foreground(color) {
    this._foreground = color;
    return this;
  }

  /**
   * Set the background color.
   * This color is used for filling the whole cell in the TTY.
   *
   * @param {String} color Color name
   * @returns {Cursor}
   */
  background(color) {
    this._background = color;
    return this;
  }

  /**
   * Change display mode (format of text).
   * You can also use helper methods like {@link bold} or {@link blink}, etc...
   *
   * @param {Number} mode Mode identifier from {@link DISPLAY_MODES}
   * @returns {Cursor}
   */
  display(mode) {
    this._display = mode;
    return this;
  }

  /**
   * Toggle bold display mode.
   *
   * @param {Boolean} [isBold=true] If false, disables bold mode
   * @returns {Cursor}
   */
  bold(isBold = true) {
    return this.display(isBold ? DISPLAY_MODES.BOLD : DISPLAY_MODES.RESET_BOLD);
  }

  /**
   * Toggle dim display mode.
   *
   * @param {Boolean} [isDim=true] If false, disables dim mode
   * @returns {Cursor}
   */
  dim(isDim = true) {
    return this.display(isDim ? DISPLAY_MODES.DIM : DISPLAY_MODES.RESET_DIM);
  }

  /**
   * Toggle underlined display mode.
   *
   * @param {Boolean} [isUnderlined=true] If false, disables underlined mode
   * @returns {Cursor}
   */
  underlined(isUnderlined = true) {
    return this.display(isUnderlined ? DISPLAY_MODES.UNDERLINED : DISPLAY_MODES.RESET_UNDERLINED);
  }

  /**
   * Toggle blink display mode.
   *
   * @param {Boolean} [isBlink=true] If false, disables blink mode
   * @returns {Cursor}
   */
  blink(isBlink = true) {
    return this.display(isBlink ? DISPLAY_MODES.BLINK : DISPLAY_MODES.RESET_BLINK);
  }

  /**
   * Toggle reverse display mode.
   *
   * @param {Boolean} [isReverse=true] If false, disables reverse display mode
   * @returns {Cursor}
   */
  reverse(isReverse = true) {
    return this.display(isReverse ? DISPLAY_MODES.REVERSE : DISPLAY_MODES.RESET_REVERSE);
  }

  /**
   * Toggle hidden display mode.
   *
   * @param {Boolean} [isHidden=true] If false, disables hidden display mode
   * @returns {Cursor}
   */
  hidden(isHidden = true) {
    return this.display(isHidden ? DISPLAY_MODES.HIDDEN : DISPLAY_MODES.RESET_HIDDEN);
  }

  /**
   * Erase the specified region.
   * The region describes the rectangle shape which need to erase.
   *
   * @param {Number} x1
   * @param {Number} y1
   * @param {Number} x2
   * @param {Number} y2
   * @returns {Cursor}
   */
  erase(x1, y1, x2, y2) {
    for (let y = y1; y <= y2; y++) {
      for (let x = x1; x <= x2; x++) {
        this._buffer[this.getPointerFromXY(x, y)] = Cursor.wrap(' ', {x, y});
      }
    }

    return this;
  }

  /**
   * Erase from current position to end of the line.
   *
   * @returns {Cursor}
   */
  eraseToEnd() {
    return this.erase(this._x, this._y, this._width - 1, this._y);
  }

  /**
   * Erase from current position to start of the line.
   *
   * @returns {Cursor}
   */
  eraseToStart() {
    return this.erase(0, this._y, this._x, this._y);
  }

  /**
   * Erase from current line to down.
   *
   * @returns {Cursor}
   */
  eraseToDown() {
    return this.erase(0, this._y, this._width - 1, this._height - 1);
  }

  /**
   * Erase from current line to up.
   *
   * @returns {Cursor}
   */
  eraseToUp() {
    return this.erase(0, 0, this._width - 1, this._y);
  }

  /**
   * Erase current line.
   *
   * @returns {Cursor}
   */
  eraseLine() {
    return this.erase(0, this._y, this._width - 1, this._y);
  }

  /**
   * Erase the entire screen.
   *
   * @returns {Cursor}
   */
  eraseScreen() {
    return this.erase(0, 0, this._width - 1, this._height - 1);
  }

  /**
   * Set the terminal cursor invisible.
   * Applies immediately without calling {@link flush}.
   *
   * @returns {Cursor}
   */
  hideCursor() {
    process.stdout.write(Cursor.encodeToVT100('[?25l'));
    return this;
  }

  /**
   * Set the terminal cursor visible.
   * Applies immediately without calling {@link flush}.
   *
   * @returns {Cursor}
   */
  showCursor() {
    process.stdout.write(Cursor.encodeToVT100('[?25h'));
    return this;
  }

  /**
   * Reset all terminal settings.
   * Applies immediately without calling {@link flush}.
   *
   * @returns {Cursor}
   */
  resetTTY() {
    process.stdout.write(Cursor.encodeToVT100('c'));
    return this;
  }

  /**
   * Wrap char with all control codes needed for rendering the cell.
   *
   * @static
   * @param {String} char
   * @param {Object} options Options object where you can set additional style to char
   * @param {Number} options.x X coordinate
   * @param {Number} options.y Y coordinate
   * @param {String} [options.background] Background color
   * @param {String} [options.foreground] Foreground color
   * @param {Number} [options.display] Display mode from {@link DISPLAY_MODES}
   * @returns {String} Returns ready to flush string with ASCII control codes
   */
  static wrap(char, options) {
    const {x, y, background, foreground, display} = options;

    return (
      Cursor.encodeToVT100(`[${Math.floor(y + 1)};${Math.floor(x + 1)}f`) +
      (background ? Cursor.encodeToVT100(`[48;5;${COLORS[background.toUpperCase()]}m`) : '') +
      (foreground ? Cursor.encodeToVT100(`[38;5;${COLORS[foreground.toUpperCase()]}m`) : '') +
      (display ? Cursor.encodeToVT100(`[${display}m`) : '') +
      char +
      Cursor.encodeToVT100(`[${DISPLAY_MODES.RESET_ALL}m`)
    );
  }

  /**
   * Bytes to encode to VT100 control sequence.
   *
   * @static
   * @param {String} string Control code that you want to encode
   * @returns {Buffer} Returns encoded bytes
   */
  static encodeToVT100(string) {
    return new Buffer([0x1b].concat(string.split('').map(char => char.charCodeAt(0))));
  }

  /**
   * Wrapper around `new Cursor()`.
   *
   * @static
   * @returns {Cursor}
   */
  static create() {
    return new this();
  }
}
<commit_msg>docs(cursor): Update doc blocks<commit_after>import { COLORS } from './colors';
import { DISPLAY_MODES } from './displayModes';

/**
 * Cursor implements low-level API to terminal control codes.
 *
 * @see http://www.termsys.demon.co.uk/vtansi.htm
 * @see http://misc.flogisoft.com/bash/tip_colors_and_formatting
 * @see http://man7.org/linux/man-pages/man4/console_codes.4.html
 * @see http://www.x.org/docs/xterm/ctlseqs.pdf
 * @since 1.0.0
 */
export default class Cursor {
  /**
   * Creates cursor that writes direct to `stdout`.
   *
   * @constructor
   */
  constructor() {
    this._width = process.stdout.columns;
    this._height = process.stdout.rows;

    this._x = 0;
    this._y = 0;

    this._background = false;
    this._foreground = false;
    this._display = false;

    this._buffer = Array.from({length: this._width * this._height});
    this._renderedBuffer = new Set(this._buffer);
  }

  /**
   * Write to the buffer.
   * Build control sequence for each cell (char) in data and store into the buffer.
   * It allows to render each unique cell separately and build difference between two frames.
   *
   * @param {String} data Data to write to the buffer
   * @returns {Cursor}
   */
  write(data) {
    const background = this._background;
    const foreground = this._foreground;
    const display = this._display;

    data.split('').forEach(char => {
      const [x, y] = [this._x, this._y];
      const pointer = this.getPointerFromXY(x, y);

      if (0 <= x && x < this._width && 0 <= y && y < this._height) {
        this._buffer[pointer] = Cursor.wrap(char, {x, y, background, foreground, display});
      }

      this._x++;
    });

    return this;
  }

  /**
   * Take current buffer and rendered buffer at last flush.
   * Build difference between them.
   * Difference contains new control codes only, optimizing the rendering perfomance.
   *
   * @returns {Cursor}
   */
  flush() {
    process.stdout.write(this._buffer.filter(item => !this._renderedBuffer.has(item)).join(''));
    this._renderedBuffer = new Set(this._buffer);

    return this;
  }

  /**
   * Get index of the buffer from (x, y) coordinates.
   *
   * @param {Number} [x] X coordinate on the terminal
   * @param {Number} [y] Y coordinate on the terminal
   * @returns {Number} Returns index in the buffer array
   */
  getPointerFromXY(x = this._x, y = this._y) {
    return y * this._width + x;
  }

  /**
   * Get (x, y) coordinate from the buffer pointer.
   *
   * @param {Number} index Index in the buffer
   * @returns {Array} Returns an array [x, y]
   */
  getXYFromPointer(index) {
    return [index - (Math.floor(index / this._width) * this._width), Math.floor(index / this._width)];
  }

  /**
   * Move the cursor up.
   *
   * @param {Number} [y=1]
   * @returns {Cursor}
   */
  up(y = 1) {
    this._y -= Math.floor(y);
    return this;
  }

  /**
   * Move the cursor down.
   *
   * @param {Number} [y=1]
   * @returns {Cursor}
   */
  down(y = 1) {
    this._y += Math.floor(y);
    return this;
  }

  /**
   * Move the cursor right.
   *
   * @param {Number} [x=1]
   * @returns {Cursor}
   */
  right(x = 1) {
    this._x += Math.floor(x);
    return this;
  }

  /**
   * Move the cursor left.
   *
   * @param {Number} [x=1]
   * @returns {Cursor}
   */
  left(x = 1) {
    this._x -= Math.floor(x);
    return this;
  }

  /**
   * Move the cursor position relative current coordinates.
   *
   * @param {Number} x Offset by X coordinate
   * @param {Number} y Offset by Y coordinate
   * @returns {Cursor}
   */
  moveBy(x, y) {
    if (x < 0) this.left(-x);
    if (x > 0) this.right(x);

    if (y < 0) this.up(-y);
    if (y > 0) this.down(y);

    return this;
  }

  /**
   * Set the cursor position by absolute coordinates.
   *
   * @param {Number} x X coordinate
   * @param {Number} y Y coordinate
   * @returns {Cursor}
   */
  moveTo(x, y) {
    this._x = Math.floor(x);
    this._y = Math.floor(y);

    return this;
  }

  /**
   * Set the foreground color.
   * This color is used when text is rendering.
   *
   * @param {String} color Color name
   * @returns {Cursor}
   */
  foreground(color) {
    this._foreground = color;
    return this;
  }

  /**
   * Set the background color.
   * This color is used for filling the whole cell in the TTY.
   *
   * @param {String} color Color name
   * @returns {Cursor}
   */
  background(color) {
    this._background = color;
    return this;
  }

  /**
   * Change display mode (format of text).
   * You can also use helper methods like {@link bold} or {@link blink}, etc...
   *
   * @param {Number} mode Mode identifier from {@link DISPLAY_MODES}
   * @returns {Cursor}
   */
  display(mode) {
    this._display = mode;
    return this;
  }

  /**
   * Toggle bold display mode.
   *
   * @param {Boolean} [isBold=true] If false, disables bold mode
   * @returns {Cursor}
   */
  bold(isBold = true) {
    return this.display(isBold ? DISPLAY_MODES.BOLD : DISPLAY_MODES.RESET_BOLD);
  }

  /**
   * Toggle dim display mode.
   *
   * @param {Boolean} [isDim=true] If false, disables dim mode
   * @returns {Cursor}
   */
  dim(isDim = true) {
    return this.display(isDim ? DISPLAY_MODES.DIM : DISPLAY_MODES.RESET_DIM);
  }

  /**
   * Toggle underlined display mode.
   *
   * @param {Boolean} [isUnderlined=true] If false, disables underlined mode
   * @returns {Cursor}
   */
  underlined(isUnderlined = true) {
    return this.display(isUnderlined ? DISPLAY_MODES.UNDERLINED : DISPLAY_MODES.RESET_UNDERLINED);
  }

  /**
   * Toggle blink display mode.
   *
   * @param {Boolean} [isBlink=true] If false, disables blink mode
   * @returns {Cursor}
   */
  blink(isBlink = true) {
    return this.display(isBlink ? DISPLAY_MODES.BLINK : DISPLAY_MODES.RESET_BLINK);
  }

  /**
   * Toggle reverse display mode.
   *
   * @param {Boolean} [isReverse=true] If false, disables reverse display mode
   * @returns {Cursor}
   */
  reverse(isReverse = true) {
    return this.display(isReverse ? DISPLAY_MODES.REVERSE : DISPLAY_MODES.RESET_REVERSE);
  }

  /**
   * Toggle hidden display mode.
   *
   * @param {Boolean} [isHidden=true] If false, disables hidden display mode
   * @returns {Cursor}
   */
  hidden(isHidden = true) {
    return this.display(isHidden ? DISPLAY_MODES.HIDDEN : DISPLAY_MODES.RESET_HIDDEN);
  }

  /**
   * Erase the specified region.
   * The region describes the rectangle shape which need to erase.
   *
   * @param {Number} x1
   * @param {Number} y1
   * @param {Number} x2
   * @param {Number} y2
   * @returns {Cursor}
   */
  erase(x1, y1, x2, y2) {
    for (let y = y1; y <= y2; y++) {
      for (let x = x1; x <= x2; x++) {
        this._buffer[this.getPointerFromXY(x, y)] = Cursor.wrap(' ', {x, y});
      }
    }

    return this;
  }

  /**
   * Erase from current position to end of the line.
   *
   * @returns {Cursor}
   */
  eraseToEnd() {
    return this.erase(this._x, this._y, this._width - 1, this._y);
  }

  /**
   * Erase from current position to start of the line.
   *
   * @returns {Cursor}
   */
  eraseToStart() {
    return this.erase(0, this._y, this._x, this._y);
  }

  /**
   * Erase from current line to down.
   *
   * @returns {Cursor}
   */
  eraseToDown() {
    return this.erase(0, this._y, this._width - 1, this._height - 1);
  }

  /**
   * Erase from current line to up.
   *
   * @returns {Cursor}
   */
  eraseToUp() {
    return this.erase(0, 0, this._width - 1, this._y);
  }

  /**
   * Erase current line.
   *
   * @returns {Cursor}
   */
  eraseLine() {
    return this.erase(0, this._y, this._width - 1, this._y);
  }

  /**
   * Erase the entire screen.
   *
   * @returns {Cursor}
   */
  eraseScreen() {
    return this.erase(0, 0, this._width - 1, this._height - 1);
  }

  /**
   * Set the terminal cursor invisible.
   * Applies immediately without calling {@link flush}.
   *
   * @returns {Cursor}
   */
  hideCursor() {
    process.stdout.write(Cursor.encodeToVT100('[?25l'));
    return this;
  }

  /**
   * Set the terminal cursor visible.
   * Applies immediately without calling {@link flush}.
   *
   * @returns {Cursor}
   */
  showCursor() {
    process.stdout.write(Cursor.encodeToVT100('[?25h'));
    return this;
  }

  /**
   * Reset all terminal settings.
   * Applies immediately without calling {@link flush}.
   *
   * @returns {Cursor}
   */
  resetTTY() {
    process.stdout.write(Cursor.encodeToVT100('c'));
    return this;
  }

  /**
   * Wrap char with all control codes needed for rendering the cell.
   *
   * @static
   * @param {String} char Char that you want to wrap with control sequence
   * @param {Object} options Options object where you can set additional style to char
   * @param {Number} options.x X coordinate
   * @param {Number} options.y Y coordinate
   * @param {String} [options.background] Background color
   * @param {String} [options.foreground] Foreground color
   * @param {Number} [options.display] Display mode from {@link DISPLAY_MODES}
   * @returns {String} Returns ready to flush string with ASCII control codes
   */
  static wrap(char, options) {
    const {x, y, background, foreground, display} = options;

    return (
      Cursor.encodeToVT100(`[${Math.floor(y + 1)};${Math.floor(x + 1)}f`) +
      (background ? Cursor.encodeToVT100(`[48;5;${COLORS[background.toUpperCase()]}m`) : '') +
      (foreground ? Cursor.encodeToVT100(`[38;5;${COLORS[foreground.toUpperCase()]}m`) : '') +
      (display ? Cursor.encodeToVT100(`[${display}m`) : '') +
      char +
      Cursor.encodeToVT100(`[${DISPLAY_MODES.RESET_ALL}m`)
    );
  }

  /**
   * Bytes to encode to VT100 control sequence.
   *
   * @static
   * @param {String} string Control code that you want to encode
   * @returns {Buffer} Returns encoded bytes
   */
  static encodeToVT100(string) {
    return new Buffer([0x1b].concat(string.split('').map(char => char.charCodeAt(0))));
  }

  /**
   * Wrapper around `new Cursor()`.
   *
   * @static
   * @returns {Cursor}
   */
  static create() {
    return new this();
  }
}
<|endoftext|>


<commit_before>function WalkontableDom() {
}

//goes up the DOM tree (including given element) until it finds an element that matches the nodeName
WalkontableDom.prototype.closest = function (elem, nodeNames, until) {
  while (elem != null && elem !== until) {
    if (elem.nodeType === 1 && nodeNames.indexOf(elem.nodeName) > -1) {
      return elem;
    }
    elem = elem.parentNode;
  }
  return null;
};

//goes up the DOM tree and checks if element is child of another element
WalkontableDom.prototype.isChildOf = function (child, parent) {
  var node = child.parentNode;
  while (node != null) {
    if (node == parent) {
      return true;
    }
    node = node.parentNode;
  }
  return false;
};

WalkontableDom.prototype.prevSiblings = function (elem) {
  var out = [];
  while ((elem = elem.previousSibling) != null) {
    if (elem.nodeType === 1) {
      out.push(elem);
    }
  }
  return out;
};

if (document.documentElement.classList) {
  // HTML5 classList API
  WalkontableDom.prototype.hasClass = function (ele, cls) {
    return ele.classList.contains(cls);
  };

  WalkontableDom.prototype.addClass = function (ele, cls) {
    ele.classList.add(cls);
  };

  WalkontableDom.prototype.removeClass = function (ele, cls) {
    ele.classList.remove(cls);
  };
}
else {
  //http://snipplr.com/view/3561/addclass-removeclass-hasclass/
  WalkontableDom.prototype.hasClass = function (ele, cls) {
    return ele.className.match(new RegExp('(\\s|^)' + cls + '(\\s|$)'));
  };

  WalkontableDom.prototype.addClass = function (ele, cls) {
    if (!this.hasClass(ele, cls)) ele.className += " " + cls;
  };

  WalkontableDom.prototype.removeClass = function (ele, cls) {
    if (this.hasClass(ele, cls)) { //is this really needed?
      var reg = new RegExp('(\\s|^)' + cls + '(\\s|$)');
      ele.className = ele.className.replace(reg, ' ').replace(/^\s\s*/, '').replace(/\s\s*$/, ''); //last 2 replaces do right trim (see http://blog.stevenlevithan.com/archives/faster-trim-javascript)
    }
  };
}

/*//http://net.tutsplus.com/tutorials/javascript-ajax/javascript-from-null-cross-browser-event-binding/
 WalkontableDom.prototype.addEvent = (function () {
 var that = this;
 if (document.addEventListener) {
 return function (elem, type, cb) {
 if ((elem && !elem.length) || elem === window) {
 elem.addEventListener(type, cb, false);
 }
 else if (elem && elem.length) {
 var len = elem.length;
 for (var i = 0; i < len; i++) {
 that.addEvent(elem[i], type, cb);
 }
 }
 };
 }
 else {
 return function (elem, type, cb) {
 if ((elem && !elem.length) || elem === window) {
 elem.attachEvent('on' + type, function () {

 //normalize
 //http://stackoverflow.com/questions/4643249/cross-browser-event-object-normalization
 var e = window['event'];
 e.target = e.srcElement;
 //e.offsetX = e.layerX;
 //e.offsetY = e.layerY;
 e.relatedTarget = e.relatedTarget || e.type == 'mouseover' ? e.fromElement : e.toElement;
 if (e.target.nodeType === 3) e.target = e.target.parentNode; //Safari bug

 return cb.call(elem, e)
 });
 }
 else if (elem.length) {
 var len = elem.length;
 for (var i = 0; i < len; i++) {
 that.addEvent(elem[i], type, cb);
 }
 }
 };
 }
 })();

 WalkontableDom.prototype.triggerEvent = function (element, eventName, target) {
 var event;
 if (document.createEvent) {
 event = document.createEvent("MouseEvents");
 event.initEvent(eventName, true, true);
 } else {
 event = document.createEventObject();
 event.eventType = eventName;
 }

 event.eventName = eventName;
 event.target = target;

 if (document.createEvent) {
 target.dispatchEvent(event);
 } else {
 target.fireEvent("on" + event.eventType, event);
 }
 };*/

WalkontableDom.prototype.removeTextNodes = function (elem, parent) {
  if (elem.nodeType === 3) {
    parent.removeChild(elem); //bye text nodes!
  }
  else if (['TABLE', 'THEAD', 'TBODY', 'TFOOT', 'TR'].indexOf(elem.nodeName) > -1) {
    var childs = elem.childNodes;
    for (var i = childs.length - 1; i >= 0; i--) {
      this.removeTextNodes(childs[i], elem);
    }
  }
};

/**
 * Remove childs function
 * WARNING - this doesn't unload events and data attached by jQuery
 * http://jsperf.com/jquery-html-vs-empty-vs-innerhtml/9
 * @param element
 * @returns {void}
 */
//
WalkontableDom.prototype.empty = function (element) {
  var child;
  while (child = element.lastChild) {
    element.removeChild(child);
  }
};

WalkontableDom.prototype.HTML_CHARACTERS = /(<(.*)>|&(.*);)/g;

/**
 * Insert content into element trying avoid innerHTML method.
 * @return {void}
 */
WalkontableDom.prototype.fastInnerHTML = function (element, content) {
  if (this.HTML_CHARACTERS.test(content)) {
    element.innerHTML = content;
  }
  else {
    this.fastInnerText(element, content);
  }
};

/**
 * Insert text content into element
 * @return {void}
 */
if (document.createTextNode('test').textContent) { //STANDARDS
  WalkontableDom.prototype.fastInnerText = function (element, content) {
    var child = element.firstChild;
    if (child && child.nodeType === 3 && child.nextSibling === null) {
      //fast lane - replace existing text node
      //http://jsperf.com/replace-text-vs-reuse
      child.textContent = content;
    }
    else {
      //slow lane - empty element and insert a text node
      this.empty(element);
      element.appendChild(document.createTextNode(content));
    }
  };
}
else { //IE7-8
  WalkontableDom.prototype.fastInnerText = function (element, content) {
    var child = element.firstChild;
    if (child && child.nodeType === 3 && child.nextSibling === null) {
      //fast lane - replace existing text node
      //http://jsperf.com/replace-text-vs-reuse
      child.data = content;
    }
    else {
      //slow lane - empty element and insert a text node
      this.empty(element);
      element.appendChild(document.createTextNode(content));
    }
  };
}

/**
 * Returns true if element is attached to the DOM and visible, false otherwise
 * @param elem
 * @returns {boolean}
 */
WalkontableDom.prototype.isVisible = function (elem) {
  //fast method
  try {//try/catch performance is not a problem here: http://jsperf.com/try-catch-performance-overhead/7
    if (!elem.offsetParent) {
      return false; //fixes problem with UI Bootstrap <tabs> directive
    }
  }
  catch (e) {
    return false; //IE7-8 throws "Unspecified error" when offsetParent is not found - we catch it here
  }

//  if (elem.offsetWidth > 0 || (elem.parentNode && elem.parentNode.offsetWidth > 0)) { //IE10 was mistaken here
  if (elem.offsetWidth > 0) {
    return true;
  }

  //slow method
  var next = elem;
  while (next !== document.documentElement) { //until <html> reached
    if (next === null) { //parent detached from DOM
      return false;
    }
    else if (next.nodeType === 11) {
      if (next.nodeName === '#document-fragment') { //Shadow DOM
        return true;
      }
      else { //IE7 reports nodeType === 11 after detaching element from DOM
        return false;
      }
    }
    else if (next.style.display === 'none') {
      return false;
    }
    /*else if (next !== elem && next.offsetWidth === 0) {
     //this is the technique used by jQuery is(':visible')
     //but in IE7, clientWidth & offsetWidth sometimes returns 0 when it shouldn't
     return false;

     compare with jQuery :visible selector (search jquery.js for `reliableHiddenOffsets`)
     }*/
    next = next.parentNode;
  }
  return true;
};

/**
 * Returns elements top and left offset relative to the document. In our usage case compatible with jQuery but 2x faster
 * @param {HTMLElement} elem
 * @return {Object}
 */
WalkontableDom.prototype.offset = function (elem) {
  var offsetLeft = elem.offsetLeft
    , offsetTop = elem.offsetTop
    , lastElem = elem;

  while (elem = elem.offsetParent) {
    if (elem === document.body) { //from my observation, document.body always has scrollLeft/scrollTop == 0
      break;
    }
    offsetLeft += elem.offsetLeft;
    offsetTop += elem.offsetTop;
    lastElem = elem;
  }

  if (lastElem && lastElem.style.position === 'fixed') { //slow - http://jsperf.com/offset-vs-getboundingclientrect/6
    //if(lastElem !== document.body) { //faster but does gives false positive in Firefox
    offsetLeft += window.pageXOffset || document.documentElement.scrollLeft;
    offsetTop += window.pageYOffset || document.documentElement.scrollTop;
  }

  return {
    left: offsetLeft,
    top: offsetTop
  };
};

WalkontableDom.prototype.getComputedStyle = function (elem) {
  return elem.currentStyle || document.defaultView.getComputedStyle(elem);
};

WalkontableDom.prototype.outerWidth = function (elem) {
  return elem.offsetWidth;
};

WalkontableDom.prototype.outerHeight = function (elem) {
  return elem.offsetHeight;
};<commit_msg>get rid of a WebStorm warning ('if statement can be simplified')<commit_after>function WalkontableDom() {
}

//goes up the DOM tree (including given element) until it finds an element that matches the nodeName
WalkontableDom.prototype.closest = function (elem, nodeNames, until) {
  while (elem != null && elem !== until) {
    if (elem.nodeType === 1 && nodeNames.indexOf(elem.nodeName) > -1) {
      return elem;
    }
    elem = elem.parentNode;
  }
  return null;
};

//goes up the DOM tree and checks if element is child of another element
WalkontableDom.prototype.isChildOf = function (child, parent) {
  var node = child.parentNode;
  while (node != null) {
    if (node == parent) {
      return true;
    }
    node = node.parentNode;
  }
  return false;
};

WalkontableDom.prototype.prevSiblings = function (elem) {
  var out = [];
  while ((elem = elem.previousSibling) != null) {
    if (elem.nodeType === 1) {
      out.push(elem);
    }
  }
  return out;
};

if (document.documentElement.classList) {
  // HTML5 classList API
  WalkontableDom.prototype.hasClass = function (ele, cls) {
    return ele.classList.contains(cls);
  };

  WalkontableDom.prototype.addClass = function (ele, cls) {
    ele.classList.add(cls);
  };

  WalkontableDom.prototype.removeClass = function (ele, cls) {
    ele.classList.remove(cls);
  };
}
else {
  //http://snipplr.com/view/3561/addclass-removeclass-hasclass/
  WalkontableDom.prototype.hasClass = function (ele, cls) {
    return ele.className.match(new RegExp('(\\s|^)' + cls + '(\\s|$)'));
  };

  WalkontableDom.prototype.addClass = function (ele, cls) {
    if (!this.hasClass(ele, cls)) ele.className += " " + cls;
  };

  WalkontableDom.prototype.removeClass = function (ele, cls) {
    if (this.hasClass(ele, cls)) { //is this really needed?
      var reg = new RegExp('(\\s|^)' + cls + '(\\s|$)');
      ele.className = ele.className.replace(reg, ' ').replace(/^\s\s*/, '').replace(/\s\s*$/, ''); //last 2 replaces do right trim (see http://blog.stevenlevithan.com/archives/faster-trim-javascript)
    }
  };
}

/*//http://net.tutsplus.com/tutorials/javascript-ajax/javascript-from-null-cross-browser-event-binding/
 WalkontableDom.prototype.addEvent = (function () {
 var that = this;
 if (document.addEventListener) {
 return function (elem, type, cb) {
 if ((elem && !elem.length) || elem === window) {
 elem.addEventListener(type, cb, false);
 }
 else if (elem && elem.length) {
 var len = elem.length;
 for (var i = 0; i < len; i++) {
 that.addEvent(elem[i], type, cb);
 }
 }
 };
 }
 else {
 return function (elem, type, cb) {
 if ((elem && !elem.length) || elem === window) {
 elem.attachEvent('on' + type, function () {

 //normalize
 //http://stackoverflow.com/questions/4643249/cross-browser-event-object-normalization
 var e = window['event'];
 e.target = e.srcElement;
 //e.offsetX = e.layerX;
 //e.offsetY = e.layerY;
 e.relatedTarget = e.relatedTarget || e.type == 'mouseover' ? e.fromElement : e.toElement;
 if (e.target.nodeType === 3) e.target = e.target.parentNode; //Safari bug

 return cb.call(elem, e)
 });
 }
 else if (elem.length) {
 var len = elem.length;
 for (var i = 0; i < len; i++) {
 that.addEvent(elem[i], type, cb);
 }
 }
 };
 }
 })();

 WalkontableDom.prototype.triggerEvent = function (element, eventName, target) {
 var event;
 if (document.createEvent) {
 event = document.createEvent("MouseEvents");
 event.initEvent(eventName, true, true);
 } else {
 event = document.createEventObject();
 event.eventType = eventName;
 }

 event.eventName = eventName;
 event.target = target;

 if (document.createEvent) {
 target.dispatchEvent(event);
 } else {
 target.fireEvent("on" + event.eventType, event);
 }
 };*/

WalkontableDom.prototype.removeTextNodes = function (elem, parent) {
  if (elem.nodeType === 3) {
    parent.removeChild(elem); //bye text nodes!
  }
  else if (['TABLE', 'THEAD', 'TBODY', 'TFOOT', 'TR'].indexOf(elem.nodeName) > -1) {
    var childs = elem.childNodes;
    for (var i = childs.length - 1; i >= 0; i--) {
      this.removeTextNodes(childs[i], elem);
    }
  }
};

/**
 * Remove childs function
 * WARNING - this doesn't unload events and data attached by jQuery
 * http://jsperf.com/jquery-html-vs-empty-vs-innerhtml/9
 * @param element
 * @returns {void}
 */
//
WalkontableDom.prototype.empty = function (element) {
  var child;
  while (child = element.lastChild) {
    element.removeChild(child);
  }
};

WalkontableDom.prototype.HTML_CHARACTERS = /(<(.*)>|&(.*);)/g;

/**
 * Insert content into element trying avoid innerHTML method.
 * @return {void}
 */
WalkontableDom.prototype.fastInnerHTML = function (element, content) {
  if (this.HTML_CHARACTERS.test(content)) {
    element.innerHTML = content;
  }
  else {
    this.fastInnerText(element, content);
  }
};

/**
 * Insert text content into element
 * @return {void}
 */
if (document.createTextNode('test').textContent) { //STANDARDS
  WalkontableDom.prototype.fastInnerText = function (element, content) {
    var child = element.firstChild;
    if (child && child.nodeType === 3 && child.nextSibling === null) {
      //fast lane - replace existing text node
      //http://jsperf.com/replace-text-vs-reuse
      child.textContent = content;
    }
    else {
      //slow lane - empty element and insert a text node
      this.empty(element);
      element.appendChild(document.createTextNode(content));
    }
  };
}
else { //IE7-8
  WalkontableDom.prototype.fastInnerText = function (element, content) {
    var child = element.firstChild;
    if (child && child.nodeType === 3 && child.nextSibling === null) {
      //fast lane - replace existing text node
      //http://jsperf.com/replace-text-vs-reuse
      child.data = content;
    }
    else {
      //slow lane - empty element and insert a text node
      this.empty(element);
      element.appendChild(document.createTextNode(content));
    }
  };
}

/**
 * Returns true if element is attached to the DOM and visible, false otherwise
 * @param elem
 * @returns {boolean}
 */
WalkontableDom.prototype.isVisible = function (elem) {
  //fast method
  try {//try/catch performance is not a problem here: http://jsperf.com/try-catch-performance-overhead/7
    if (!elem.offsetParent) {
      return false; //fixes problem with UI Bootstrap <tabs> directive
    }
  }
  catch (e) {
    return false; //IE7-8 throws "Unspecified error" when offsetParent is not found - we catch it here
  }

//  if (elem.offsetWidth > 0 || (elem.parentNode && elem.parentNode.offsetWidth > 0)) { //IE10 was mistaken here
  if (elem.offsetWidth > 0) {
    return true;
  }

  //slow method
  var next = elem;
  while (next !== document.documentElement) { //until <html> reached
    if (next === null) { //parent detached from DOM
      return false;
    }
    else if (next.nodeType === 11) {
      if (next.nodeName === '#document-fragment') { //Shadow DOM
        return (true);
      }
      else { //IE7 reports nodeType === 11 after detaching element from DOM
        return (false);
      }
    }
    else if (next.style.display === 'none') {
      return false;
    }
    /*else if (next !== elem && next.offsetWidth === 0) {
     //this is the technique used by jQuery is(':visible')
     //but in IE7, clientWidth & offsetWidth sometimes returns 0 when it shouldn't
     return false;

     compare with jQuery :visible selector (search jquery.js for `reliableHiddenOffsets`)
     }*/
    next = next.parentNode;
  }
  return true;
};

/**
 * Returns elements top and left offset relative to the document. In our usage case compatible with jQuery but 2x faster
 * @param {HTMLElement} elem
 * @return {Object}
 */
WalkontableDom.prototype.offset = function (elem) {
  var offsetLeft = elem.offsetLeft
    , offsetTop = elem.offsetTop
    , lastElem = elem;

  while (elem = elem.offsetParent) {
    if (elem === document.body) { //from my observation, document.body always has scrollLeft/scrollTop == 0
      break;
    }
    offsetLeft += elem.offsetLeft;
    offsetTop += elem.offsetTop;
    lastElem = elem;
  }

  if (lastElem && lastElem.style.position === 'fixed') { //slow - http://jsperf.com/offset-vs-getboundingclientrect/6
    //if(lastElem !== document.body) { //faster but does gives false positive in Firefox
    offsetLeft += window.pageXOffset || document.documentElement.scrollLeft;
    offsetTop += window.pageYOffset || document.documentElement.scrollTop;
  }

  return {
    left: offsetLeft,
    top: offsetTop
  };
};

WalkontableDom.prototype.getComputedStyle = function (elem) {
  return elem.currentStyle || document.defaultView.getComputedStyle(elem);
};

WalkontableDom.prototype.outerWidth = function (elem) {
  return elem.offsetWidth;
};

WalkontableDom.prototype.outerHeight = function (elem) {
  return elem.offsetHeight;
};<|endoftext|>


<commit_before>
var redraw;

/* only do all this when document has finished loading (needed for RaphaelJS) */
window.onload = function() {

  var width = 600;
  var height = 400;

  var g = new Graph();

  /* add a simple node */
  g.addNode("strawberry");
  g.addNode("cherry");

  /* add a node with a customized label */
  g.addNode("1", { label : "Tomato" });

  /* add a node with a customized shape
     (the Raphael graph drawing implementation can draw this shape, please
     consult the RaphaelJS reference for details http://raphaeljs.com/) */
  var render = function(r, n) {
    var label = r.text(0, 30, n.label).attr({opacity:0});
    //the Raphael set is obligatory, containing all you want to display
    var set = r.set().push(
        r.rect(-30, -13, 62, 86)
          .attr({"fill": "#fa8",
                "stroke-width": 2
                , r : "9px"}))
      .push(label);
      // make the label show only on hover
      set.hover(
        function mouseIn() {
          label.animate({opacity:1,"fill-opacity":1}, 500);
        },
        function mouseOut() {
          label.animate({opacity:0},300);
        }
      );

      tooltip = r.set()
      .push(
        r.rect(0, 0, 90, 30).attr({"fill": "#fec", "stroke-width": 1, r : "9px"})
      ).push(
        r.text(25, 15, "overlay").attr({"fill": "#000000"})
      );
      for(i in set.items) {
        set.items[i].tooltip(tooltip);
      };
      //            set.tooltip(r.set().push(r.rect(0, 0, 30, 30).attr({"fill": "#fec", "stroke-width": 1, r : "9px"})).hide());
      return set;
  };

  g.addNode("id35", {
    label : "meat\nand\ngreed",
    /* filling the shape with a color makes it easier to be dragged */
    /* arguments: r = Raphael object, n : node object */
    render : render
  });
  //    g.addNode("Wheat", {
  /* filling the shape with a color makes it easier to be dragged */
  /* arguments: r = Raphael object, n : node object */
  //        shapes : [ {
  //                type: "rect",
  //                x: 10,
  //                y: 10,
  //                width: 25,
  //                height: 25,
  //                stroke: "#f00"
  //            }, {
  //                type: "text",
  //                x: 30,
  //                y: 40,
  //                text: "Dump"
  //            }],
  //        overlay : "Hello World!"
  //    });

  var st = { directed: true, label : "Label",
    "label-style" : {
      "font-size": 20
    }
  };
  g.addEdge("kiwi", "penguin", st);

  /* connect nodes with edges */
  g.addEdge("strawberry", "cherry", {directed: true});
  g.addEdge("cherry", "apple");
  g.addEdge("cherry", "apple")
  g.addEdge("1", "id35");
  g.addEdge("penguin", "id35");
  g.addEdge("penguin", "apple");
  g.addEdge("kiwi", "id35");

  /* a directed connection, using an arrow */
  g.addEdge("1", "cherry", { directed : true } );

  /* customize the colors of that edge */
  g.addEdge("id35", "apple", { stroke : "#bfa" , fill : "#56f", label : "Meat-to-Apple" });

  /* add an unknown node implicitly by adding an edge */
  g.addEdge("strawberry", "apple");

  g.removeNode("1");

  /* layout the graph using the Spring layout implementation */
  var layouter = new Graph.Layout.Spring(g);

  /* draw the graph using the RaphaelJS draw implementation */
  var renderer = new Graph.Renderer.Raphael('canvas', g, width, height);

  redraw = function() {
    layouter.layout();
    renderer.draw();
  };
  hide = function(id) {
    g.nodes[id].hide();
  };
  show = function(id) {
    g.nodes[id].show();
  };
  //    console.log(g.nodes["kiwi"]);
  redraw();
};

<commit_msg>remove node removed from example until it works<commit_after>
var redraw;

/* only do all this when document has finished loading (needed for RaphaelJS) */
window.onload = function() {

  var width = 600;
  var height = 400;

  var g = new Graph();

  /* add a simple node */
  g.addNode("strawberry");
  g.addNode("cherry");

  /* add a node with a customized label */
  g.addNode("1", { label : "Tomato" });

  /* add a node with a customized shape
     (the Raphael graph drawing implementation can draw this shape, please
     consult the RaphaelJS reference for details http://raphaeljs.com/) */
  var render = function(r, n) {
    var label = r.text(0, 30, n.label).attr({opacity:0});
    //the Raphael set is obligatory, containing all you want to display
    var set = r.set().push(
        r.rect(-30, -13, 62, 86)
          .attr({"fill": "#fa8",
                "stroke-width": 2
                , r : "9px"}))
      .push(label);
      // make the label show only on hover
      set.hover(
        function mouseIn() {
          label.animate({opacity:1,"fill-opacity":1}, 500);
        },
        function mouseOut() {
          label.animate({opacity:0},300);
        }
      );

      tooltip = r.set()
      .push(
        r.rect(0, 0, 90, 30).attr({"fill": "#fec", "stroke-width": 1, r : "9px"})
      ).push(
        r.text(25, 15, "overlay").attr({"fill": "#000000"})
      );
      for(i in set.items) {
        set.items[i].tooltip(tooltip);
      };
      //            set.tooltip(r.set().push(r.rect(0, 0, 30, 30).attr({"fill": "#fec", "stroke-width": 1, r : "9px"})).hide());
      return set;
  };

  g.addNode("id35", {
    label : "meat\nand\ngreed",
    /* filling the shape with a color makes it easier to be dragged */
    /* arguments: r = Raphael object, n : node object */
    render : render
  });
  //    g.addNode("Wheat", {
  /* filling the shape with a color makes it easier to be dragged */
  /* arguments: r = Raphael object, n : node object */
  //        shapes : [ {
  //                type: "rect",
  //                x: 10,
  //                y: 10,
  //                width: 25,
  //                height: 25,
  //                stroke: "#f00"
  //            }, {
  //                type: "text",
  //                x: 30,
  //                y: 40,
  //                text: "Dump"
  //            }],
  //        overlay : "Hello World!"
  //    });

  var st = { directed: true, label : "Label",
    "label-style" : {
      "font-size": 20
    }
  };
  g.addEdge("kiwi", "penguin", st);

  /* connect nodes with edges */
  g.addEdge("strawberry", "cherry", {directed: true});
  g.addEdge("cherry", "apple");
  g.addEdge("cherry", "apple")
  g.addEdge("1", "id35");
  g.addEdge("penguin", "id35");
  g.addEdge("penguin", "apple");
  g.addEdge("kiwi", "id35");

  /* a directed connection, using an arrow */
  g.addEdge("1", "cherry", { directed : true } );

  /* customize the colors of that edge */
  g.addEdge("id35", "apple", { stroke : "#bfa" , fill : "#56f", label : "Meat-to-Apple" });

  /* add an unknown node implicitly by adding an edge */
  g.addEdge("strawberry", "apple");

  //g.removeNode("1");

  /* layout the graph using the Spring layout implementation */
  var layouter = new Graph.Layout.Spring(g);

  /* draw the graph using the RaphaelJS draw implementation */
  var renderer = new Graph.Renderer.Raphael('canvas', g, width, height);

  redraw = function() {
    layouter.layout();
    renderer.draw();
  };
  hide = function(id) {
    g.nodes[id].hide();
  };
  show = function(id) {
    g.nodes[id].show();
  };
  //    console.log(g.nodes["kiwi"]);
  redraw();
};

<|endoftext|>


<commit_before>import React from 'react';
import { View } from 'react-native';
import PropTypes from 'prop-types';
import { branch, renderComponent, compose, setPropTypes } from 'recompose';
import get from 'lodash/get';
import { withFormik } from 'formik';
import Yup from 'yup';

import withGive from '@data/withGive';
import withCheckout from '@data/withCheckout';
import withUser from '@data/withUser';
import { withRouter } from '@ui/NativeWebRouter';
import PaddedView from '@ui/PaddedView';
import TableView, { FormFields } from '@ui/TableView';
import ActivityIndicator from '@ui/ActivityIndicator';
import sentry from '@utils/sentry';
import * as Inputs from '@ui/inputs';
import Button from '@ui/Button';

const enhance = compose(
  setPropTypes({
    isLoading: PropTypes.bool,
    onSubmit: PropTypes.func,
    setFieldValue: PropTypes.func,
    countries: PropTypes.arrayOf(
      PropTypes.shape({
        id: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
        label: PropTypes.string,
      }),
    ),
    states: PropTypes.arrayOf(
      PropTypes.shape({
        id: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
        label: PropTypes.string,
      }),
    ),
    values: PropTypes.shape({
      countryId: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
      stateId: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
      street1: PropTypes.string,
      street2: PropTypes.string,
      city: PropTypes.string,
      zipCode: PropTypes.string,
    }),
    touched: PropTypes.shape({
      countryId: PropTypes.bool,
      stateId: PropTypes.bool,
      street1: PropTypes.bool,
      street2: PropTypes.bool,
      city: PropTypes.bool,
      zipCode: PropTypes.bool,
    }),
    errors: PropTypes.shape({
      countryId: PropTypes.string,
      stateId: PropTypes.string,
      street1: PropTypes.string,
      street2: PropTypes.string,
      city: PropTypes.string,
      zipCode: PropTypes.string,
    }),
    isSubmitting: PropTypes.bool,
    isValid: PropTypes.bool,
  }),
  branch(({ isLoading }) => isLoading, renderComponent(ActivityIndicator)),
);

export const BillingAddressFormWithoutData = enhance(
  ({
    setFieldValue,
    handleSubmit,
    values,
    countries,
    states,
    setFieldTouched,
    touched,
    errors,
    isSubmitting,
    isValid,
  }) => {
    const isUSOrCanada = values.countryId === 'US' || values.countryId === 'CA';

    return (
      <View>
        <TableView responsive={false}>
          <FormFields>
            <Inputs.Text
              label="Street Address"
              value={values.street1}
              onChangeText={text => setFieldValue('street1', text)}
              onBlur={() => setFieldTouched('street1', true)}
              error={touched.street1 && errors.street1}
            />
            <Inputs.Text
              label="Street Address (optional)"
              value={values.street2}
              onChangeText={text => setFieldValue('street2', text)}
              onBlur={() => setFieldTouched('street2', true)}
              error={touched.street2 && errors.street2}
            />
            <Inputs.Picker
              label="Country"
              value={values.countryId}
              displayValue={get(
                countries.find(country => country.id === values.countryId),
                'label',
              )}
              onValueChange={value => setFieldValue('countryId', value)}
              error={touched.countryId && errors.countryId}
            >
              {countries.map(({ label, id }) => (
                <Inputs.PickerItem label={label} value={id} key={id} />
              ))}
            </Inputs.Picker>
            <Inputs.Text
              label="City"
              value={values.city}
              onChangeText={text => setFieldValue('city', text)}
              onBlur={() => setFieldTouched('city', true)}
              error={touched.city && errors.city}
            />
            {isUSOrCanada && (
              <Inputs.Picker
                label="State/Territory"
                value={values.stateId}
                displayValue={get(states.find(state => state.id === values.stateId), 'label')}
                onValueChange={value => setFieldValue('stateId', value)}
                error={touched.stateId && errors.stateId}
              >
                {states.map(({ label, id }) => (
                  <Inputs.PickerItem label={label} value={id} key={id} />
                ))}
              </Inputs.Picker>
            )}
            <Inputs.Text
              label="Zip/Postal"
              type="numeric"
              value={values.zipCode}
              onChangeText={text => setFieldValue('zipCode', text)}
              onBlur={() => setFieldTouched('zipCode', true)}
              error={touched.zipCode && errors.zipCode}
            />
          </FormFields>
        </TableView>
        <PaddedView>
          <Button onPress={handleSubmit} title="Next" disabled={!isValid} loading={isSubmitting} />
        </PaddedView>
      </View>
    );
  },
);

const validationSchema = Yup.object().shape({
  street1: Yup.string().required('Street address is a required field'),
  street2: Yup.string().nullable(),
  city: Yup.string().required('City is a required field'),
  stateId: Yup.string(),
  countryId: Yup.string().required('Country is a required field'),
  zipCode: Yup.string().required('Zip Code is a required field'),
});

const mapPropsToValues = props => ({
  street1: get(props, 'user.home.street1', ''),
  street2: get(props, 'user.home.street2', ''),
  city: get(props, 'user.home.city', ''),
  stateId: get(props, 'user.home.state') || 'SC',
  countryId: get(props, 'user.home.country') || 'US',
  zipCode: get(props, 'user.home.zip', ''),
});

const BillingAddressForm = compose(
  withGive,
  withCheckout,
  withUser,
  withRouter,
  withFormik({
    mapPropsToValues,
    validationSchema,
    isInitialValid(props) {
      return validationSchema.isValidSync(mapPropsToValues(props));
    },
    handleSubmit: async (formValues, { props, setSubmitting }) => {
      try {
        setSubmitting(true);
        props.setBillingAddress(formValues);
        setSubmitting(false);
        if (props.navigateToOnComplete) props.history.push(props.navigateToOnComplete);
      } catch (e) {
        // TODO: If there's an error, we want to stay on this page and display it.
        sentry.captureException(e);
      }
    },
  }),
)(BillingAddressFormWithoutData);

export default BillingAddressForm;
<commit_msg>removed withUser from BillingAddressForm<commit_after>import React from 'react';
import { View } from 'react-native';
import PropTypes from 'prop-types';
import { branch, renderComponent, compose, setPropTypes } from 'recompose';
import get from 'lodash/get';
import { withFormik } from 'formik';
import Yup from 'yup';

import withGive from '@data/withGive';
import withCheckout from '@data/withCheckout';
import { withRouter } from '@ui/NativeWebRouter';
import PaddedView from '@ui/PaddedView';
import TableView, { FormFields } from '@ui/TableView';
import ActivityIndicator from '@ui/ActivityIndicator';
import sentry from '@utils/sentry';
import * as Inputs from '@ui/inputs';
import Button from '@ui/Button';

const enhance = compose(
  setPropTypes({
    isLoading: PropTypes.bool,
    onSubmit: PropTypes.func,
    setFieldValue: PropTypes.func,
    countries: PropTypes.arrayOf(
      PropTypes.shape({
        id: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
        label: PropTypes.string,
      }),
    ),
    states: PropTypes.arrayOf(
      PropTypes.shape({
        id: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
        label: PropTypes.string,
      }),
    ),
    values: PropTypes.shape({
      countryId: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
      stateId: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
      street1: PropTypes.string,
      street2: PropTypes.string,
      city: PropTypes.string,
      zipCode: PropTypes.string,
    }),
    touched: PropTypes.shape({
      countryId: PropTypes.bool,
      stateId: PropTypes.bool,
      street1: PropTypes.bool,
      street2: PropTypes.bool,
      city: PropTypes.bool,
      zipCode: PropTypes.bool,
    }),
    errors: PropTypes.shape({
      countryId: PropTypes.string,
      stateId: PropTypes.string,
      street1: PropTypes.string,
      street2: PropTypes.string,
      city: PropTypes.string,
      zipCode: PropTypes.string,
    }),
    isSubmitting: PropTypes.bool,
    isValid: PropTypes.bool,
  }),
  branch(({ isLoading }) => isLoading, renderComponent(ActivityIndicator)),
);

export const BillingAddressFormWithoutData = enhance(
  ({
    setFieldValue,
    handleSubmit,
    values,
    countries,
    states,
    setFieldTouched,
    touched,
    errors,
    isSubmitting,
    isValid,
  }) => {
    const isUSOrCanada = values.countryId === 'US' || values.countryId === 'CA';

    return (
      <View>
        <TableView responsive={false}>
          <FormFields>
            <Inputs.Text
              label="Street Address"
              value={values.street1}
              onChangeText={text => setFieldValue('street1', text)}
              onBlur={() => setFieldTouched('street1', true)}
              error={touched.street1 && errors.street1}
            />
            <Inputs.Text
              label="Street Address (optional)"
              value={values.street2}
              onChangeText={text => setFieldValue('street2', text)}
              onBlur={() => setFieldTouched('street2', true)}
              error={touched.street2 && errors.street2}
            />
            <Inputs.Picker
              label="Country"
              value={values.countryId}
              displayValue={get(
                countries.find(country => country.id === values.countryId),
                'label',
              )}
              onValueChange={value => setFieldValue('countryId', value)}
              error={touched.countryId && errors.countryId}
            >
              {countries.map(({ label, id }) => (
                <Inputs.PickerItem label={label} value={id} key={id} />
              ))}
            </Inputs.Picker>
            <Inputs.Text
              label="City"
              value={values.city}
              onChangeText={text => setFieldValue('city', text)}
              onBlur={() => setFieldTouched('city', true)}
              error={touched.city && errors.city}
            />
            {isUSOrCanada && (
              <Inputs.Picker
                label="State/Territory"
                value={values.stateId}
                displayValue={get(states.find(state => state.id === values.stateId), 'label')}
                onValueChange={value => setFieldValue('stateId', value)}
                error={touched.stateId && errors.stateId}
              >
                {states.map(({ label, id }) => (
                  <Inputs.PickerItem label={label} value={id} key={id} />
                ))}
              </Inputs.Picker>
            )}
            <Inputs.Text
              label="Zip/Postal"
              type="numeric"
              value={values.zipCode}
              onChangeText={text => setFieldValue('zipCode', text)}
              onBlur={() => setFieldTouched('zipCode', true)}
              error={touched.zipCode && errors.zipCode}
            />
          </FormFields>
        </TableView>
        <PaddedView>
          <Button onPress={handleSubmit} title="Next" disabled={!isValid} loading={isSubmitting} />
        </PaddedView>
      </View>
    );
  },
);

const validationSchema = Yup.object().shape({
  street1: Yup.string().required('Street address is a required field'),
  street2: Yup.string().nullable(),
  city: Yup.string().required('City is a required field'),
  stateId: Yup.string(),
  countryId: Yup.string().required('Country is a required field'),
  zipCode: Yup.string().required('Zip Code is a required field'),
});

const mapPropsToValues = props => ({
  street1: get(props, 'contributions.street1') || get(props, 'person.home.street1', ''),
  street2: get(props, 'contributions.street2') || get(props, 'person.home.street2', ''),
  city: get(props, 'contributions.city') || get(props, 'person.home.city', ''),
  stateId: get(props, 'contributions.stateId') || get(props, 'person.home.stateId') || 'SC',
  countryId: get(props, 'contributions.countryId') || get(props, 'person.home.countryId') || 'US',
  zipCode: get(props, 'contributions.zipCode') || get(props, 'person.home.zip', ''),
});

const BillingAddressForm = compose(
  withGive,
  withCheckout,
  withRouter,
  withFormik({
    mapPropsToValues,
    validationSchema,
    isInitialValid(props) {
      return validationSchema.isValidSync(mapPropsToValues(props));
    },
    handleSubmit: async (formValues, { props, setSubmitting }) => {
      try {
        setSubmitting(true);
        props.setBillingAddress(formValues);
        setSubmitting(false);
        if (props.navigateToOnComplete) props.history.push(props.navigateToOnComplete);
      } catch (e) {
        // TODO: If there's an error, we want to stay on this page and display it.
        sentry.captureException(e);
      }
    },
  }),
)(BillingAddressFormWithoutData);

export default BillingAddressForm;
<|endoftext|>


<commit_before>/*!
 * jQuery Smart Banner
 * Copyright (c) 2012 Arnold Daniels <arnold@jasny.net>
 * Based on 'jQuery Smart Web App Banner' by Kurt Zenisek @ kzeni.com
 */
!function ($) {
    var SmartBanner = function (options) {
        this.origHtmlMargin = parseFloat($('html').css('margin-top')) // Get the original margin-top of the HTML element so we can take that into account
        this.options = $.extend({}, $.smartbanner.defaults, options)

        var standalone = navigator.standalone // Check if it's already a standalone web app or running within a webui view of an app (not mobile safari)
          , UA = navigator.userAgent

        // Detect banner type (iOS or Android)
        if (this.options.force) {
            this.type = this.options.force
        } else if (UA.match(/iPad|iPhone|iPod/i) != null) {
            if (UA.match(/Safari/i) != null &&
               (UA.match(/CriOS/i) != null ||
               window.Number(navigator.userAgent.substr(navigator.userAgent.indexOf('OS ') + 3, 3).replace('_', '.')) < 6)) this.type = 'ios' // Check webview and native smart banner support (iOS 6+)
        } else if (UA.match(/Android/i) != null) {
            this.type = 'android'
        } else if (UA.match(/Windows NT 6.2/i) != null && UA.match(/Touch/i) !== null) {
            this.type = 'windows'
        }

        // Don't show banner if device isn't iOS or Android, website is loaded in app or user dismissed banner
        if (!this.type || standalone || this.getCookie('sb-closed') || this.getCookie('sb-installed')) {
            return
        }

        // Calculate scale
        this.scale = this.options.scale == 'auto' ? $(window).width() / window.screen.width : this.options.scale
        if (this.scale < 1) this.scale = 1

        // Get info from meta data
        var meta = $(this.type == 'android' ? 'meta[name="google-play-app"]' :
            this.type == 'ios' ? 'meta[name="apple-itunes-app"]' : 'meta[name="msApplication-ID"]');
        if (meta.length == 0) return

        // For Windows Store apps, get the PackageFamilyName for protocol launch
        if (this.type == 'windows') {
            this.pfn = $('meta[name="msApplication-PackageFamilyName"]').attr('content');
            this.appId = meta.attr('content')[1]
        } else {
            this.appId = /app-id=([^\s,]+)/.exec(meta.attr('content'))[1]
        }

        this.title = this.options.title ? this.options.title : $('title').text().replace(/\s*[|\-·].*$/, '')
        this.author = this.options.author ? this.options.author : ($('meta[name="author"]').length ? $('meta[name="author"]').attr('content') : window.location.hostname)

        // Create banner
        this.create()
        this.show()
        this.listen()
    }

    SmartBanner.prototype = {

        constructor: SmartBanner

      , create: function() {
            var iconURL
              , link=(this.options.url ? this.options.url : (this.type == 'windows' ? 'ms-windows-store:PDP?PFN=' + this.pfn : (this.type == 'android' ? 'market://details?id=' : 'https://itunes.apple.com/' + this.options.appStoreLanguage + '/app/id')) + this.appId)
              , inStore=this.options.price ? this.options.price + ' - ' + (this.type == 'android' ? this.options.inGooglePlay : this.type == 'ios' ? this.options.inAppStore : this.options.inWindowsStore) : ''
              , gloss=this.options.iconGloss === null ? (this.type=='ios') : this.options.iconGloss

            $('body').append('

×
'+this.title+''+this.author+''+inStore+'
'+this.options.button+'

')

            if (this.options.icon) {
                iconURL = this.options.icon
            } else if ($('link[rel="apple-touch-icon-precomposed"]').length > 0) {
                iconURL = $('link[rel="apple-touch-icon-precomposed"]').attr('href')
                if (this.options.iconGloss === null) gloss = false
            } else if ($('link[rel="apple-touch-icon"]').length > 0) {
                iconURL = $('link[rel="apple-touch-icon"]').attr('href')
            } else if ($('meta[name="msApplication-TileImage"]').length > 0) {
              iconURL = $('meta[name="msApplication-TileImage"]').attr('content')
            } else if ($('meta[name="msapplication-TileImage"]').length > 0) { /* redundant because ms docs show two case usages */
              iconURL = $('meta[name="msapplication-TileImage"]').attr('content')
            }

            if (iconURL) {
                $('#smartbanner .sb-icon').css('background-image','url('+iconURL+')')
                if (gloss) $('#smartbanner .sb-icon').addClass('gloss')
            } else{
                $('#smartbanner').addClass('no-icon')
            }

            this.bannerHeight = $('#smartbanner').outerHeight() + 2

            if (this.scale > 1) {
                $('#smartbanner')
                    .css('top', parseFloat($('#smartbanner').css('top')) * this.scale)
                    .css('height', parseFloat($('#smartbanner').css('height')) * this.scale)
                $('#smartbanner .sb-container')
                    .css('-webkit-transform', 'scale('+this.scale+')')
                    .css('-msie-transform', 'scale('+this.scale+')')
                    .css('-moz-transform', 'scale('+this.scale+')')
                    .css('width', $(window).width() / this.scale)
            }
        }

      , listen: function () {
            $('#smartbanner .sb-close').on('click',$.proxy(this.close, this))
            $('#smartbanner .sb-button').on('click',$.proxy(this.install, this))
        }

      , show: function(callback) {
            $('#smartbanner').stop().animate({top:0},this.options.speedIn).addClass('shown')
            $('html').animate({marginTop:this.origHtmlMargin+(this.bannerHeight*this.scale)},this.options.speedIn,'swing',callback)
        }

      , hide: function(callback) {
            $('#smartbanner').stop().animate({top:-1*this.bannerHeight*this.scale},this.options.speedOut).removeClass('shown')
            $('html').animate({marginTop:this.origHtmlMargin},this.options.speedOut,'swing',callback)
        }

      , close: function(e) {
            e.preventDefault()
            this.hide()
            this.setCookie('sb-closed','true',this.options.daysHidden)
        }

      , install: function(e) {
            this.hide()
            this.setCookie('sb-installed','true',this.options.daysReminder)
        }

      , setCookie: function(name, value, exdays) {
            var exdate = new Date()
            exdate.setDate(exdate.getDate()+exdays)
            value=escape(value)+((exdays==null)?'':'; expires='+exdate.toUTCString())
            document.cookie=name+'='+value+'; path=/;'
        }

      , getCookie: function(name) {
            var i,x,y,ARRcookies = document.cookie.split(";")
            for(i=0;i<ARRcookies.length;i++) {
                x = ARRcookies[i].substr(0,ARRcookies[i].indexOf("="))
                y = ARRcookies[i].substr(ARRcookies[i].indexOf("=")+1)
                x = x.replace(/^\s+|\s+$/g,"")
                if (x==name) {
                    return unescape(y)
                }
            }
            return null
        }

      // Demo only
      , switchType: function() {
          var that = this

          this.hide(function () {
              that.type = that.type == 'android' ? 'ios' : 'android'
              var meta = $(that.type == 'android' ? 'meta[name="google-play-app"]' : 'meta[name="apple-itunes-app"]').attr('content')
              that.appId = /app-id=([^\s,]+)/.exec(meta)[1]

              $('#smartbanner').detach()
              that.create()
              that.show()
          })
      }
    }

    $.smartbanner = function (option) {
        var $window = $(window)
        , data = $window.data('typeahead')
        , options = typeof option == 'object' && option
        if (!data) $window.data('typeahead', (data = new SmartBanner(options)))
        if (typeof option == 'string') data[option]()
    }

    // override these globally if you like (they are all optional)
    $.smartbanner.defaults = {
        title: null, // What the title of the app should be in the banner (defaults to <title>)
        author: null, // What the author of the app should be in the banner (defaults to <meta name="author"> or hostname)
        price: 'FREE', // Price of the app
        appStoreLanguage: 'us', // Language code for App Store
        inAppStore: 'On the App Store', // Text of price for iOS
        inGooglePlay: 'In Google Play', // Text of price for Android
        inWindowsStore: 'In the Windows Store', //Text of price for Windows
        icon: null, // The URL of the icon (defaults to <meta name="apple-touch-icon">)
        iconGloss: null, // Force gloss effect for iOS even for precomposed
        button: 'VIEW', // Text for the install button
        url: null, // The URL for the button. Keep null if you want the button to link to the app store.
        scale: 'auto', // Scale based on viewport size (set to 1 to disable)
        speedIn: 300, // Show animation speed of the banner
        speedOut: 400, // Close animation speed of the banner
        daysHidden: 15, // Duration to hide the banner after being closed (0 = always show banner)
        daysReminder: 90, // Duration to hide the banner after "VIEW" is clicked *separate from when the close button is clicked* (0 = always show banner)
        force: null // Choose 'ios', 'android' or 'windows'. Don't do a browser check, just always show this banner
    }

    $.smartbanner.Constructor = SmartBanner

}(window.jQuery);
<commit_msg>link to Amazon Appstore on Kindle Fire<commit_after>/*!
 * jQuery Smart Banner
 * Copyright (c) 2012 Arnold Daniels <arnold@jasny.net>
 * Based on 'jQuery Smart Web App Banner' by Kurt Zenisek @ kzeni.com
 */
!function ($) {
    var SmartBanner = function (options) {
        this.origHtmlMargin = parseFloat($('html').css('margin-top')) // Get the original margin-top of the HTML element so we can take that into account
        this.options = $.extend({}, $.smartbanner.defaults, options)

        var standalone = navigator.standalone // Check if it's already a standalone web app or running within a webui view of an app (not mobile safari)
          , UA = navigator.userAgent

        // Detect banner type (iOS or Android)
        if (this.options.force) {
            this.type = this.options.force
        } else if (UA.match(/iPad|iPhone|iPod/i) != null) {
            if (UA.match(/Safari/i) != null &&
               (UA.match(/CriOS/i) != null ||
               window.Number(navigator.userAgent.substr(navigator.userAgent.indexOf('OS ') + 3, 3).replace('_', '.')) < 6)) this.type = 'ios' // Check webview and native smart banner support (iOS 6+)
        } else if (UA.match(/\bSilk\/(.*\bMobile Safari\b)?/) || UA.match(/\bKF\w/) || UA.match('Kindle Fire')) {
            this.type = 'kindle'
        } else if (UA.match(/Android/i) != null) {
            this.type = 'android'
        } else if (UA.match(/Windows NT 6.2/i) != null && UA.match(/Touch/i) !== null) {
            this.type = 'windows'
        }

        // Don't show banner if device isn't iOS or Android, website is loaded in app or user dismissed banner
        if (!this.type || standalone || this.getCookie('sb-closed') || this.getCookie('sb-installed')) {
            return
        }

        // Calculate scale
        this.scale = this.options.scale == 'auto' ? $(window).width() / window.screen.width : this.options.scale
        if (this.scale < 1) this.scale = 1

        // Get info from meta data
        var meta = $(this.type == 'android' ? 'meta[name="google-play-app"]' :
            this.type == 'ios' ? 'meta[name="apple-itunes-app"]' :
            this.type == 'kindle' ? 'meta[name="kindle-fire-app"]' : 'meta[name="msApplication-ID"]');
        if (meta.length == 0) return

        // For Windows Store apps, get the PackageFamilyName for protocol launch
        if (this.type == 'windows') {
            this.pfn = $('meta[name="msApplication-PackageFamilyName"]').attr('content');
            this.appId = meta.attr('content')[1]
        } else {
            this.appId = /app-id=([^\s,]+)/.exec(meta.attr('content'))[1]
        }

        this.title = this.options.title ? this.options.title : $('title').text().replace(/\s*[|\-·].*$/, '')
        this.author = this.options.author ? this.options.author : ($('meta[name="author"]').length ? $('meta[name="author"]').attr('content') : window.location.hostname)

        // Create banner
        this.create()
        this.show()
        this.listen()
    }

    SmartBanner.prototype = {

        constructor: SmartBanner

      , create: function() {
            var iconURL
              , link=(this.options.url ? this.options.url : (this.type == 'windows' ? 'ms-windows-store:PDP?PFN=' + this.pfn : (this.type == 'android' ? 'market://details?id=' : (this.type == 'kindle' ? 'amzn://apps/android?asin=' : 'https://itunes.apple.com/' + this.options.appStoreLanguage + '/app/id'))) + this.appId)
              , inStore=this.options.price ? this.options.price + ' - ' + (this.type == 'android' ? this.options.inGooglePlay : this.type == 'kindle' ? this.options.inAmazonAppStore : this.type == 'ios' ? this.options.inAppStore : this.options.inWindowsStore) : ''
              , gloss=this.options.iconGloss === null ? (this.type=='ios') : this.options.iconGloss

            $('body').append('

×
'+this.title+''+this.author+''+inStore+'
'+this.options.button+'

')

            if (this.options.icon) {
                iconURL = this.options.icon
            } else if ($('link[rel="apple-touch-icon-precomposed"]').length > 0) {
                iconURL = $('link[rel="apple-touch-icon-precomposed"]').attr('href')
                if (this.options.iconGloss === null) gloss = false
            } else if ($('link[rel="apple-touch-icon"]').length > 0) {
                iconURL = $('link[rel="apple-touch-icon"]').attr('href')
            } else if ($('meta[name="msApplication-TileImage"]').length > 0) {
              iconURL = $('meta[name="msApplication-TileImage"]').attr('content')
            } else if ($('meta[name="msapplication-TileImage"]').length > 0) { /* redundant because ms docs show two case usages */
              iconURL = $('meta[name="msapplication-TileImage"]').attr('content')
            }

            if (iconURL) {
                $('#smartbanner .sb-icon').css('background-image','url('+iconURL+')')
                if (gloss) $('#smartbanner .sb-icon').addClass('gloss')
            } else{
                $('#smartbanner').addClass('no-icon')
            }

            this.bannerHeight = $('#smartbanner').outerHeight() + 2

            if (this.scale > 1) {
                $('#smartbanner')
                    .css('top', parseFloat($('#smartbanner').css('top')) * this.scale)
                    .css('height', parseFloat($('#smartbanner').css('height')) * this.scale)
                $('#smartbanner .sb-container')
                    .css('-webkit-transform', 'scale('+this.scale+')')
                    .css('-msie-transform', 'scale('+this.scale+')')
                    .css('-moz-transform', 'scale('+this.scale+')')
                    .css('width', $(window).width() / this.scale)
            }
        }

      , listen: function () {
            $('#smartbanner .sb-close').on('click',$.proxy(this.close, this))
            $('#smartbanner .sb-button').on('click',$.proxy(this.install, this))
        }

      , show: function(callback) {
            $('#smartbanner').stop().animate({top:0},this.options.speedIn).addClass('shown')
            $('html').animate({marginTop:this.origHtmlMargin+(this.bannerHeight*this.scale)},this.options.speedIn,'swing',callback)
        }

      , hide: function(callback) {
            $('#smartbanner').stop().animate({top:-1*this.bannerHeight*this.scale},this.options.speedOut).removeClass('shown')
            $('html').animate({marginTop:this.origHtmlMargin},this.options.speedOut,'swing',callback)
        }

      , close: function(e) {
            e.preventDefault()
            this.hide()
            this.setCookie('sb-closed','true',this.options.daysHidden)
        }

      , install: function(e) {
            this.hide()
            this.setCookie('sb-installed','true',this.options.daysReminder)
        }

      , setCookie: function(name, value, exdays) {
            var exdate = new Date()
            exdate.setDate(exdate.getDate()+exdays)
            value=escape(value)+((exdays==null)?'':'; expires='+exdate.toUTCString())
            document.cookie=name+'='+value+'; path=/;'
        }

      , getCookie: function(name) {
            var i,x,y,ARRcookies = document.cookie.split(";")
            for(i=0;i<ARRcookies.length;i++) {
                x = ARRcookies[i].substr(0,ARRcookies[i].indexOf("="))
                y = ARRcookies[i].substr(ARRcookies[i].indexOf("=")+1)
                x = x.replace(/^\s+|\s+$/g,"")
                if (x==name) {
                    return unescape(y)
                }
            }
            return null
        }

      // Demo only
      , switchType: function() {
          var that = this

          this.hide(function () {
              that.type = that.type == 'android' ? 'ios' : 'android'
              var meta = $(that.type == 'android' ? 'meta[name="google-play-app"]' : 'meta[name="apple-itunes-app"]').attr('content')
              that.appId = /app-id=([^\s,]+)/.exec(meta)[1]

              $('#smartbanner').detach()
              that.create()
              that.show()
          })
      }
    }

    $.smartbanner = function (option) {
        var $window = $(window)
        , data = $window.data('typeahead')
        , options = typeof option == 'object' && option
        if (!data) $window.data('typeahead', (data = new SmartBanner(options)))
        if (typeof option == 'string') data[option]()
    }

    // override these globally if you like (they are all optional)
    $.smartbanner.defaults = {
        title: null, // What the title of the app should be in the banner (defaults to <title>)
        author: null, // What the author of the app should be in the banner (defaults to <meta name="author"> or hostname)
        price: 'FREE', // Price of the app
        appStoreLanguage: 'us', // Language code for App Store
        inAppStore: 'On the App Store', // Text of price for iOS
        inGooglePlay: 'In Google Play', // Text of price for Android
        inAmazonAppStore: 'In the Amazon Appstore',
        inWindowsStore: 'In the Windows Store', //Text of price for Windows
        icon: null, // The URL of the icon (defaults to <meta name="apple-touch-icon">)
        iconGloss: null, // Force gloss effect for iOS even for precomposed
        button: 'VIEW', // Text for the install button
        url: null, // The URL for the button. Keep null if you want the button to link to the app store.
        scale: 'auto', // Scale based on viewport size (set to 1 to disable)
        speedIn: 300, // Show animation speed of the banner
        speedOut: 400, // Close animation speed of the banner
        daysHidden: 15, // Duration to hide the banner after being closed (0 = always show banner)
        daysReminder: 90, // Duration to hide the banner after "VIEW" is clicked *separate from when the close button is clicked* (0 = always show banner)
        force: null // Choose 'ios', 'android' or 'windows'. Don't do a browser check, just always show this banner
    }

    $.smartbanner.Constructor = SmartBanner

}(window.jQuery);
<|endoftext|>


<commit_before>var Q = require("q");
var _ = require("lodash");

var path = require("path");

var fs = require("./fs");
var parse = require("../parse");

var generators = {
    "site": require("./site"),
    "page": require("./page"),
    "pdf": require("./pdf"),
    "json": require("./json")
};

var generate = function(options) {
    var generator = null;
    var files;

    options = _.defaults(options || {}, {
        // Folders to use
        input: null,
        output: null,

        // Output generator
        generator: "site",

        // Book title, keyword, description
        title: null,
        description: "Book generated using GitBook",

        // Origin github repository id
        github: null,
        githubHost: 'https://github.com/',

        // Theming
        theme: path.resolve(__dirname, '../../theme')
    });

    if (!options.github || !options.title || !options.input || !options.output) {
        return Q.reject(new Error("Need options: github, title, input, output"));
    }

    if (!generators[options.generator]) {
        return Q.reject(new Error("Invalid generator (availables are: "+_.keys(generators).join(", ")));
    }

    // Clean output folder
    return fs.remove(options.output)

    .then(function() {
        return fs.mkdirp(options.output);
    })

    // List all files in the repository
    .then(function() {
        return fs.list(options.input);
    })

    // Check repository is valid
    .then(function(_files) {
        files = _files;

        if (!_.contains(files, "SUMMARY.md") || !_.contains(files, "README.md")) {
            return Q.reject(new Error("Invalid gitbook repository, need SUMMARY.md and README.md"));
        }
    })

    // Get summary
    .then(function() {
        return fs.readFile(path.join(options.input, "SUMMARY.md"), "utf-8")
        .then(function(_summary) {
            options.summary = parse.summary(_summary);

            // Parse navigation
            options.navigation = parse.navigation(options.summary);
        });
    })

    // Create the generator
    .then(function() {
        generator = new generators[options.generator](options);
    })

    // Copy file and replace markdown file
    .then(function() {
        return Q.all(
            _.chain(files)
            .map(function(file) {
                if (!file) return;

                if (file[file.length -1] == "/") {
                    return Q(generator.transferFolder(file));
                } else if (path.extname(file) == ".md" && options.navigation[file] != null) {
                    return fs.readFile(path.join(options.input, file), "utf-8")
                    .then(function(content) {
                        return Q(generator.convertFile(content, file));
                    });
                } else {
                    return Q(generator.transferFile(file));
                }
            })
            .value()
        );
    })

    // Finish gneration
    .then(function() {
        return generator.finish();
    });
};

module.exports = {
    generators: generators,
    folder: generate
};
<commit_msg>Add base for multi-languages book<commit_after>var Q = require("q");
var _ = require("lodash");

var path = require("path");
var swig = require('swig');

var fs = require("./fs");
var parse = require("../parse");

var generators = {
    "site": require("./site"),
    "page": require("./page"),
    "pdf": require("./pdf"),
    "json": require("./json")
};

var generate = function(options) {
    var generator = null;
    var files;

    options = _.defaults(options || {}, {
        // Folders to use
        input: null,
        output: null,

        // Output generator
        generator: "site",

        // Book title, keyword, description
        title: null,
        description: "Book generated using GitBook",

        // Origin github repository id
        github: null,
        githubHost: 'https://github.com/',

        // Theming
        theme: path.resolve(__dirname, '../../theme')
    });

    if (!options.github || !options.title || !options.input || !options.output) {
        return Q.reject(new Error("Need options: github, title, input, output"));
    }

    if (!generators[options.generator]) {
        return Q.reject(new Error("Invalid generator (availables are: "+_.keys(generators).join(", ")));
    }

    // Clean output folder
    return fs.remove(options.output)

    .then(function() {
        return fs.mkdirp(options.output);
    })

    // List all files in the repository
    .then(function() {
        return fs.list(options.input)
        .then(function(_files) {
            files = _files;
        })
    })

    // Detect multi-languages book
    .then(function() {
        if (_.contains(files, "README.md") && _.contains(files, "LANGS.md")) {
            // Multi-languages book
            return fs.readFile(path.join(options.input, "LANGS.md"), "utf-8")

            // Generate sub-books
            .then(function(_langsSummary) {
                options.langsSummary = parse.summary(_langsSummary);

                // Generated a book for each valid entry
                return Q.all(
                    _.chain(langsSummary.chapters)
                    .filter(function(entry) {
                        return entry.path != null;
                    })
                    .map(function(entry) {
                        return generate(_.extend({}, options, {
                            input: path.join(options.input, entry.path),
                            output: path.join(options.output, entry.path)
                        }));
                    })
                    .value()
                );
            });
        } else if (!_.contains(files, "SUMMARY.md") || !_.contains(files, "README.md")) {
            // Invalid book
            return Q.reject(new Error("Invalid gitbook repository, need SUMMARY.md and README.md"));
        } else {
            // Generate the book
            return fs.readFile(path.join(options.input, "SUMMARY.md"), "utf-8")

            // Get summary
            .then(function(_summary) {
                options.summary = parse.summary(_summary);

                // Parse navigation
                options.navigation = parse.navigation(options.summary);
            })

            // Create the generator
            .then(function() {
                generator = new generators[options.generator](options);
            })

            // Copy file and replace markdown file
            .then(function() {
                return Q.all(
                    _.chain(files)
                    .map(function(file) {
                        if (!file) return;

                        if (file[file.length -1] == "/") {
                            return Q(generator.transferFolder(file));
                        } else if (path.extname(file) == ".md" && options.navigation[file] != null) {
                            return fs.readFile(path.join(options.input, file), "utf-8")
                            .then(function(content) {
                                return Q(generator.convertFile(content, file));
                            });
                        } else {
                            return Q(generator.transferFile(file));
                        }
                    })
                    .value()
                );
            })

            // Finish gneration
            .then(function() {
                return generator.finish();
            });
        }
    })
};

module.exports = {
    generators: generators,
    folder: generate
};
<|endoftext|>