<commit_before>//import * as NetProgress from "./NetProgress.js";
import * as ConvBlock from "../Conv/Block.js";

export { Config, Base };

/**
 * A neural network's configuration.
 *
 * A special recommended configuration is 3x3 ShuffleNetV2 without explicit bias (but with implicit bias by cosine activation)
 * and without activation at the end of every block:
 *   - bChannelShuffler: true
 *   - pointwise1ChannelCountRate: 1
 *   - strAvgMaxConv: "Conv"
 *   - depthwiseFilterHeight: 3
 *   - depthwiseChannelMultiplierBlock0Step0: 1
 *   - bBias: false
 *   - strActivationName: "cos"
 *   - strActivationNameAtBlockEnd: null
 *
 * The cosine activation function can achieve impilcit bias. This is because depthwise and pointwise convolution can easily achieve zero
 * and ( cos( 0 ) == 1 ). A constant value (e.g. 1) becomes an bias of the next convolution. So there is not necessary to use explicit bias
 * which has worse performance (than without it).
 *
 * The recommended sourceChannelCount is 4. This is because source image is usually generated by tf.browser.fromPixels() and it handles
 * RGBA 4 channels faster than RGB 3 channels.
 *
 * The last PointDepthPoint's pointwise2 of every block without activation function (i.e. strLastActivationNameOfEveryBlock == "" (or null))
 * could let the output of neural network be any arbitrary value because it will not be restricted by the range of the activation function.
 */
class Config {
  /**
   * @param {number} depthwiseChannelMultiplierBlock0Step0
   *   The depthwise convolution of the first step (Step 0) of the first block (Block 0) will expand input channel by this factor.
   *
   * @see ConvBlock.Base.init
   */
  constructor(
    sourceHeight, sourceWidth, sourceChannelCount = 4,
    stepCountPerBlock,
    bChannelShuffler = true,
    pointwise1ChannelCountRate = 1,
    strAvgMaxConv = "Conv", depthwiseFilterHeight = 3, depthwiseChannelMultiplierBlock0Step0 = 1, bBias = false, strActivationName = "cos",
    strActivationNameAtBlockEnd = null )
  {
    this.sourceHeight = sourceHeight;
    this.sourceWidth = sourceWidth;
    this.sourceChannelCount = sourceChannelCount;
    this.stepCountPerBlock = stepCountPerBlock;
    this.bChannelShuffler = bChannelShuffler;
    this.pointwise1ChannelCountRate = pointwise1ChannelCountRate;
    this.strAvgMaxConv = strAvgMaxConv;
    this.depthwiseFilterHeight = depthwiseFilterHeight;
    this.depthwiseChannelMultiplierBlock0Step0 = depthwiseChannelMultiplierBlock0Step0;
    this.bBias = bBias;
    this.strActivationName = strActivationName;
    this.strActivationNameAtBlockEnd = strActivationNameAtBlockEnd;

    this.sourceHeightWidth = [ this.sourceHeight, this.sourceWidth ];
  }

  /**
   * @param {ImageData|HTMLCanvasElement} source_ImageData_or_Canvas
   *   The image or canvas which provides image.
   *
   * @return {tf.tensor3d}
   *   Return the tensor3d which is the scaled image from canvas. Its size will be this.sourceImageHeightWidth. Its channel count
   * will be this.config.sourceChannelCount.
   */
  create_ScaledSourceTensor_from_ImageData_or_Canvas( source_ImageData_or_Canvas ) {

    let sourceTensor = tf.browser.fromPixels( source_ImageData_or_Canvas, this.sourceChannelCount );

    // If the size (height x width) is as expected, use it directly.
    if (   ( sourceTensor.shape[ 0 ] == this.sourceHeight )
        && ( sourceTensor.shape[ 1 ] == this.sourceWidth  ) )
      return sourceTensor;

    // Otherwise, resize to the default size (height x width) which is the input image size used for training the neural network.
    let scaledSourceTensor = tf.image.resizeBilinear( sourceTensor, this.sourceHeightWidth, true ); // ( alignCorners = true ) for visual image resizing.
    sourceTensor.dispose();

    return scaledSourceTensor;
  }

  /** The channel count of the first block (Block 0). */
  get channelCountBlock0() {
    return this.sourceChannelCount * this.depthwiseChannelMultiplierBlock0Step0;
  }

}

/**
 * A neural network composes of multiple blocks.
 *
 *
 *
 * @member {Config} config
 *   This neural network's configuration.
 *
 * @member {string} structure
 *   A string denote this neural network's structure (configuration).
 *
 * @member {number} totalChannelExpansionFactor
 *   The final output of this neural network will have ( totalChannelExpansionFactor * sourceChannelCount ) channel count.
 *
 * @member {number[]} sourceImageHeightWidth
 *   The size (i.e. [ height, width ]) of the source image. When apply_and_destroy_or_keep() is called, the input tensor should be this size.
 */
class Base {

  /**
   * @param {Config} config
   *   The configuration of this neural network.
   *
   * @param {boolean} bKeepInputTensor
   *   If true, apply_and_destroy_or_keep() will not dispose inputTensor (i.e. keep).
   *
   * @see ConvBlock.Base.init
   */
  init( config, bKeepInputTensor ) {

    this.disposeTensors();

    this.config = config;
    this.bKeepInputTensor = bKeepInputTensor;

    let targetHeight = 1; // The final output always has ( height x width ) = ( 1 x 1 ), i.e. only one pixel per channel.

    let differenceHeight = config.sourceHeight - targetHeight;
//    let filterWidth = depthwiseFilterHeight;

//  this.depthwiseChannelMultiplierBlock0Step0 = depthwiseChannelMultiplierBlock0Step0;
//    this.channelCountBlock0 = config.sourceChannelCount * config.depthwiseChannelMultiplierBlock0Step0;  // the channel count of the first block (Block 0).

    if ( config.stepCountPerBlock <= 0 ) { // Not ShuffleNetV2, Not MobileNetV2.

      // The height of processed image will be reduced a little for any depthwise filter larger than 1x1.
      let heightReducedPerBlock = config.depthwiseFilterHeight - 1;

      // The block count for reducing sourceHeight to targetHeight by tf.depthwiseConv2d( strides = 1, pad = "valid" ).
      this.blockCount = Math.floor( differenceHeight / heightReducedPerBlock );

      // Channel count only be expanded by channel multiplier of depthwise convolution of step 0 of block 0.
      this.totalChannelExpansionFactor = config.depthwiseChannelMultiplierBlock0Step0;

    } else {  // ShuffleNetV2 or MobileNetV2. Halven per block.

      // The block count for reducing sourceHeight to targetHeight by tf.depthwiseConv2d( strides = 2, pad = "same" ).
//      this.blockCount = Math.floor( Math.log2( sourceHeight ) );
      this.blockCount = Math.ceil( Math.log2( config.sourceHeight ) );

//!!! wrong
//       // Channel count is expanded both by channel multiplier of depthwise convolution of step 0 of block 0
//       // and by every block (half height x width and double channel count).
//       this.totalChannelExpansionFactor = depthwiseChannelMultiplierBlock0Step0 * Math.pow( 2, this.blockCount );

      // Channel count is expanded by every block (half height x width and double channel count).
      this.totalChannelExpansionFactor = Math.pow( 2, this.blockCount );
    }

    let nextBlockInputChannelCount = config.sourceChannelCount;
    let nextBlockDepthwiseChannelMultiplier = config.depthwiseChannelMultiplierBlock0Step0; // Only block 0 can have ( depthwise channel multiplier > 1 ).

    let nextKeepInputTensor = bKeepInputTensor; // Only step 0 may or may not keep the input tensor according to caller's necessary.

    this.blocks = new Array( this.blockCount );
    for ( let i = 0; i < this.blockCount; ++i )
    {
      let block = new ConvBlock.Base();
      block.init(
        config.sourceHeight, config.sourceWidth, nextBlockInputChannelCount,
        config.stepCountPerBlock,
        config.bChannelShuffler,
        config.pointwise1ChannelCountRate,
        config.strAvgMaxConv, config.depthwiseFilterHeight, nextBlockDepthwiseChannelMultiplier, config.bBias, config.strActivationName,
        config.strActivationNameAtBlockEnd,
        nextKeepInputTensor
      );

      this.blocks[ i ] = block;
      nextBlockInputChannelCount = block.outputChannelCount; // Using previous block's output channel count as next block's input channel count.
      nextBlockDepthwiseChannelMultiplier = 1;               // Except block 0, all other blocks' depthwise channel multiplier should be 1.
      nextKeepInputTensor = false;                           // All blocks (except block 0) should not keep (and should dispose) the input tensor.
    }

    let block0 = this.blocks[ 0 ];
    {
      let step0 = block0.step0;
      let stepLast = block0.stepLast;

      // e.g. "C24_24__DConv_101x101_DBias_RELU__PConv_PBias_RELU__Block_1__Step_1"
      this.structure = `C${config.sourceChannelCount}_${config.channelCountBlock0}`

        + `${ ( step0.bPointwise1 ) ? "__PConv1(x" + config.pointwise1ChannelCountRate + ")" : "" }`
        + `${ ( step0.bPointwise1 && step0.bPointwise1Bias ) ? ( "_PBias1" ) : "" }`
        + `${ ( step0.bPointwise1 && step0.pointwise1ActivationFunction ) ? ( "_" + step0.pointwise1ActivationName ) : "" }`

        + `${ ( step0.bDepthwise ) ? `__D${config.strAvgMaxConv}_${config.depthwiseFilterHeight}x${config.depthwiseFilterHeight}` : "" }`
        + `${ ( step0.bDepthwise && step0.bDepthwiseBias ) ? ( "_DBias" ) : "" }`
        + `${ ( step0.bDepthwise && step0.depthwiseActivationFunction ) ? ( "_" + config.strActivationName ) : "" }`

        + `${ ( step0.bPointwise2 ) ? "__PConv2" : "" }`
        + `${ ( step0.bPointwise2 && step0.bPointwise2Bias ) ? ( "_PBias2" ) : "" }`
        + `${ ( step0.bPointwise2 && step0.pointwise2ActivationFunction ) ? ( "_" + step0.pointwise2ActivationName ) : "" }`

//If there is not pointwise2 ?

        // If there are more (than 1) steps, show the activation function name (if exists) of the last step.
        + `${ ( config.stepCountPerBlock > 1 ) ?
                ( ( stepLast.bPointwise2 && stepLast.pointwise2ActivationFunction ) ? ( "_" + stepLast.pointwise2ActivationName ) : "" ) : "" }`

        + `__${this.blockCount}_Block`
        + `__${config.stepCountPerBlock}_Step`
        + `${ ( config.bChannelShuffler ) ? "__Shuffle" : ( ( config.stepCountPerBlock > 0 ) ? "__AddInput" : "" ) }`
        //+ `${ ( bKeepInputTensor ) ? "__KeepInput" : "" }`
      ;
    }

    this.sourceImageHeightWidth = [ block0.sourceHeight, block0.sourceWidth ];
  }

  disposeTensors() {
    if ( this.blocks ) {
      for ( let i = 0; i < this.blocks.length; ++i ) {
        let block = this.blocks[ i ];
        block.disposeTensors();
      }
      this.blocks = null;
    }
  }

  /**
   *
   * @param {tf.tensor3d} inputTensor
   *   The image which will be processed. This inputTensor may or may not be disposed according to init()'s bKeepInputTensor.
   * Its size should be this.sourceImageHeightWidth.
   *
   * @param {boolean} bReturn
   *   If true, the result tensor will be returned. Otherwise, the result tensor will be disposed.
   *
   * @return {tf.tensor3d}
   *   If ( bReturn == true ), return the result (new) tensor. If ( bReturn == false ), the result tensor will be disposed and nothing
   * will be returned. No matter in which case, all other intermediate tensors were disposed.
   */
  apply_and_destroy_or_keep( inputTensor, bReturn ) {
    let t = inputTensor;

    let block;
    for ( let i = 0; i < this.blocks.length; ++i ) {
      block = this.blocks[ i ];
      t = block.apply_and_destroy_or_keep( t );
    }

    if ( bReturn )
      return t;
    else
      t.dispose();
  }

}
<commit_msg>Update Net.js<commit_after>//import * as NetProgress from "./NetProgress.js";
import * as ConvBlock from "../Conv/Block.js";

export { Config, Base };

/**
 * A neural network's configuration.
 *
 * A special recommended configuration is 3x3 ShuffleNetV2 without explicit bias (but with implicit bias by cosine activation)
 * and without activation at the end of every block:
 *   - bChannelShuffler: true
 *   - pointwise1ChannelCountRate: 1
 *   - strAvgMaxConv: "Conv"
 *   - depthwiseFilterHeight: 3
 *   - depthwiseChannelMultiplierBlock0Step0: 1
 *   - bBias: false
 *   - strActivationName: "cos"
 *   - strActivationNameAtBlockEnd: null
 *
 * The cosine activation function can achieve impilcit bias. This is because depthwise and pointwise convolution can easily achieve zero
 * and ( cos( 0 ) == 1 ). A constant value (e.g. 1) becomes an bias of the next convolution. So there is not necessary to use explicit bias
 * which has worse performance (than without it).
 *
 * The recommended sourceChannelCount is 4. This is because source image is usually generated by tf.browser.fromPixels() and it handles
 * RGBA 4 channels faster than RGB 3 channels.
 *
 * The last PointDepthPoint's pointwise2 of every block without activation function (i.e. strLastActivationNameOfEveryBlock == "" (or null))
 * could let the output of neural network be any arbitrary value because it will not be restricted by the range of the activation function.
 */
class Config {
  /**
   * @param {number} depthwiseChannelMultiplierBlock0Step0
   *   The depthwise convolution of the first step (Step 0) of the first block (Block 0) will expand input channel by this factor.
   *
   * @see ConvBlock.Base.init
   */
  constructor(
    sourceHeight, sourceWidth, sourceChannelCount = 4,
    stepCountPerBlock,
    bChannelShuffler = true,
    pointwise1ChannelCountRate = 1,
    strAvgMaxConv = "Conv", depthwiseFilterHeight = 3, depthwiseChannelMultiplierBlock0Step0 = 1, bBias = false, strActivationName = "cos",
    strActivationNameAtBlockEnd = null )
  {
    this.sourceHeight = sourceHeight;
    this.sourceWidth = sourceWidth;
    this.sourceChannelCount = sourceChannelCount;
    this.stepCountPerBlock = stepCountPerBlock;
    this.bChannelShuffler = bChannelShuffler;
    this.pointwise1ChannelCountRate = pointwise1ChannelCountRate;
    this.strAvgMaxConv = strAvgMaxConv;
    this.depthwiseFilterHeight = depthwiseFilterHeight;
    this.depthwiseChannelMultiplierBlock0Step0 = depthwiseChannelMultiplierBlock0Step0;
    this.bBias = bBias;
    this.strActivationName = strActivationName;
    this.strActivationNameAtBlockEnd = strActivationNameAtBlockEnd;

    this.sourceHeightWidth = [ this.sourceHeight, this.sourceWidth ];
  }

  /**
   * @param {ImageData|HTMLCanvasElement} source_ImageData_or_Canvas
   *   The image or canvas which provides image.
   *
   * @return {tf.tensor3d}
   *   Return the tensor3d which is the scaled image from canvas. Its size will be this.sourceImageHeightWidth. Its channel count
   * will be this.config.sourceChannelCount.
   */
  create_ScaledSourceTensor_from_ImageData_or_Canvas( source_ImageData_or_Canvas ) {

    let sourceTensor = tf.browser.fromPixels( source_ImageData_or_Canvas, this.sourceChannelCount );

    // If the size (height x width) is as expected, use it directly.
    if (   ( sourceTensor.shape[ 0 ] == this.sourceHeight )
        && ( sourceTensor.shape[ 1 ] == this.sourceWidth  ) )
      return sourceTensor;

    // Otherwise, resize to the default size (height x width) which is the input image size used for training the neural network.
    let scaledSourceTensor = tf.image.resizeBilinear( sourceTensor, this.sourceHeightWidth, true ); // ( alignCorners = true ) for visual image resizing.
    sourceTensor.dispose();

    return scaledSourceTensor;
  }

  /** The channel count of the first block (Block 0). */
  get channelCountBlock0() {
    return this.sourceChannelCount * this.depthwiseChannelMultiplierBlock0Step0;
  }

}

/**
 * A neural network composes of multiple blocks.
 *
 *
 *
 * @member {Config} config
 *   This neural network's configuration.
 *
 * @member {string} structure
 *   A string denote this neural network's structure (configuration).
 *
 * @member {number} totalChannelExpansionFactor
 *   The final output of this neural network will have ( totalChannelExpansionFactor * sourceChannelCount ) channel count.
 *
 * @member {number[]} sourceImageHeightWidth
 *   The size (i.e. [ height, width ]) of the source image. When apply_and_destroy_or_keep() is called, the input tensor should be this size.
 */
class Base {

  /**
   * @param {Config} config
   *   The configuration of this neural network.
   *
   * @param {boolean} bKeepInputTensor
   *   If true, apply_and_destroy_or_keep() will not dispose inputTensor (i.e. keep).
   *
   * @see ConvBlock.Base.init
   */
  init( config, bKeepInputTensor ) {

    this.disposeTensors();

    this.config = config;
    this.bKeepInputTensor = bKeepInputTensor;

    let targetHeight = 1; // The final output always has ( height x width ) = ( 1 x 1 ), i.e. only one pixel per channel.

    let differenceHeight = config.sourceHeight - targetHeight;
//    let filterWidth = depthwiseFilterHeight;

//  this.depthwiseChannelMultiplierBlock0Step0 = depthwiseChannelMultiplierBlock0Step0;
//    this.channelCountBlock0 = config.sourceChannelCount * config.depthwiseChannelMultiplierBlock0Step0;  // the channel count of the first block (Block 0).

    if ( config.stepCountPerBlock <= 0 ) { // Not ShuffleNetV2, Not MobileNetV2.

      // The height of processed image will be reduced a little for any depthwise filter larger than 1x1.
      let heightReducedPerBlock = config.depthwiseFilterHeight - 1;

      // The block count for reducing sourceHeight to targetHeight by tf.depthwiseConv2d( strides = 1, pad = "valid" ).
      this.blockCount = Math.floor( differenceHeight / heightReducedPerBlock );

      // Channel count only be expanded by channel multiplier of depthwise convolution of step 0 of block 0.
      this.totalChannelExpansionFactor = config.depthwiseChannelMultiplierBlock0Step0;

    } else {  // ShuffleNetV2 or MobileNetV2. Halven per block.

      // The block count for reducing sourceHeight to targetHeight by tf.depthwiseConv2d( strides = 2, pad = "same" ).
//      this.blockCount = Math.floor( Math.log2( sourceHeight ) );
      this.blockCount = Math.ceil( Math.log2( config.sourceHeight ) );

//!!! wrong
//       // Channel count is expanded both by channel multiplier of depthwise convolution of step 0 of block 0
//       // and by every block (half height x width and double channel count).
//       this.totalChannelExpansionFactor = depthwiseChannelMultiplierBlock0Step0 * Math.pow( 2, this.blockCount );

      // Channel count is expanded by every block (half height x width and double channel count).
      this.totalChannelExpansionFactor = Math.pow( 2, this.blockCount );
    }

    let nextBlockInputChannelCount = config.sourceChannelCount;
    let nextBlockDepthwiseChannelMultiplier = config.depthwiseChannelMultiplierBlock0Step0; // Only block 0 can have ( depthwise channel multiplier > 1 ).

    let nextKeepInputTensor = bKeepInputTensor; // Only step 0 may or may not keep the input tensor according to caller's necessary.

    this.blocks = new Array( this.blockCount );
    for ( let i = 0; i < this.blockCount; ++i )
    {
      let block = new ConvBlock.Base();
      block.init(
        config.sourceHeight, config.sourceWidth, nextBlockInputChannelCount,
        config.stepCountPerBlock,
        config.bChannelShuffler,
        config.pointwise1ChannelCountRate,
        config.strAvgMaxConv, config.depthwiseFilterHeight, nextBlockDepthwiseChannelMultiplier, config.bBias, config.strActivationName,
        config.strActivationNameAtBlockEnd,
        nextKeepInputTensor
      );

      this.blocks[ i ] = block;
      nextBlockInputChannelCount = block.outputChannelCount; // Using previous block's output channel count as next block's input channel count.
      nextBlockDepthwiseChannelMultiplier = 1;               // Except block 0, all other blocks' depthwise channel multiplier should be 1.
      nextKeepInputTensor = false;                           // All blocks (except block 0) should not keep (and should dispose) the input tensor.
    }

    let block0 = this.blocks[ 0 ];
    {
      let step0 = block0.step0;
      let stepLast = block0.stepLast;

      // e.g. "C24_24__DConv_101x101_DBias_RELU__PConv_PBias_RELU__Block_1__Step_1"
      this.structure = `C${config.sourceChannelCount}_${config.channelCountBlock0}`

        + `${ ( step0.bPointwise1 ) ? "__PConv1(x" + config.pointwise1ChannelCountRate + ")" : "" }`
        + `${ ( step0.bPointwise1 && step0.bPointwise1Bias ) ? ( "_PBias1" ) : "" }`
        + `${ ( step0.bPointwise1 && step0.pointwise1ActivationFunction ) ? ( "_" + step0.pointwise1ActivationName ) : "" }`

        + `${ ( step0.bDepthwise ) ? `__D${config.strAvgMaxConv}_${config.depthwiseFilterHeight}x${config.depthwiseFilterHeight}` : "" }`
        + `${ ( step0.bDepthwise && step0.bDepthwiseBias ) ? ( "_DBias" ) : "" }`
        + `${ ( step0.bDepthwise && step0.depthwiseActivationFunction ) ? ( "_" + config.strActivationName ) : "" }`

        + `${ ( step0.bPointwise2 ) ? "__PConv2" : "" }`
        + `${ ( step0.bPointwise2 && step0.bPointwise2Bias ) ? ( "_PBias2" ) : "" }`
        + `${ ( step0.bPointwise2 && step0.pointwise2ActivationFunction ) ? ( "_" + step0.pointwise2ActivationName ) : "" }`

        // If there are more (than 1) steps, show the activation function name (if exists) of the last step.
        //
        // Note: Is it possible that there is not pointwise2 convolution? No, it is not possible (for our Conv/Block.js).
        // So the last activation function must be the activation function the last step's pointwise2 convolution.
        + `${ ( config.stepCountPerBlock > 1 ) ?
                ( ( stepLast.bPointwise2 && stepLast.pointwise2ActivationFunction ) ? ( "_" + stepLast.pointwise2ActivationName ) : "" ) : "" }`

        + `__${this.blockCount}_Block`
        + `__${config.stepCountPerBlock}_Step`
        + `${ ( config.bChannelShuffler ) ? "__Shuffle" : ( ( config.stepCountPerBlock > 0 ) ? "__AddInput" : "" ) }`
        //+ `${ ( bKeepInputTensor ) ? "__KeepInput" : "" }`
      ;
    }

    this.sourceImageHeightWidth = [ block0.sourceHeight, block0.sourceWidth ];
  }

  disposeTensors() {
    if ( this.blocks ) {
      for ( let i = 0; i < this.blocks.length; ++i ) {
        let block = this.blocks[ i ];
        block.disposeTensors();
      }
      this.blocks = null;
    }
  }

  /**
   *
   * @param {tf.tensor3d} inputTensor
   *   The image which will be processed. This inputTensor may or may not be disposed according to init()'s bKeepInputTensor.
   * Its size should be this.sourceImageHeightWidth.
   *
   * @param {boolean} bReturn
   *   If true, the result tensor will be returned. Otherwise, the result tensor will be disposed.
   *
   * @return {tf.tensor3d}
   *   If ( bReturn == true ), return the result (new) tensor. If ( bReturn == false ), the result tensor will be disposed and nothing
   * will be returned. No matter in which case, all other intermediate tensors were disposed.
   */
  apply_and_destroy_or_keep( inputTensor, bReturn ) {
    let t = inputTensor;

    let block;
    for ( let i = 0; i < this.blocks.length; ++i ) {
      block = this.blocks[ i ];
      t = block.apply_and_destroy_or_keep( t );
    }

    if ( bReturn )
      return t;
    else
      t.dispose();
  }

}
<|endoftext|>


<commit_before>"use strict";

const autosize = require("autosize");

const render_invitation_failed_error = require("../templates/invitation_failed_error.hbs");
const render_invite_subscription = require("../templates/invite_subscription.hbs");
const render_settings_dev_env_email_access = require("../templates/settings/dev_env_email_access.hbs");

function reset_error_messages() {
    $("#invite_status").hide().text("").removeClass(common.status_classes);
    $("#multiuse_invite_status").hide().text("").removeClass(common.status_classes);

    $("#invitee_emails").closest(".control-group").removeClass("warning error");

    if (page_params.development_environment) {
        $("#dev_env_msg").hide().text("").removeClass(common.status_classes);
    }
}

function get_common_invitation_data() {
    const invite_as = parseInt($("#invite_as").val(), 10);
    const stream_ids = [];
    $("#invite-stream-checkboxes input:checked").each(function () {
        const stream_id = parseInt($(this).val(), 10);
        stream_ids.push(stream_id);
    });
    const data = {
        csrfmiddlewaretoken: $('input[name="csrfmiddlewaretoken"]').attr("value"),
        invite_as,
        stream_ids: JSON.stringify(stream_ids),
    };
    return data;
}

function beforeSend() {
    reset_error_messages();
    // TODO: You could alternatively parse the textarea here, and return errors to
    // the user if they don't match certain constraints (i.e. not real email addresses,
    // aren't in the right domain, etc.)
    //
    // OR, you could just let the server do it. Probably my temptation.
    $("#submit-invitation").button("loading");
    return true;
}

function submit_invitation_form() {
    const invite_status = $("#invite_status");
    const invitee_emails = $("#invitee_emails");
    const invitee_emails_group = invitee_emails.closest(".control-group");
    const data = get_common_invitation_data();
    data.invitee_emails = $("#invitee_emails").val();

    channel.post({
        url: "/json/invites",
        data,
        beforeSend,
        success() {
            ui_report.success(i18n.t("User(s) invited successfully."), invite_status);
            invitee_emails_group.removeClass("warning");
            invitee_emails.val("");

            if (page_params.development_environment) {
                const rendered_email_msg = render_settings_dev_env_email_access();
                $("#dev_env_msg").html(rendered_email_msg).addClass("alert-info").show();
            }
        },
        error(xhr) {
            const arr = JSON.parse(xhr.responseText);
            if (arr.errors === undefined) {
                // There was a fatal error, no partial processing occurred.
                ui_report.error("", xhr, invite_status);
            } else {
                // Some users were not invited.
                const invitee_emails_errored = [];
                const error_list = [];
                let is_invitee_deactivated = false;
                arr.errors.forEach((value) => {
                    const [email, error_message, deactivated] = value;
                    error_list.push(`${email}: ${error_message}`);
                    if (deactivated) {
                        is_invitee_deactivated = true;
                    }
                    invitee_emails_errored.push(email);
                });

                const error_response = render_invitation_failed_error({
                    error_message: arr.msg,
                    error_list,
                    is_admin: page_params.is_admin,
                    is_invitee_deactivated,
                });
                ui_report.message(error_response, invite_status, "alert-warning");
                invitee_emails_group.addClass("warning");

                if (arr.sent_invitations) {
                    invitee_emails.val(invitee_emails_errored.join("\n"));
                }
            }
        },
        complete() {
            $("#submit-invitation").button("reset");
        },
    });
}

function generate_multiuse_invite() {
    const invite_status = $("#multiuse_invite_status");
    const data = get_common_invitation_data();
    channel.post({
        url: "/json/invites/multiuse",
        data,
        beforeSend,
        success(data) {
            ui_report.success(
                i18n.t('Invitation link: __link__', {
                    link: data.invite_link,
                }),
                invite_status,
            );
        },
        error(xhr) {
            ui_report.error("", xhr, invite_status);
        },
        complete() {
            $("#submit-invitation").button("reset");
        },
    });
}

exports.get_invite_streams = function () {
    const streams = stream_data.get_invite_stream_data();

    function compare_streams(a, b) {
        return a.name.localeCompare(b.name);
    }
    streams.sort(compare_streams);
    return streams;
};

function update_subscription_checkboxes() {
    const data = {
        streams: exports.get_invite_streams(),
        notifications_stream: stream_data.get_notifications_stream(),
    };
    const html = render_invite_subscription(data);
    $("#streams_to_add").html(html);
}

function prepare_form_to_be_shown() {
    update_subscription_checkboxes();
    reset_error_messages();
}

exports.launch = function () {
    $("#submit-invitation").button();
    prepare_form_to_be_shown();

    overlays.open_overlay({
        name: "invite",
        overlay: $("#invite-user"),
        on_close() {
            hashchange.exit_overlay();
        },
    });

    autosize($("#invitee_emails").trigger("focus"));

    // Ctrl + Enter key to submit form
    $("#invite-user").on("keydown", (e) => {
        if (e.key === "Enter" && e.ctrlKey) {
            submit_invitation_form();
        }
    });
};

exports.initialize = function () {
    $(document).on("click", ".invite_check_all_button", (e) => {
        $("#streams_to_add :checkbox").prop("checked", true);
        e.preventDefault();
    });

    $(document).on("click", ".invite_uncheck_all_button", (e) => {
        $("#streams_to_add :checkbox").prop("checked", false);
        e.preventDefault();
    });

    $("#submit-invitation").on("click", () => {
        const is_generate_invite_link = $("#generate_multiuse_invite_radio").prop("checked");
        if (is_generate_invite_link) {
            generate_multiuse_invite();
        } else {
            submit_invitation_form();
        }
    });

    $("#generate_multiuse_invite_button").on("click", () => {
        $("#generate_multiuse_invite_radio").prop("checked", true);
        $("#multiuse_radio_section").show();
        $("#invite-method-choice").hide();
        $("#invitee_emails").prop("disabled", true);
        $("#submit-invitation").text(i18n.t("Generate invite link"));
        $("#submit-invitation").data("loading-text", i18n.t("Generating link..."));
        reset_error_messages();
    });

    $("#invite-user").on("change", "#generate_multiuse_invite_radio", () => {
        $("#invitee_emails").prop("disabled", false);
        $("#submit-invitation").text(i18n.t("Invite"));
        $("#submit-invitation").data("loading-text", i18n.t("Inviting..."));
        $("#multiuse_radio_section").hide();
        $("#invite-method-choice").show();
        reset_error_messages();
    });
};

window.invite = exports;
<commit_msg>invite: Scroll to top and focus email input after submit in invite.js.<commit_after>"use strict";

const autosize = require("autosize");

const render_invitation_failed_error = require("../templates/invitation_failed_error.hbs");
const render_invite_subscription = require("../templates/invite_subscription.hbs");
const render_settings_dev_env_email_access = require("../templates/settings/dev_env_email_access.hbs");

function reset_error_messages() {
    $("#invite_status").hide().text("").removeClass(common.status_classes);
    $("#multiuse_invite_status").hide().text("").removeClass(common.status_classes);

    $("#invitee_emails").closest(".control-group").removeClass("warning error");

    if (page_params.development_environment) {
        $("#dev_env_msg").hide().text("").removeClass(common.status_classes);
    }
}

function get_common_invitation_data() {
    const invite_as = parseInt($("#invite_as").val(), 10);
    const stream_ids = [];
    $("#invite-stream-checkboxes input:checked").each(function () {
        const stream_id = parseInt($(this).val(), 10);
        stream_ids.push(stream_id);
    });
    const data = {
        csrfmiddlewaretoken: $('input[name="csrfmiddlewaretoken"]').attr("value"),
        invite_as,
        stream_ids: JSON.stringify(stream_ids),
    };
    return data;
}

function beforeSend() {
    reset_error_messages();
    // TODO: You could alternatively parse the textarea here, and return errors to
    // the user if they don't match certain constraints (i.e. not real email addresses,
    // aren't in the right domain, etc.)
    //
    // OR, you could just let the server do it. Probably my temptation.
    $("#submit-invitation").button("loading");
    return true;
}

function submit_invitation_form() {
    const invite_status = $("#invite_status");
    const invitee_emails = $("#invitee_emails");
    const invitee_emails_group = invitee_emails.closest(".control-group");
    const data = get_common_invitation_data();
    data.invitee_emails = $("#invitee_emails").val();

    channel.post({
        url: "/json/invites",
        data,
        beforeSend,
        success() {
            ui_report.success(i18n.t("User(s) invited successfully."), invite_status);
            invitee_emails_group.removeClass("warning");
            invitee_emails.val("");

            if (page_params.development_environment) {
                const rendered_email_msg = render_settings_dev_env_email_access();
                $("#dev_env_msg").html(rendered_email_msg).addClass("alert-info").show();
            }
        },
        error(xhr) {
            const arr = JSON.parse(xhr.responseText);
            if (arr.errors === undefined) {
                // There was a fatal error, no partial processing occurred.
                ui_report.error("", xhr, invite_status);
            } else {
                // Some users were not invited.
                const invitee_emails_errored = [];
                const error_list = [];
                let is_invitee_deactivated = false;
                arr.errors.forEach((value) => {
                    const [email, error_message, deactivated] = value;
                    error_list.push(`${email}: ${error_message}`);
                    if (deactivated) {
                        is_invitee_deactivated = true;
                    }
                    invitee_emails_errored.push(email);
                });

                const error_response = render_invitation_failed_error({
                    error_message: arr.msg,
                    error_list,
                    is_admin: page_params.is_admin,
                    is_invitee_deactivated,
                });
                ui_report.message(error_response, invite_status, "alert-warning");
                invitee_emails_group.addClass("warning");

                if (arr.sent_invitations) {
                    invitee_emails.val(invitee_emails_errored.join("\n"));
                }
            }
        },
        complete() {
            $("#submit-invitation").button("reset");
            $("#invitee_emails").focus();
            ui.get_scroll_element($('#invite_user_form .modal-body'))[0].scrollTop = 0;
        },
    });
}

function generate_multiuse_invite() {
    const invite_status = $("#multiuse_invite_status");
    const data = get_common_invitation_data();
    channel.post({
        url: "/json/invites/multiuse",
        data,
        beforeSend,
        success(data) {
            ui_report.success(
                i18n.t('Invitation link: __link__', {
                    link: data.invite_link,
                }),
                invite_status,
            );
        },
        error(xhr) {
            ui_report.error("", xhr, invite_status);
        },
        complete() {
            $("#submit-invitation").button("reset");
        },
    });
}

exports.get_invite_streams = function () {
    const streams = stream_data.get_invite_stream_data();

    function compare_streams(a, b) {
        return a.name.localeCompare(b.name);
    }
    streams.sort(compare_streams);
    return streams;
};

function update_subscription_checkboxes() {
    const data = {
        streams: exports.get_invite_streams(),
        notifications_stream: stream_data.get_notifications_stream(),
    };
    const html = render_invite_subscription(data);
    $("#streams_to_add").html(html);
}

function prepare_form_to_be_shown() {
    update_subscription_checkboxes();
    reset_error_messages();
}

exports.launch = function () {
    $("#submit-invitation").button();
    prepare_form_to_be_shown();

    overlays.open_overlay({
        name: "invite",
        overlay: $("#invite-user"),
        on_close() {
            hashchange.exit_overlay();
        },
    });

    autosize($("#invitee_emails").trigger("focus"));

    // Ctrl + Enter key to submit form
    $("#invite-user").on("keydown", (e) => {
        if (e.key === "Enter" && e.ctrlKey) {
            submit_invitation_form();
        }
    });
};

exports.initialize = function () {
    $(document).on("click", ".invite_check_all_button", (e) => {
        $("#streams_to_add :checkbox").prop("checked", true);
        e.preventDefault();
    });

    $(document).on("click", ".invite_uncheck_all_button", (e) => {
        $("#streams_to_add :checkbox").prop("checked", false);
        e.preventDefault();
    });

    $("#submit-invitation").on("click", () => {
        const is_generate_invite_link = $("#generate_multiuse_invite_radio").prop("checked");
        if (is_generate_invite_link) {
            generate_multiuse_invite();
        } else {
            submit_invitation_form();
        }
    });

    $("#generate_multiuse_invite_button").on("click", () => {
        $("#generate_multiuse_invite_radio").prop("checked", true);
        $("#multiuse_radio_section").show();
        $("#invite-method-choice").hide();
        $("#invitee_emails").prop("disabled", true);
        $("#submit-invitation").text(i18n.t("Generate invite link"));
        $("#submit-invitation").data("loading-text", i18n.t("Generating link..."));
        reset_error_messages();
    });

    $("#invite-user").on("change", "#generate_multiuse_invite_radio", () => {
        $("#invitee_emails").prop("disabled", false);
        $("#submit-invitation").text(i18n.t("Invite"));
        $("#submit-invitation").data("loading-text", i18n.t("Inviting..."));
        $("#multiuse_radio_section").hide();
        $("#invite-method-choice").show();
        reset_error_messages();
    });
};

window.invite = exports;
<|endoftext|>


<commit_before>var invite = (function () {

var exports = {};

function reset_error_messages() {
    var invite_status = $('#invite_status');
    var invitee_emails = $("#invitee_emails");
    var invitee_emails_group = invitee_emails.closest('.control-group');

    invite_status.hide().text('').removeClass('alert-error alert-warning alert-success');
    invitee_emails_group.removeClass('warning error');
    if (page_params.development_environment) {
        $('#dev_env_msg').hide().text('').removeClass('alert-error alert-warning alert-success');
    }
}

function submit_invitation_form() {
    var invite_status = $('#invite_status');
    var invitee_emails = $("#invitee_emails");
    var invitee_emails_group = invitee_emails.closest('.control-group');
    var invite_as = $('#invite_as').val();
    var data = {
        invitee_emails: $("#invitee_emails").val(),
        invite_as_admin: invite_as === 'admin',
        csrfmiddlewaretoken: $('input[name="csrfmiddlewaretoken"]').attr('value'),
    };
    var streams = [];
    $.each($("#invite-stream-checkboxes input:checked"), function () {
        streams.push($(this).val());
    });
    data.stream = streams;

    channel.post({
        url: "/json/invites",
        data: data,
        traditional: true,
        beforeSubmit: function () {
            reset_error_messages();
            // TODO: You could alternatively parse the textarea here, and return errors to
            // the user if they don't match certain constraints (i.e. not real email addresses,
            // aren't in the right domain, etc.)
            //
            // OR, you could just let the server do it. Probably my temptation.
            $('#submit-invitation').button('loading');
            return true;
        },
        success: function () {
            $('#submit-invitation').button('reset');
            invite_status.text(i18n.t('User(s) invited successfully.'))
                .addClass('alert-success')
                .show();
            invitee_emails.val('');

            if (page_params.development_environment) {
                var rendered_email_msg = templates.render('dev_env_email_access');
                $('#dev_env_msg').html(rendered_email_msg).addClass('alert-info').show();
            }

        },
        error: function (xhr) {
            $('#submit-invitation').button('reset');
            var arr = JSON.parse(xhr.responseText);
            if (arr.errors === undefined) {
                // There was a fatal error, no partial processing occurred.
                invite_status.text(arr.msg)
                    .addClass('alert-error')
                    .show();
            } else {
                // Some users were not invited.
                var invitee_emails_errored = [];
                var error_list = $('<ul>');
                _.each(arr.errors, function (value) {
                    error_list.append($('<li>').text(value.join(': ')));
                    invitee_emails_errored.push(value[0]);
                });

                invite_status.addClass('alert-warning')
                    .empty()
                    .append($('<p>').text(arr.msg))
                    .append(error_list)
                    .show();
                invitee_emails_group.addClass('warning');

                if (arr.sent_invitations) {
                    invitee_emails.val(invitee_emails_errored.join('\n'));
                }

            }

        },
    });
}

// `get_invite_streams` is further modification of stream_data.invite_streams(), it is
// defined here to keep stream_data.invite_stream() generic.
exports.get_invite_streams = function () {
    var streams = [];

    _.each(stream_data.invite_streams(), function (value) {
        var is_invite_only = stream_data.get_invite_only(value);
        var is_notifications_stream = value === page_params.notifications_stream;

        // You can't actually elect to invite someone to the
        // notifications stream. We won't even show it as a choice unless
        // it's the only stream you have, or if you've made it private.
        if (stream_data.subscribed_streams().length === 1 ||
            !is_notifications_stream ||
            is_notifications_stream && is_invite_only) {

            streams.push({
                name: value,
                invite_only: is_invite_only,
                default_stream: stream_data.get_default_status(value),
            });

            // Sort by default status.
            streams.sort(function (a, b) {
                return b.default_stream - a.default_stream;
            });
        }
    });

    return streams;
};

function update_subscription_checkboxes() {
    var data = {streams: exports.get_invite_streams()};
    var html = templates.render('invite_subscription', data);
    $('#streams_to_add').html(html);
}

function prepare_form_to_be_shown() {
    update_subscription_checkboxes();
    reset_error_messages();
}

exports.launch = function () {
    ui.set_up_scrollbar($("#invite_user_form .modal-body"));

    $('#submit-invitation').button();
    prepare_form_to_be_shown();
    $("#invitee_emails").focus().autosize();

    overlays.open_overlay({
        name: 'invite',
        overlay: $('#invite-user'),
        on_close: function () {
            hashchange.exit_overlay();
        },
    });
    $("#submit-invitation").on("click", submit_invitation_form);
};

exports.initialize = function () {
    $(document).on('click', '.invite_check_all_button', function (e) {
        $('#streams_to_add :checkbox').prop('checked', true);
        e.preventDefault();
    });

    $(document).on('click', '.invite_uncheck_all_button', function (e) {
        $('#streams_to_add :checkbox').prop('checked', false);
        e.preventDefault();
    });
};

return exports;

}());

if (typeof module !== 'undefined') {
    module.exports = invite;
}
window.invite = invite;
<commit_msg>invite user: Fix click handler called multiple time on submit.<commit_after>var invite = (function () {

var exports = {};

function reset_error_messages() {
    var invite_status = $('#invite_status');
    var invitee_emails = $("#invitee_emails");
    var invitee_emails_group = invitee_emails.closest('.control-group');

    invite_status.hide().text('').removeClass('alert-error alert-warning alert-success');
    invitee_emails_group.removeClass('warning error');
    if (page_params.development_environment) {
        $('#dev_env_msg').hide().text('').removeClass('alert-error alert-warning alert-success');
    }
}

function submit_invitation_form() {
    var invite_status = $('#invite_status');
    var invitee_emails = $("#invitee_emails");
    var invitee_emails_group = invitee_emails.closest('.control-group');
    var invite_as = $('#invite_as').val();
    var data = {
        invitee_emails: $("#invitee_emails").val(),
        invite_as_admin: invite_as === 'admin',
        csrfmiddlewaretoken: $('input[name="csrfmiddlewaretoken"]').attr('value'),
    };
    var streams = [];
    $.each($("#invite-stream-checkboxes input:checked"), function () {
        streams.push($(this).val());
    });
    data.stream = streams;

    channel.post({
        url: "/json/invites",
        data: data,
        traditional: true,
        beforeSubmit: function () {
            reset_error_messages();
            // TODO: You could alternatively parse the textarea here, and return errors to
            // the user if they don't match certain constraints (i.e. not real email addresses,
            // aren't in the right domain, etc.)
            //
            // OR, you could just let the server do it. Probably my temptation.
            $('#submit-invitation').button('loading');
            return true;
        },
        success: function () {
            $('#submit-invitation').button('reset');
            invite_status.text(i18n.t('User(s) invited successfully.'))
                .addClass('alert-success')
                .show();
            invitee_emails.val('');

            if (page_params.development_environment) {
                var rendered_email_msg = templates.render('dev_env_email_access');
                $('#dev_env_msg').html(rendered_email_msg).addClass('alert-info').show();
            }

        },
        error: function (xhr) {
            $('#submit-invitation').button('reset');
            var arr = JSON.parse(xhr.responseText);
            if (arr.errors === undefined) {
                // There was a fatal error, no partial processing occurred.
                invite_status.text(arr.msg)
                    .addClass('alert-error')
                    .show();
            } else {
                // Some users were not invited.
                var invitee_emails_errored = [];
                var error_list = $('<ul>');
                _.each(arr.errors, function (value) {
                    error_list.append($('<li>').text(value.join(': ')));
                    invitee_emails_errored.push(value[0]);
                });

                invite_status.addClass('alert-warning')
                    .empty()
                    .append($('<p>').text(arr.msg))
                    .append(error_list)
                    .show();
                invitee_emails_group.addClass('warning');

                if (arr.sent_invitations) {
                    invitee_emails.val(invitee_emails_errored.join('\n'));
                }

            }

        },
    });
}

// `get_invite_streams` is further modification of stream_data.invite_streams(), it is
// defined here to keep stream_data.invite_stream() generic.
exports.get_invite_streams = function () {
    var streams = [];

    _.each(stream_data.invite_streams(), function (value) {
        var is_invite_only = stream_data.get_invite_only(value);
        var is_notifications_stream = value === page_params.notifications_stream;

        // You can't actually elect to invite someone to the
        // notifications stream. We won't even show it as a choice unless
        // it's the only stream you have, or if you've made it private.
        if (stream_data.subscribed_streams().length === 1 ||
            !is_notifications_stream ||
            is_notifications_stream && is_invite_only) {

            streams.push({
                name: value,
                invite_only: is_invite_only,
                default_stream: stream_data.get_default_status(value),
            });

            // Sort by default status.
            streams.sort(function (a, b) {
                return b.default_stream - a.default_stream;
            });
        }
    });

    return streams;
};

function update_subscription_checkboxes() {
    var data = {streams: exports.get_invite_streams()};
    var html = templates.render('invite_subscription', data);
    $('#streams_to_add').html(html);
}

function prepare_form_to_be_shown() {
    update_subscription_checkboxes();
    reset_error_messages();
}

exports.launch = function () {
    ui.set_up_scrollbar($("#invite_user_form .modal-body"));

    $('#submit-invitation').button();
    prepare_form_to_be_shown();
    $("#invitee_emails").focus().autosize();

    overlays.open_overlay({
        name: 'invite',
        overlay: $('#invite-user'),
        on_close: function () {
            hashchange.exit_overlay();
        },
    });
};

exports.initialize = function () {
    $(document).on('click', '.invite_check_all_button', function (e) {
        $('#streams_to_add :checkbox').prop('checked', true);
        e.preventDefault();
    });

    $(document).on('click', '.invite_uncheck_all_button', function (e) {
        $('#streams_to_add :checkbox').prop('checked', false);
        e.preventDefault();
    });
    $("#submit-invitation").on("click", submit_invitation_form);
};

return exports;

}());

if (typeof module !== 'undefined') {
    module.exports = invite;
}
window.invite = invite;
<|endoftext|>


<commit_before>/* global describe, it, before, after */
import { expect } from 'chai'
import { existsSync, statSync } from 'fs'
import { sync as rimraf } from 'rimraf'
import kill from 'tree-kill'
import { add } from '../../lib/commands.js'

const mockDir = './test/_mock'
const tmpDir = './test/_tmp'

describe('Functional: add()', function () {
  before(function (done) {
    rimraf('*/**/.DS_Store')
    rimraf(`${tmpDir}/*`)
    done()
  })

  after(function (done) {
    rimraf(`${tmpDir}/*`)
    done()
  })

  it('should return an error on 7z error', function (done) {
    const archive = `${tmpDir}/addnot.7z`
    const source = `${mockDir}/dev/null`
    const seven = add(archive, source)
    seven.on('error', function (err) {
      expect(err).to.be.an.instanceof(Error)
      expect(err.level).to.equal('WARNING')
      expect(err.message).to.equal('No such file or directory')
      expect(err.path).to.equal(source)
      done()
      try { kill(seven._childProcess.pid) } catch (e) {}
    })
  })

  it('should return an error on spawn error', function (done) {
    const archive = ``
    const source = ``
    const bin = '/i/hope/this/is/not/where/yout/7zip/bin/is'
    const seven = add(archive, source, {
      $bin: bin
      // or this test will fail
    })
    seven.on('error', function (err) {
      expect(err).to.be.an.instanceof(Error)
      expect(err.errno).to.equal('ENOENT')
      expect(err.code).to.equal('ENOENT')
      expect(err.syscall).to.equal(`spawn ${bin}`)
      expect(err.path).to.equal(bin)
      done()
    })
  })

  it('should emit progress values', function (done) {
    const archive = `${tmpDir}/progress.7z`
    const source = `${mockDir}/DirImages/`
    const seven = add(archive, source, { bs: ['p1'] })
    seven.on('progress', function (progress) {
      expect(progress.percent).to.be.an('number')
      expect(progress.fileCount).to.be.an('number')
      try { kill(seven._childProcess.pid) } catch (e) {}
    }).on('end', () => done())
  })

  it('should create an archive of correct size', function (done) {
    const archive = `${tmpDir}/size.7z`
    const source = `${mockDir}/DirHex/`
    const seven = add(archive, source)
    seven.on('end', function () {
      const size = statSync(archive).size
      expect(size).to.equal(427)
      expect(existsSync(archive)).to.be.true
      done()
      try { kill(seven._childProcess.pid) } catch (e) {}
    })
  })

  it('should get info from headers and footers', function (done) {
    const archive = `${tmpDir}/headers-and-footers.7z`
    const source = `${mockDir}/DirHex/`
    const seven = add(archive, source)
    seven.on('end', function () {
      // headers
      expect(seven.info['Creating archive']).to.equal(archive)
      expect(seven.info['Items to compress']).to.equal('30')
      // footers
      expect(seven.info['Files read from disk']).to.equal('24')
      expect(seven.info['Archive size']).to.equal('427 bytes (1 KiB)')
      done()
      try { kill(seven._childProcess.pid) } catch (e) {}
    })
  })

  it('should emit files on progress', function (done) {
    const archive = `${tmpDir}/files.7z`
    const source = `${mockDir}/DirHex/`
    const seven = add(archive, source, { bs: ['p1'] })
    seven.on('data', function (data) {
      expect(data.symbol).to.equal('+')
      expect(data.file).to.be.a('string')
      try { kill(seven._childProcess.pid) } catch (e) {}
    }).on('end', () => done())
  })

  it('should accept multiple sources as a array', function (done) {
    const archive = `${tmpDir}/txt-and-md-only.7z`
    const source = [
      `${mockDir}/DirExt/*.txt`,
      `${mockDir}/DirExt/*.md`
    ]
    const seven = add(archive, source, {
      r: true
    })
    seven.on('end', function () {
      expect(seven.info['Items to compress']).to.equal('6')
      expect(seven.info['Files read from disk']).to.equal('6')
      done()
    })
  })

  it('should add files to an exsiting archive', function (done) {
    // This test relies on a predecessor test
    const archive = `${tmpDir}/files.7z`
    const source = [
      `${mockDir}/DirExt/*.txt`,
      `${mockDir}/DirExt/*.md`
    ]
    const seven = add(archive, source, {
      r: true
    })
    seven.on('data', function (data) {
      expect(data.symbol).to.equal('+')
      expect(data.file).to.be.a('string')
    }).on('end', function () {
      expect(seven.info['Open archive']).to.equal(archive)
      expect(seven.info['Updating archive']).to.equal(archive)
      expect(seven.info['Items to compress']).to.equal('6')
      expect(seven.info['Files read from disk']).to.equal('6')
      expect(seven.info['Archive size']).to.equal('511 bytes (1 KiB)')
      done()
    })
  })

  it('should update files of an exsiting archive', function (done) {
    // This test relies on a predecessor test
    const archive = `${tmpDir}/files.7z`
    const source = [
      `${mockDir}/DirExtUpdate/*.txt`,
      `${mockDir}/DirExtUpdate/*.md`
    ]
    const seven = add(archive, source)
    seven.on('data', function (data) {
      expect(data.symbol).to.equal('U')
      expect(data.file).to.be.a('string')
    }).on('end', function () {
      expect(seven.info['Open archive']).to.equal(archive)
      expect(seven.info['Updating archive']).to.equal(archive)
      expect(seven.info['Items to compress']).to.equal('2')
      expect(seven.info['Files read from disk']).to.equal('2')
      expect(seven.info['Archive size']).to.equal('2690 bytes (3 KiB)')
      done()
    })
  })
})
<commit_msg>ci: Find coverage error part1<commit_after>/* global describe, it, before, after */
import { expect } from 'chai'
import { existsSync, statSync } from 'fs'
import { sync as rimraf } from 'rimraf'
import kill from 'tree-kill'
import { add } from '../../lib/commands.js'

const mockDir = './test/_mock'
const tmpDir = './test/_tmp'

describe('Functional: add()', function () {
  before(function (done) {
    rimraf('*/**/.DS_Store')
    rimraf(`${tmpDir}/*`)
    done()
  })

  after(function (done) {
    rimraf(`${tmpDir}/*`)
    done()
  })

  // it('should return an error on 7z error', function (done) {
  //   const archive = `${tmpDir}/addnot.7z`
  //   const source = `${mockDir}/dev/null`
  //   const seven = add(archive, source)
  //   seven.on('error', function (err) {
  //     expect(err).to.be.an.instanceof(Error)
  //     expect(err.level).to.equal('WARNING')
  //     expect(err.message).to.equal('No such file or directory')
  //     expect(err.path).to.equal(source)
  //     done()
  //     try { kill(seven._childProcess.pid) } catch (e) {}
  //   })
  // })

  // it('should return an error on spawn error', function (done) {
  //   const archive = ``
  //   const source = ``
  //   const bin = '/i/hope/this/is/not/where/yout/7zip/bin/is'
  //   const seven = add(archive, source, {
  //     $bin: bin
  //     // or this test will fail
  //   })
  //   seven.on('error', function (err) {
  //     expect(err).to.be.an.instanceof(Error)
  //     expect(err.errno).to.equal('ENOENT')
  //     expect(err.code).to.equal('ENOENT')
  //     expect(err.syscall).to.equal(`spawn ${bin}`)
  //     expect(err.path).to.equal(bin)
  //     done()
  //   })
  // })

  it('should emit progress values', function (done) {
    const archive = `${tmpDir}/progress.7z`
    const source = `${mockDir}/DirImages/`
    const seven = add(archive, source, { bs: ['p1'] })
    seven.on('progress', function (progress) {
      expect(progress.percent).to.be.an('number')
      expect(progress.fileCount).to.be.an('number')
      try { kill(seven._childProcess.pid) } catch (e) {}
    }).on('end', () => done())
  })

  it('should create an archive of correct size', function (done) {
    const archive = `${tmpDir}/size.7z`
    const source = `${mockDir}/DirHex/`
    const seven = add(archive, source)
    seven.on('end', function () {
      const size = statSync(archive).size
      expect(size).to.equal(427)
      expect(existsSync(archive)).to.be.true
      done()
      try { kill(seven._childProcess.pid) } catch (e) {}
    })
  })

  it('should get info from headers and footers', function (done) {
    const archive = `${tmpDir}/headers-and-footers.7z`
    const source = `${mockDir}/DirHex/`
    const seven = add(archive, source)
    seven.on('end', function () {
      // headers
      expect(seven.info['Creating archive']).to.equal(archive)
      expect(seven.info['Items to compress']).to.equal('30')
      // footers
      expect(seven.info['Files read from disk']).to.equal('24')
      expect(seven.info['Archive size']).to.equal('427 bytes (1 KiB)')
      done()
      try { kill(seven._childProcess.pid) } catch (e) {}
    })
  })

  it('should emit files on progress', function (done) {
    const archive = `${tmpDir}/files.7z`
    const source = `${mockDir}/DirHex/`
    const seven = add(archive, source, { bs: ['p1'] })
    seven.on('data', function (data) {
      expect(data.symbol).to.equal('+')
      expect(data.file).to.be.a('string')
      try { kill(seven._childProcess.pid) } catch (e) {}
    }).on('end', () => done())
  })

  it('should accept multiple sources as a array', function (done) {
    const archive = `${tmpDir}/txt-and-md-only.7z`
    const source = [
      `${mockDir}/DirExt/*.txt`,
      `${mockDir}/DirExt/*.md`
    ]
    const seven = add(archive, source, {
      r: true
    })
    seven.on('end', function () {
      expect(seven.info['Items to compress']).to.equal('6')
      expect(seven.info['Files read from disk']).to.equal('6')
      done()
    })
  })

  it('should add files to an exsiting archive', function (done) {
    // This test relies on a predecessor test
    const archive = `${tmpDir}/files.7z`
    const source = [
      `${mockDir}/DirExt/*.txt`,
      `${mockDir}/DirExt/*.md`
    ]
    const seven = add(archive, source, {
      r: true
    })
    seven.on('data', function (data) {
      expect(data.symbol).to.equal('+')
      expect(data.file).to.be.a('string')
    }).on('end', function () {
      expect(seven.info['Open archive']).to.equal(archive)
      expect(seven.info['Updating archive']).to.equal(archive)
      expect(seven.info['Items to compress']).to.equal('6')
      expect(seven.info['Files read from disk']).to.equal('6')
      expect(seven.info['Archive size']).to.equal('511 bytes (1 KiB)')
      done()
    })
  })

  it('should update files of an exsiting archive', function (done) {
    // This test relies on a predecessor test
    const archive = `${tmpDir}/files.7z`
    const source = [
      `${mockDir}/DirExtUpdate/*.txt`,
      `${mockDir}/DirExtUpdate/*.md`
    ]
    const seven = add(archive, source)
    seven.on('data', function (data) {
      expect(data.symbol).to.equal('U')
      expect(data.file).to.be.a('string')
    }).on('end', function () {
      expect(seven.info['Open archive']).to.equal(archive)
      expect(seven.info['Updating archive']).to.equal(archive)
      expect(seven.info['Items to compress']).to.equal('2')
      expect(seven.info['Files read from disk']).to.equal('2')
      expect(seven.info['Archive size']).to.equal('2690 bytes (3 KiB)')
      done()
    })
  })
})
<|endoftext|>


<commit_before>/* global app, Backbone, StateMachine,
   mozRTCPeerConnection, mozRTCSessionDescription */
/**
 * Talkilla models and collections.
 */
(function(app, Backbone, StateMachine) {
  "use strict";

  app.models.Call = Backbone.Model.extend({
    media: undefined,

    initialize: function(media) {
      this.media = media;
      this.state = StateMachine.create({
        initial: 'ready',
        events: [
          // Call initiation scenario
          {name: 'start',     from: 'ready',   to: 'pending'},
          {name: 'establish', from: 'pending', to: 'ongoing'},

          // Incoming call scenario
          {name: 'incoming',  from: 'ready',   to: 'pending'},
          {name: 'accept',    from: 'pending', to: 'ongoing'},

          // Call hangup
          {name: 'hangup',    from: '*',       to: 'terminated'}
        ]
      });

      this.accept    = this.state.accept.bind(this.state);

      this.media.on("offer-ready", function(offer) {
        var callData = {
          caller: this.get("id"),
          callee: this.get("otherUser"),
          offer: offer
        };

        this.trigger("send-offer", callData);
      }.bind(this));

      this.media.on("answer-ready", function(answer) {
        var callData = {
          caller: this.get("otherUser"),
          callee: this.get("id"),
          answer: answer
        };

        this.trigger("send-answer", callData);
      }.bind(this));
    },

    start: function(options) {
      this.set({id: options.caller, otherUser: options.callee});
      this.state.start();
      this.media.offer(options);
    },

    incoming: function(options) {
      this.set({otherUser: options.caller, id: options.callee});
      this.state.incoming();
      this.media.answer(options);
    },

    establish: function(options) {
      this.state.establish();
      this.media.establish(options.answer);
    },

    hangup: function() {
      this.state.hangup();
      this.media.hangup();
    }
  });

  /**
   * WebRTC call model
   *
   * @class WebRTCCall
   * @constructor
   *
   * Fired when a SDP offer is available (see #offer).
   * @event offer-ready
   * @param {Object} offer An SDP offer
   *
   * Fired when a SDP answer is available (see #answer).
   * @event answer-ready
   * @param {Object} answer An SDP answer
   *
   *
   * Example:
   *
   * var webrtc = new app.models.WebRTCCall();
   *
   * // Caller side
   * webrtc.on('offer-ready', function(offer) {
   *   sendOffer(offer);
   * });
   * webrtc.offer()
   *
   * // Callee side
   * webrtc.on('answer-ready', function(answer) {
   *   sendAnswer(answer);
   * }
   * webrtc.answer(offer);
   *
   * // Once the caller receive the answer
   * webrtc.establish(answer)
   *
   * // Both sides
   * webrtc.on("change:localStream", function() {
   *   localVideo.mozSrcObject = webrtc.get("localStream");
   *   localVideo.play();
   * });
   * webrtc.on("change:remoteStream", function() {
   *   remoteVideo.mozSrcObject = webrtc.get("remoteStream");
   *   remoteVideo.play();
   * });
   */
  app.models.WebRTCCall = Backbone.Model.extend({
    pc: undefined, // peer connection
    dcIn: undefined, // data channel in
    dcOut: undefined, // data channel out

    initialize: function() {
      this.pc = new mozRTCPeerConnection();
      this.dcOut = this.pc.createDataChannel('dc', {});

      this.pc.onaddstream = function(event) {
        this.set("remoteStream", event.stream);
      }.bind(this);

      // data channel for incoming calls
      this.pc.ondatachannel = function(event) {
        this.dcIn = this._setupDataChannelIn(event.channel);
        this.trigger('dc.in.ready', event);
      }.bind(this);

      this._onError = this._onError.bind(this);
    },

    /**
     * Create a SDP offer after calling getUserMedia. In case of
     * success, it triggers an offer-ready event with the created offer.
     */
    offer: function(options) {
      this.set({video: options.video, audio: options.audio});
      var callback = this.trigger.bind(this, "offer-ready");
      this._getMedia(this._createOffer.bind(this, callback), this._onError);
    },

    /**
     * Establish a WebRTC p2p connection.
     * @param {Object} answer
     */
    establish: function(answer) {
      var answerDescription = new mozRTCSessionDescription(answer);
      var cb = function() {};
      this.pc.setRemoteDescription(answerDescription, cb, this._onError);
    },

    /**
     * Create a SDP answer after calling getUserMedia. In case of
     * success, it triggers an answer-ready event with the created answer.
     * @param {Object} the offer to respond to
     */
    answer: function(options) {
      this.set({video: options.video, audio: options.audio});
      var callback = this.trigger.bind(this, "answer-ready");
      var createAnswer = this._createAnswer.bind(this, options.offer, callback);
      this._getMedia(createAnswer, this._onError);
    },

    send: function(data) {
      if (!this.dcOut)
        return this._onError('no data channel connection available');
      this.dcOut.send(data);
    },

    /**
     * Close the p2p connection.
     */
    hangup: function() {
      this.pc.close();
    },

    _createOffer: function(callback) {
      if (!this.get('video') && !this.get('audio'))
        return this._onError("Call type has not been defined");

      this.pc.createOffer(function(offer) {
        var cb = callback.bind(this, offer);
        this.pc.setLocalDescription(offer, cb, this._onError);
      }.bind(this), this._onError);
    },

    _createAnswer: function(offer, callback) {
      if (!this.get('video') && !this.get('audio'))
        return this._onError("Call type has not been defined");

      var offerDescription = new mozRTCSessionDescription(offer);
      this.pc.setRemoteDescription(offerDescription, function() {
        this.pc.createAnswer(function(answer) {
          var cb = callback.bind(this, answer);
          this.pc.setLocalDescription(answer, cb, this._onError);
        }.bind(this), this._onError);
      }.bind(this), this._onError);
    },

    _getMedia: function(callback, errback) {
      var constraints = {
        video: this.get('video'),
        audio: this.get('audio')
      };

      var cb = function (localStream) {
        this.pc.addStream(localStream);
        this.set("localStream", localStream);
        callback();
      }.bind(this);

      navigator.mozGetUserMedia(constraints, cb, errback);
    },

    _setupDataChannelIn: function(channel) {
      channel.binaryType = 'blob';

      channel.onopen = function(event) {
        this.trigger('dc.in.open', event);
      }.bind(this);

      channel.onmessage = function(event) {
        this.trigger('dc.in.message', event);
      }.bind(this);

      channel.onerror = function(event) {
        this.trigger('dc.in.error', event);
      }.bind(this);

      channel.onclose = function(event) {
        this.trigger('dc.in.close', event);
      }.bind(this);

      return channel;
    },

    _onError: function(error) {
      // XXX Better error logging and handling
      console.error("WebRTCCall error: " + error);
    }
  });

  app.models.Notification = Backbone.Model.extend({
    defaults: {type:    "default",
               message: "empty message"}
  });

  app.models.TextChatEntry = Backbone.Model.extend({
    defaults: {nick: undefined,
               message: undefined,
               date: new Date().getTime()}
  });

  app.models.TextChat = Backbone.Collection.extend({
    model: app.models.TextChatEntry,

    newEntry: function(data) {
      var entry = this.add(data).at(this.length - 1);
      this.trigger('entry.created', entry);
    }
  });

  app.models.User = Backbone.Model.extend({
    defaults: {nick: undefined, presence: "disconnected"},

    initialize: function() {
      // If the user has signed in or out, trigger the appropraite
      // change
      this.on("change", function() {
        if (this.isLoggedIn() && !this.wasLoggedIn())
          this.trigger('signin');
        else if (!this.isLoggedIn() && this.wasLoggedIn())
          this.trigger('signout');
      }.bind(this));
    },

    /**
     * Returns true if the user is logged in.
     */
    isLoggedIn: function() {
      return this.get('presence') !== "disconnected" &&
        this.get('nick') !== undefined;
    },

    /**
     * Returns true if the user was logged in prior to the last change
     * on the model. Returns false if there have been no changes.
     */
    wasLoggedIn: function() {
      return this.previous('presence') !== "disconnected" &&
        this.previous('nick') !== undefined;
    }
  });

  app.models.UserSet = Backbone.Collection.extend({
    model: app.models.User,

    initialize: function(models, options) {
      this.models = models || [];
      this.options = options;
      // register the talkilla.users event
      app.port.on('talkilla.users', function(users) {
        this.reset(users);
      }.bind(this));
    }
  });
})(app, Backbone, StateMachine);
<commit_msg>Some initial doc additions and formatting correcting<commit_after>/* global app, Backbone, StateMachine,
   mozRTCPeerConnection, mozRTCSessionDescription */
/**
 * Talkilla models and collections.
 */
(function(app, Backbone, StateMachine) {
  "use strict";

  app.models.Call = Backbone.Model.extend({
    media: undefined,

    initialize: function(media) {
      this.media = media;
      this.state = StateMachine.create({
        initial: 'ready',
        events: [
          // Call initiation scenario
          {name: 'start',     from: 'ready',   to: 'pending'},
          {name: 'establish', from: 'pending', to: 'ongoing'},

          // Incoming call scenario
          {name: 'incoming',  from: 'ready',   to: 'pending'},
          {name: 'accept',    from: 'pending', to: 'ongoing'},

          // Call hangup
          {name: 'hangup',    from: '*',       to: 'terminated'}
        ]
      });

      this.accept    = this.state.accept.bind(this.state);

      this.media.on("offer-ready", function(offer) {
        this.trigger("send-offer", {
          caller: this.get("id"),
          callee: this.get("otherUser"),
          offer: offer
        });
      }.bind(this));

      this.media.on("answer-ready", function(answer) {
        this.trigger("send-answer", {
          caller: this.get("otherUser"),
          callee: this.get("id"),
          answer: answer
        });
      }.bind(this));
    },

    start: function(options) {
      this.set({id: options.caller, otherUser: options.callee});
      this.state.start();
      this.media.offer(options);
    },

    incoming: function(options) {
      this.set({otherUser: options.caller, id: options.callee});
      this.state.incoming();
      this.media.answer(options);
    },

    establish: function(options) {
      this.state.establish();
      this.media.establish(options.answer);
    },

    hangup: function() {
      this.state.hangup();
      this.media.hangup();
    }
  });

  /**
   * WebRTC call model
   *
   * @class WebRTCCall
   * @constructor
   *
   * Fired when a SDP offer is available (see #offer).
   * @event offer-ready
   * @param {Object} offer An SDP offer
   *
   * Fired when a SDP answer is available (see #answer).
   * @event answer-ready
   * @param {Object} answer An SDP answer
   *
   *
   * Example:
   *
   * var webrtc = new app.models.WebRTCCall();
   *
   * // Caller side
   * webrtc.on('offer-ready', function(offer) {
   *   sendOffer(offer);
   * });
   * webrtc.offer()
   *
   * // Callee side
   * webrtc.on('answer-ready', function(answer) {
   *   sendAnswer(answer);
   * }
   * webrtc.answer(offer);
   *
   * // Once the caller receive the answer
   * webrtc.establish(answer)
   *
   * // Both sides
   * webrtc.on("change:localStream", function() {
   *   localVideo.mozSrcObject = webrtc.get("localStream");
   *   localVideo.play();
   * });
   * webrtc.on("change:remoteStream", function() {
   *   remoteVideo.mozSrcObject = webrtc.get("remoteStream");
   *   remoteVideo.play();
   * });
   */
  app.models.WebRTCCall = Backbone.Model.extend({
    pc: undefined, // peer connection
    dcIn: undefined, // data channel in
    dcOut: undefined, // data channel out

    initialize: function() {
      this.pc = new mozRTCPeerConnection();
      this.dcOut = this.pc.createDataChannel('dc', {});

      this.pc.onaddstream = function(event) {
        this.set("remoteStream", event.stream);
      }.bind(this);

      // data channel for incoming calls
      this.pc.ondatachannel = function(event) {
        this.dcIn = this._setupDataChannelIn(event.channel);
        this.trigger('dc.in.ready', event);
      }.bind(this);

      this._onError = this._onError.bind(this);
    },

    /**
     * Create a SDP offer after calling getUserMedia. In case of
     * success, it triggers an offer-ready event with the created offer.
     * @param {Object} options object containing:
     *
     * - video: set to true to enable video
     * - audio: set to true to enable audio
     */
    offer: function(options) {
      this.set({video: options.video, audio: options.audio});
      var callback = this.trigger.bind(this, "offer-ready");
      this._getMedia(this._createOffer.bind(this, callback), this._onError);
    },

    /**
     * Establish a WebRTC p2p connection.
     * @param {Object} answer
     */
    establish: function(answer) {
      var answerDescription = new mozRTCSessionDescription(answer);
      var cb = function() {};
      this.pc.setRemoteDescription(answerDescription, cb, this._onError);
    },

    /**
     * Create a SDP answer after calling getUserMedia. In case of
     * success, it triggers an answer-ready event with the created answer.
     * @param {Object} options object containing:
     *
     * - video: set to true to enable video
     * - audio: set to true to enable audio
     * - offer: the offer (sdp) to respond to.
     */
    answer: function(options) {
      this.set({video: options.video, audio: options.audio});
      var callback = this.trigger.bind(this, "answer-ready");
      var createAnswer = this._createAnswer.bind(this, options.offer, callback);
      this._getMedia(createAnswer, this._onError);
    },

    send: function(data) {
      if (!this.dcOut)
        return this._onError('no data channel connection available');
      this.dcOut.send(data);
    },

    /**
     * Close the p2p connection.
     */
    hangup: function() {
      this.pc.close();
    },

    _createOffer: function(callback) {
      if (!this.get('video') && !this.get('audio'))
        return this._onError("Call type has not been defined");

      this.pc.createOffer(function(offer) {
        var cb = callback.bind(this, offer);
        this.pc.setLocalDescription(offer, cb, this._onError);
      }.bind(this), this._onError);
    },

    _createAnswer: function(offer, callback) {
      if (!this.get('video') && !this.get('audio'))
        return this._onError("Call type has not been defined");

      var offerDescription = new mozRTCSessionDescription(offer);
      this.pc.setRemoteDescription(offerDescription, function() {
        this.pc.createAnswer(function(answer) {
          var cb = callback.bind(this, answer);
          this.pc.setLocalDescription(answer, cb, this._onError);
        }.bind(this), this._onError);
      }.bind(this), this._onError);
    },

    _getMedia: function(callback, errback) {
      var constraints = {
        video: this.get('video'),
        audio: this.get('audio')
      };

      var cb = function (localStream) {
        this.pc.addStream(localStream);
        this.set("localStream", localStream);
        callback();
      }.bind(this);

      navigator.mozGetUserMedia(constraints, cb, errback);
    },

    _setupDataChannelIn: function(channel) {
      channel.binaryType = 'blob';

      channel.onopen = function(event) {
        this.trigger('dc.in.open', event);
      }.bind(this);

      channel.onmessage = function(event) {
        this.trigger('dc.in.message', event);
      }.bind(this);

      channel.onerror = function(event) {
        this.trigger('dc.in.error', event);
      }.bind(this);

      channel.onclose = function(event) {
        this.trigger('dc.in.close', event);
      }.bind(this);

      return channel;
    },

    _onError: function(error) {
      // XXX Better error logging and handling
      console.error("WebRTCCall error: " + error);
    }
  });

  app.models.Notification = Backbone.Model.extend({
    defaults: {type:    "default",
               message: "empty message"}
  });

  app.models.TextChatEntry = Backbone.Model.extend({
    defaults: {nick: undefined,
               message: undefined,
               date: new Date().getTime()}
  });

  app.models.TextChat = Backbone.Collection.extend({
    model: app.models.TextChatEntry,

    newEntry: function(data) {
      var entry = this.add(data).at(this.length - 1);
      this.trigger('entry.created', entry);
    }
  });

  app.models.User = Backbone.Model.extend({
    defaults: {nick: undefined, presence: "disconnected"},

    initialize: function() {
      // If the user has signed in or out, trigger the appropraite
      // change
      this.on("change", function() {
        if (this.isLoggedIn() && !this.wasLoggedIn())
          this.trigger('signin');
        else if (!this.isLoggedIn() && this.wasLoggedIn())
          this.trigger('signout');
      }.bind(this));
    },

    /**
     * Returns true if the user is logged in.
     */
    isLoggedIn: function() {
      return this.get('presence') !== "disconnected" &&
        this.get('nick') !== undefined;
    },

    /**
     * Returns true if the user was logged in prior to the last change
     * on the model. Returns false if there have been no changes.
     */
    wasLoggedIn: function() {
      return this.previous('presence') !== "disconnected" &&
        this.previous('nick') !== undefined;
    }
  });

  app.models.UserSet = Backbone.Collection.extend({
    model: app.models.User,

    initialize: function(models, options) {
      this.models = models || [];
      this.options = options;
      // register the talkilla.users event
      app.port.on('talkilla.users', function(users) {
        this.reset(users);
      }.bind(this));
    }
  });
})(app, Backbone, StateMachine);
<|endoftext|>


<commit_before>const make = require('./make')

// This test isn't included in abstract-leveldown because
// the empty-check is currently performed by leveldown.
make('iterator#seek throws if target is empty', function (db, t, done) {
  var targets = ['', Buffer.alloc(0), []]
  var pending = targets.length

  targets.forEach(function (target) {
    var ite = db.iterator()
    var error

    try {
      ite.seek(target)
    } catch (err) {
      error = err.message
    }

    t.is(error, 'cannot seek() to an empty target', 'got error')
    ite.end(end)
  })

  function end (err) {
    t.ifError(err, 'no error from end()')
    if (!--pending) done()
  }
})

make('iterator optimized for seek', function (db, t, done) {
  var batch = db.batch()
  batch.put('a', 1)
  batch.put('b', 1)
  batch.put('c', 1)
  batch.put('d', 1)
  batch.put('e', 1)
  batch.put('f', 1)
  batch.put('g', 1)
  batch.write(function (err) {
    var ite = db.iterator()
    t.ifError(err, 'no error from batch()')
    ite.next(function (err, key, value) {
      t.ifError(err, 'no error from next()')
      t.equal(key.toString(), 'a', 'key matches')
      t.equal(ite.cache.length, 0, 'no cache')
      ite.next(function (err, key, value) {
        t.ifError(err, 'no error from next()')
        t.equal(key.toString(), 'b', 'key matches')
        t.ok(ite.cache.length > 0, 'has cached items')
        ite.seek('d')
        t.notOk(ite.cache, 'cache is removed')
        ite.next(function (err, key, value) {
          t.ifError(err, 'no error from next()')
          t.equal(key.toString(), 'd', 'key matches')
          t.equal(ite.cache.length, 0, 'no cache')
          ite.next(function (err, key, value) {
            t.ifError(err, 'no error from next()')
            t.equal(key.toString(), 'e', 'key matches')
            t.ok(ite.cache.length > 0, 'has cached items')
            ite.end(done)
          })
        })
      })
    })
  })
})

make('close db with open iterator', function (db, t, done) {
  var ite = db.iterator()
  var cnt = 0
  var hadError = false

  ite.next(function loop (err, key, value) {
    if (cnt++ === 0) {
      t.ifError(err, 'no error from next()')
    } else {
      t.equal(err.message, 'iterator has ended')
      hadError = true
    }
    if (key !== undefined) { ite.next(loop) }
  })

  db.close(function (err) {
    t.ifError(err, 'no error from close()')
    t.ok(hadError)

    done(null, false)
  })
})
<commit_msg>Clarify expected behavior in iterator-test<commit_after>const make = require('./make')

// This test isn't included in abstract-leveldown because
// the empty-check is currently performed by leveldown.
make('iterator#seek throws if target is empty', function (db, t, done) {
  var targets = ['', Buffer.alloc(0), []]
  var pending = targets.length

  targets.forEach(function (target) {
    var ite = db.iterator()
    var error

    try {
      ite.seek(target)
    } catch (err) {
      error = err.message
    }

    t.is(error, 'cannot seek() to an empty target', 'got error')
    ite.end(end)
  })

  function end (err) {
    t.ifError(err, 'no error from end()')
    if (!--pending) done()
  }
})

make('iterator optimized for seek', function (db, t, done) {
  var batch = db.batch()
  batch.put('a', 1)
  batch.put('b', 1)
  batch.put('c', 1)
  batch.put('d', 1)
  batch.put('e', 1)
  batch.put('f', 1)
  batch.put('g', 1)
  batch.write(function (err) {
    var ite = db.iterator()
    t.ifError(err, 'no error from batch()')
    ite.next(function (err, key, value) {
      t.ifError(err, 'no error from next()')
      t.equal(key.toString(), 'a', 'key matches')
      t.equal(ite.cache.length, 0, 'no cache')
      ite.next(function (err, key, value) {
        t.ifError(err, 'no error from next()')
        t.equal(key.toString(), 'b', 'key matches')
        t.ok(ite.cache.length > 0, 'has cached items')
        ite.seek('d')
        t.notOk(ite.cache, 'cache is removed')
        ite.next(function (err, key, value) {
          t.ifError(err, 'no error from next()')
          t.equal(key.toString(), 'd', 'key matches')
          t.equal(ite.cache.length, 0, 'no cache')
          ite.next(function (err, key, value) {
            t.ifError(err, 'no error from next()')
            t.equal(key.toString(), 'e', 'key matches')
            t.ok(ite.cache.length > 0, 'has cached items')
            ite.end(done)
          })
        })
      })
    })
  })
})

make('close db with open iterator', function (db, t, done) {
  var ite = db.iterator()
  var cnt = 0
  var hadError = false

  ite.next(function loop (err, key, value) {
    if (cnt++ === 0) {
      // The first call should succeed, because it was scheduled before close()
      t.ifError(err, 'no error from next()')
    } else {
      // The second call should fail, because it was scheduled after close()
      t.equal(err.message, 'iterator has ended')
      hadError = true
    }
    if (key !== undefined) { ite.next(loop) }
  })

  db.close(function (err) {
    t.ifError(err, 'no error from close()')
    t.ok(hadError)

    done(null, false)
  })
})
<|endoftext|>


<commit_before>var lunrIndex, pagesIndex;

// Initialize lunrjs using our generated index file
function initLunr() {
    // First retrieve the index file
    $.getJSON(baseurl + "/index.json")
        .done(function(index) {
            pagesIndex = index;
            // Set up lunrjs by declaring the fields we use
            // Also provide their boost level for the ranking
            lunrIndex = new lunr.Index
            lunrIndex.ref("uri");
            lunrIndex.field('title', {
                boost: 15
            });
            lunrIndex.field('tags', {
                boost: 10
            });
            lunrIndex.field("content", {
                boost: 5
            });

            // Feed lunr with each file and let lunr actually index them
            pagesIndex.forEach(function(page) {
                lunrIndex.add(page);
            });
            lunrIndex.pipeline.remove(lunrIndex.stemmer)
        })
        .fail(function(jqxhr, textStatus, error) {
            var err = textStatus + ", " + error;
            console.error("Error getting Hugo index flie:", err);
        });
}

/**
 * Trigger a search in lunr and transform the result
 *
 * @param  {String} query
 * @return {Array}  results
 */
function search(query) {
    // Find the item in our index corresponding to the lunr one to have more info
    return lunrIndex.search(query).map(function(result) {
            return pagesIndex.filter(function(page) {
                return page.uri === result.ref;
            })[0];
        });
}

// Let's get started
initLunr();
$( document ).ready(function() {
    var horseyList = horsey($("#search-by").get(0), {
        suggestions: function (value, done) {
            var query = $("#search-by").val();
            var results = search(query);
            done(results);
        },
        filter: function (q, suggestion) {
            return true;
        },
        set: function (value) {
            location.href=value.href;
        },
        render: function (li, suggestion) {
            var uri = suggestion.uri.substring(1,suggestion.uri.length);
            var indexOfIndex = uri.lastIndexOf("/index");
            if (indexOfIndex == -1) {
                indexOfIndex = uri.length;
            }
            var href = uri.substring(uri.indexOf("/"), indexOfIndex);
            suggestion.href = baseurl + href;

            var query = $("#search-by").val();
            var numWords = 2;
            var text = suggestion.content.match("(?:\\s?(?:[\\w]+)\\s?){0,"+numWords+"}"+query+"(?:\\s?(?:[\\w]+)\\s?){0,"+numWords+"}");
            suggestion.context = text;
            var image = '
' + '» ' + suggestion.title + '

' + (suggestion.context || '') +'
';
            li.innerHTML = image;
        },
        limit: 10
    });
    horseyList.refreshPosition();
});
<commit_msg>fix search url<commit_after>var lunrIndex, pagesIndex;

// Initialize lunrjs using our generated index file
function initLunr() {
    // First retrieve the index file
    $.getJSON(baseurl + "index.json")
        .done(function(index) {
            pagesIndex = index;
            // Set up lunrjs by declaring the fields we use
            // Also provide their boost level for the ranking
            lunrIndex = new lunr.Index
            lunrIndex.ref("uri");
            lunrIndex.field('title', {
                boost: 15
            });
            lunrIndex.field('tags', {
                boost: 10
            });
            lunrIndex.field("content", {
                boost: 5
            });

            // Feed lunr with each file and let lunr actually index them
            pagesIndex.forEach(function(page) {
                lunrIndex.add(page);
            });
            lunrIndex.pipeline.remove(lunrIndex.stemmer)
        })
        .fail(function(jqxhr, textStatus, error) {
            var err = textStatus + ", " + error;
            console.error("Error getting Hugo index flie:", err);
        });
}

/**
 * Trigger a search in lunr and transform the result
 *
 * @param  {String} query
 * @return {Array}  results
 */
function search(query) {
    // Find the item in our index corresponding to the lunr one to have more info
    return lunrIndex.search(query).map(function(result) {
            return pagesIndex.filter(function(page) {
                return page.uri === result.ref;
            })[0];
        });
}

// Let's get started
initLunr();
$( document ).ready(function() {
    var horseyList = horsey($("#search-by").get(0), {
        suggestions: function (value, done) {
            var query = $("#search-by").val();
            var results = search(query);
            done(results);
        },
        filter: function (q, suggestion) {
            return true;
        },
        set: function (value) {
            location.href=value.href;
        },
        render: function (li, suggestion) {
            var uri = suggestion.uri.substring(1,suggestion.uri.length);
            var indexOfIndex = uri.lastIndexOf("/index");
            if (indexOfIndex == -1) {
                indexOfIndex = uri.length;
            }
            var href = uri.substring(uri.indexOf("/"), indexOfIndex);
            suggestion.href = baseurl + href;

            var query = $("#search-by").val();
            var numWords = 2;
            var text = suggestion.content.match("(?:\\s?(?:[\\w]+)\\s?){0,"+numWords+"}"+query+"(?:\\s?(?:[\\w]+)\\s?){0,"+numWords+"}");
            suggestion.context = text;
            var image = '
' + '» ' + suggestion.title + '

' + (suggestion.context || '') +'
';
            li.innerHTML = image;
        },
        limit: 10
    });
    horseyList.refreshPosition();
});
<|endoftext|>


<commit_before>"use strict";

var jsdom = require("../..").jsdom;

// These tests are mostly random regression tests, not systematic parsing tests. They are compiled from the bug tracker.

exports["unclosed  (GH-605)"] = function (t) {
  var doc = jsdom("

first tdsecond td

");

  var tds = doc.getElementsByTagName("td");

  t.equal(tds.length, 2);
  t.equal(tds[0].innerHTML, 'first td');
  t.equal(tds[1].innerHTML, 'second td');

  t.done();
};

exports["innerHTML of ");

  var scripts = doc.getElementsByTagName("script");

  t.equal(scripts.length, 1);
  t.equal(scripts[0].innerHTML, "script innerHTML");

  t.done();
};

exports["attributes containing '<' and '>' (GH-494)"] = function (t) {
  var doc = jsdom("<p title='<'>stuff

'>more

just testing
");

  var ps = doc.getElementsByTagName("p");

  t.equal(ps.length, 3);
  t.equal(ps[0].getAttribute("title"), "<");
  t.equal(ps[1].getAttribute("title"), ">");

  t.done();
};
<commit_msg>Add test for multiline attributes. Fixes #585.<commit_after>"use strict";

var jsdom = require("../..").jsdom;

// These tests are mostly random regression tests, not systematic parsing tests. They are compiled from the bug tracker.

exports["unclosed  (GH-605)"] = function (t) {
  var doc = jsdom("

first tdsecond td

");

  var tds = doc.getElementsByTagName("td");

  t.equal(tds.length, 2);
  t.equal(tds[0].innerHTML, 'first td');
  t.equal(tds[1].innerHTML, 'second td');

  t.done();
};

exports["multiline attributes (GH-585)"] = function (t) {
  var doc = jsdom("<a data-title='hello \nworld' href='example.org'>link");

  var as = doc.getElementsByTagName("a");

  t.equal(as.length, 1);
  t.equal(as[0].innerHTML, "link");
  t.equal(as[0].getAttribute("data-title"), "hello \nworld");
  t.equal(as[0].getAttribute("href"), "example.org");

  t.done();
};

exports["innerHTML of ");

  var scripts = doc.getElementsByTagName("script");

  t.equal(scripts.length, 1);
  t.equal(scripts[0].innerHTML, "script innerHTML");

  t.done();
};

exports["attributes containing '<' and '>' (GH-494)"] = function (t) {
  var doc = jsdom("<p title='<'>stuff

'>more

just testing
");

  var ps = doc.getElementsByTagName("p");

  t.equal(ps.length, 3);
  t.equal(ps[0].getAttribute("title"), "<");
  t.equal(ps[1].getAttribute("title"), ">");

  t.done();
};
<|endoftext|>


<commit_before>if (typeof SH === 'undefined' || !SH) {
  var SH = {};
}

(function($) {
  SH.map = function () {
    var map,
        infoWindow = new google.maps.InfoWindow(),
        _self;

    _self = {
      create: function (options) {
        var settings = {
            mapId: 'map',
            zoom: 12,
            mapTypId: google.maps.MapTypeId.ROADMAP
          };
        console.log(document.getElementById(settings.mapId));

        if (options) {
          $.extend(settings, options);
        }
			  map = new google.maps.Map(document.getElementById(settings.mapId), settings);

      },

      anotherMethod: function () {

      }
    };

    return _self;
  };
})(jQuery);
<commit_msg>Removed console.log.<commit_after>if (typeof SH === 'undefined' || !SH) {
  var SH = {};
}

(function($) {
  SH.map = function () {
    var map,
        infoWindow = new google.maps.InfoWindow(),
        _self;

    _self = {
      create: function (options) {
        var settings = {
            mapId: 'map',
            zoom: 12,
            mapTypId: google.maps.MapTypeId.ROADMAP
          };

        if (options) {
          $.extend(settings, options);
        }
			  map = new google.maps.Map(document.getElementById(settings.mapId), settings);

      },

      anotherMethod: function () {

      }
    };

    return _self;
  };
})(jQuery);
<|endoftext|>


<commit_before>'use strict';

/* globals define, $, app, socket, config */

define('admin/plugins/session-sharing', ['settings'], function (Settings) {
	var ACP = {};

	ACP.init = function () {
		Settings.load('session-sharing', $('.session-sharing-settings'));

		$('#save').on('click', function () {
			Settings.save('session-sharing', $('.session-sharing-settings'), function () {
				app.alert({
					type: 'success',
					alert_id: 'session-sharing-saved',
					title: 'Settings Saved',
					message: 'No restart/reload is required',
					timeout: 5000,
				});
			});
		});

		$('#search').on('keyup', ACP.showUserId);
		$('#remote_search').on('keyup', ACP.findUserByRemoteId);
	};

	ACP.showUserId = function () {
		if (ACP._searchDelay) {
			clearTimeout(ACP._searchDelay);
			delete ACP._searchDelay;
		}

		var element = $(this);

		ACP._searchDelay = setTimeout(function () {
			delete ACP._searchDelay;

			socket.emit('admin.user.search', {
				query: element.val(),
			}, function (err, results) {
				var resultEl = $('#result');

				if (err) {
					return resultEl.text('We encountered an error while servicing this request:' + err.message);
				}

				if (results.users.length) {
					socket.emit('plugins.sessionSharing.showUserIds', {
						uids: results.users.map(function (user) {
							return user.uid;
						}),
					}, function (err, remoteIds) {
						if (err) {
							resultEl.text('We encountered an error while servicing this request:' + err.message);
						} else {
							resultEl.empty();
							results.users.forEach(function (userObj, idx) {
								resultEl.append('
Username: ' + userObj.username + '
NodeBB uid: ' + userObj.uid + '
Remote id: ' + (remoteIds[idx] || 'Not Found'));
							});
						}
					});
				} else {
					resultEl.text('No users matched your query');
				}
			});
		}, 500);
	};

	ACP.findUserByRemoteId = function () {
		if (ACP._searchDelay) {
			clearTimeout(ACP._searchDelay);
			delete ACP._searchDelay;
		}

		var element = $(this);

		ACP._searchDelay = setTimeout(function () {
			delete ACP._searchDelay;

			socket.emit('plugins.sessionSharing.findUserByRemoteId', {
				remoteId: element.val(),
			}, function (err, results) {
				if (!err && results) {
					$('#local_result').html(
						'

' +
						(results.picture ? '' : '
' + results['icon:text'] + '
') +
						'
' +
						'
' + results.username + '

');
				} else {
					$('#local_result').text('No users were found associated with that remote ID');
				}
			});
		}, 500);
	};

	return ACP;
});
<commit_msg>Update admin.user.search to api call<commit_after>'use strict';

/* globals define, $, app, socket, config */

define('admin/plugins/session-sharing', ['settings'], function (Settings) {
	var ACP = {};

	ACP.init = function () {
		Settings.load('session-sharing', $('.session-sharing-settings'));

		$('#save').on('click', function () {
			Settings.save('session-sharing', $('.session-sharing-settings'), function () {
				app.alert({
					type: 'success',
					alert_id: 'session-sharing-saved',
					title: 'Settings Saved',
					message: 'No restart/reload is required',
					timeout: 5000,
				});
			});
		});

		$('#search').on('keyup', ACP.showUserId);
		$('#remote_search').on('keyup', ACP.findUserByRemoteId);
	};

	ACP.showUserId = function () {
		if (ACP._searchDelay) {
			clearTimeout(ACP._searchDelay);
			delete ACP._searchDelay;
		}

		var element = $(this);

		ACP._searchDelay = setTimeout(function () {
			delete ACP._searchDelay;

			var qs = decodeURIComponent($.param({
				query: element.val(),
			}));

			$.get(config.relative_path + '/api/admin/manage/users?' + qs)
				.then(function (results) {
					var resultEl = $('#result');

					if (results.users.length) {
						socket.emit('plugins.sessionSharing.showUserIds', {
							uids: results.users.map(function (user) {
								return user.uid;
							}),
						}, function (err, remoteIds) {
							if (err) {
								resultEl.text('We encountered an error while servicing this request:' + err.message);
							} else {
								resultEl.empty();
								results.users.forEach(function (userObj, idx) {
									resultEl.append('
Username: ' + userObj.username + '
NodeBB uid: ' + userObj.uid + '
Remote id: ' + (remoteIds[idx] || 'Not Found'));
								});
							}
						});
					} else {
						resultEl.text('No users matched your query');
					}
				})
				.fail(function (err) {
					$('#result').text('We encountered an error while servicing this request:' + err.message);
				});
		}, 500);
	};

	ACP.findUserByRemoteId = function () {
		if (ACP._searchDelay) {
			clearTimeout(ACP._searchDelay);
			delete ACP._searchDelay;
		}

		var element = $(this);

		ACP._searchDelay = setTimeout(function () {
			delete ACP._searchDelay;

			socket.emit('plugins.sessionSharing.findUserByRemoteId', {
				remoteId: element.val(),
			}, function (err, results) {
				if (!err && results) {
					$('#local_result').html(
						'

' +
						(results.picture ? '' : '
' + results['icon:text'] + '
') +
						'
' +
						'
' + results.username + '

');
				} else {
					$('#local_result').text('No users were found associated with that remote ID');
				}
			});
		}, 500);
	};

	return ACP;
});
<|endoftext|>