<commit_before>// Basics for testing
var fs = require('fs'),
    assert = require('assert'),
    jsmin = require('../lib/jsmin.sourcemap.js'),
    testFilesDir = __dirname + '/test_files',
    expectedDir = __dirname + '/expected_files';

// Reverse test items
var sourcemap = require('source-map'),
    charProps = require('char-props'),
    SourceMapConsumer = sourcemap.SourceMapConsumer;

// Define all of the commands that a test will use
module.exports = {
  "jQuery": function jQueryPaths () {
    var info = {
      'paths': {'src': 'jquery.js', 'dest': 'jquery.min.js'}
    };
    return info;
  },
  "jQuery and Underscore": function jQueryAnd_Paths () {
    var info = {
      'paths': {'src': ['jquery.js', 'underscore.js'], 'dest': 'jqueryAndUnderscore.min.js'},
      'breaks': [141405]
    };
    return info;
  },
  "Multiple files": function multiPaths () {
    var info = {
      'paths': {'src': ['1.js', '2.js', '3.js'], 'dest': 'multi.js'},
      'breaks': [52, 70]
    };
    return info;
  },
  "minified and sourcemapped (single)": function (info) {
    // Localize the src and dest
    var filePaths = info.paths,
        src = filePaths.src,
        dest = filePaths.dest;

    // Read in the src file
    var singleSrc = fs.readFileSync(testFilesDir + '/' + src, 'utf8'),
        actualSingle = jsmin({'code': singleSrc, 'src': src, 'dest': dest}),
        expectedSingleCode = fs.readFileSync(expectedDir + '/' + dest, 'utf8');

    // Save to the code namespace
    info.code = {
      'input': [{'src': src, 'code': singleSrc}],
      'actual': actualSingle.code,
      'actualMap': actualSingle.sourcemap,
      'expected': expectedSingleCode
    };

    // Return info
    return info;
  },
  "minified and sourcemapped (multi)": function (info) {
    // Localize the src and dest
    var filePaths = info.paths,
        src = filePaths.src,
        dest = filePaths.dest;

    // Read in the src files
    var srcFiles = src.map(function (filepath) {
      var fileSrc = fs.readFileSync(testFilesDir + '/' + filepath, 'utf8'),
          retObj = {
            'code': fileSrc,
            'src': filepath
          };
      return retObj;
    });

    var actualMulti = jsmin({'input': srcFiles, 'dest': dest}),
        expectedMultiCode = fs.readFileSync(expectedDir + '/' + dest, 'utf8');

    // Save to the code namespace
    info.code = {
      'input': srcFiles,
      'actual': actualMulti.code,
      'actualMap': actualMulti.sourcemap,
      'expected': expectedMultiCode
    };

    // Return info
    return info;
  },
  "matches its C-minified counterpart": function (info) {
    var code = info.code,
        paths = info.paths,
        srcPaths = JSON.stringify(paths.src);
    assert.strictEqual(code.actual, code.expected, 'Minified ' + srcPaths + ' does not match ' + paths.dest);
  },
  "mapped against its source": function (info) {
    // Localize code items
    var code = info.code,
        input = code.input,
        actual = code.actual,
        actualMap = code.actualMap;

    // Iterate over the input
    var srcPropMap = {};
    input.forEach(function (item) {
      var src = item.src,
          code = item.code;
      srcPropMap[src] = charProps(code);
    });

    // Generate a consumer and charProps lookups
    info.props = {
      'consumer': new SourceMapConsumer(actualMap),
      'actualProps': charProps(actual),
      'srcPropMap': srcPropMap
    };

    // Return the info
    return info;
  },
  "matches at all positions": function (info) {
    // Localize test items
    var srcCode = info.code.src,
        actualCode = info.code.actual,
        props = info.props,
        consumer = props.consumer,
        actualProps = props.actualProps,
        srcPropMap = props.srcPropMap,
        breaks = info.breaks || [];

    // Iterate over each of the characters
    var i = 0,
        len = actualCode.length;
    for (; i < len; i++) {
      // Look up the position of our index
      var actualPosition = {
        'line': actualProps.lineAt(i) + 1,
        'column': actualProps.columnAt(i)
      };

      // Grab the position of the item and its fil
      var srcPosition = consumer.originalPositionFor(actualPosition),
          srcFile = srcPosition.source;

      // If we have a source file and we are not at a break spot
      var atBreakSpot = breaks.indexOf(i) > -1;
      if (srcFile && !atBreakSpot) {
        // Grab the srcProps for it
        var srcProps = srcPropMap[srcFile];

        // Lookup the character at the src positions
        var srcLine = srcPosition.line - 1,
            srcCol = srcPosition.column,
            srcChar = srcProps.charAt({
              'line': srcLine,
              'column': srcCol
            });

        // Assert that the actual and expected characters are equal
        var actualChar = actualCode.charAt(i);
        assert.strictEqual(actualChar, srcChar, 'The sourcemapped character at index ' + i + ' does not match its original character at line ' + srcLine + ', column ' + srcCol + '.');
      }
    }
  }
};<commit_msg>Added some comments to commands<commit_after>// Basics for testing
var fs = require('fs'),
    assert = require('assert'),
    jsmin = require('../lib/jsmin.sourcemap.js'),
    testFilesDir = __dirname + '/test_files',
    expectedDir = __dirname + '/expected_files';

// Reverse test items
var sourcemap = require('source-map'),
    charProps = require('char-props'),
    SourceMapConsumer = sourcemap.SourceMapConsumer;

// Define all of the commands that a test will use
module.exports = {
  // Set up for jQuery constants
  "jQuery": function jQueryPaths () {
    var info = {
      'paths': {'src': 'jquery.js', 'dest': 'jquery.min.js'}
    };
    return info;
  },
  // Set up for jQuery and _ constants
  "jQuery and Underscore": function jQueryAnd_Paths () {
    var info = {
      'paths': {'src': ['jquery.js', 'underscore.js'], 'dest': 'jqueryAndUnderscore.min.js'},
      'breaks': [141405]
    };
    return info;
  },
  // Set up for multiple file constants
  "Multiple files": function multiPaths () {
    var info = {
      'paths': {'src': ['1.js', '2.js', '3.js'], 'dest': 'multi.js'},
      'breaks': [52, 70]
    };
    return info;
  },
  // Generate minified code and source map (for single file)
  "minified and sourcemapped (single)": function (info) {
    // Localize the src and dest
    var filePaths = info.paths,
        src = filePaths.src,
        dest = filePaths.dest;

    // Read in the src file
    var singleSrc = fs.readFileSync(testFilesDir + '/' + src, 'utf8'),
        actualSingle = jsmin({'code': singleSrc, 'src': src, 'dest': dest}),
        expectedSingleCode = fs.readFileSync(expectedDir + '/' + dest, 'utf8');

    // Save to the code namespace
    info.code = {
      'input': [{'src': src, 'code': singleSrc}],
      'actual': actualSingle.code,
      'actualMap': actualSingle.sourcemap,
      'expected': expectedSingleCode
    };

    // Return info
    return info;
  },
  // Generate minified code and source map (for multiple files)
  "minified and sourcemapped (multi)": function (info) {
    // Localize the src and dest
    var filePaths = info.paths,
        src = filePaths.src,
        dest = filePaths.dest;

    // Read in the src files
    var srcFiles = src.map(function (filepath) {
      var fileSrc = fs.readFileSync(testFilesDir + '/' + filepath, 'utf8'),
          retObj = {
            'code': fileSrc,
            'src': filepath
          };
      return retObj;
    });

    var actualMulti = jsmin({'input': srcFiles, 'dest': dest}),
        expectedMultiCode = fs.readFileSync(expectedDir + '/' + dest, 'utf8');

    // Save to the code namespace
    info.code = {
      'input': srcFiles,
      'actual': actualMulti.code,
      'actualMap': actualMulti.sourcemap,
      'expected': expectedMultiCode
    };

    // Return info
    return info;
  },
  // Compare minified code to gcc'd counterpart
  "matches its C-minified counterpart": function (info) {
    var code = info.code,
        paths = info.paths,
        srcPaths = JSON.stringify(paths.src);
    assert.strictEqual(code.actual, code.expected, 'Minified ' + srcPaths + ' does not match ' + paths.dest);
  },
  // Generate charProps from sourcemap
  "mapped against its source": function (info) {
    // Localize code items
    var code = info.code,
        input = code.input,
        actual = code.actual,
        actualMap = code.actualMap;

    // Iterate over the input
    var srcPropMap = {};
    input.forEach(function (item) {
      var src = item.src,
          code = item.code;
      srcPropMap[src] = charProps(code);
    });

    // Generate a consumer and charProps lookups
    info.props = {
      'consumer': new SourceMapConsumer(actualMap),
      'actualProps': charProps(actual),
      'srcPropMap': srcPropMap
    };

    // Return the info
    return info;
  },
  // Assert that each character reverse maps back to its same character
  "matches at all positions": function (info) {
    // Localize test items
    var srcCode = info.code.src,
        actualCode = info.code.actual,
        props = info.props,
        consumer = props.consumer,
        actualProps = props.actualProps,
        srcPropMap = props.srcPropMap,
        breaks = info.breaks || [];

    // Iterate over each of the characters
    var i = 0,
        len = actualCode.length;
    for (; i < len; i++) {
      // Look up the position of our index
      var actualPosition = {
        'line': actualProps.lineAt(i) + 1,
        'column': actualProps.columnAt(i)
      };

      // Grab the position of the item and its fil
      var srcPosition = consumer.originalPositionFor(actualPosition),
          srcFile = srcPosition.source;

      // If we have a source file and we are not at a break spot
      var atBreakSpot = breaks.indexOf(i) > -1;
      if (srcFile && !atBreakSpot) {
        // Grab the srcProps for it
        var srcProps = srcPropMap[srcFile];

        // Lookup the character at the src positions
        var srcLine = srcPosition.line - 1,
            srcCol = srcPosition.column,
            srcChar = srcProps.charAt({
              'line': srcLine,
              'column': srcCol
            });

        // Assert that the actual and expected characters are equal
        var actualChar = actualCode.charAt(i);
        assert.strictEqual(actualChar, srcChar, 'The sourcemapped character at index ' + i + ' does not match its original character at line ' + srcLine + ', column ' + srcCol + '.');
      }
    }
  }
};<|endoftext|>


<commit_before>/**
 * @license
 * Copyright 2020 Google LLC
 * SPDX-License-Identifier: Apache-2.0
 */

suite('Logic ternary', function() {
  setup(function() {
    sharedTestSetup.call(this);
    this.workspace = new Blockly.Workspace();
    this.block = this.workspace.newBlock('logic_ternary');
  });

  teardown(function() {
    sharedTestTeardown.call(this);
  });

  test('Structure', function() {
    chai.assert.exists(this.block.inputList, 'Has inputList');
    chai.assert.equal(this.block.inputList.length, 3);
    chai.assert.equal(this.block.getInput('IF').connection.check_.length, 1);
    chai.assert.equal(this.block.getInput('IF').connection.check_[0], 'Boolean');
    chai.assert.exists(this.block.onchangeWrapper_, 'Has onchange handler');
  });

  function connectParentAndCheckConnections(
      block, parent, parentInputName, opt_thenInput, opt_elseInput) {
    parent.getInput(parentInputName).connection.connect(block.outputConnection);
    Blockly.Events.fireNow_();  // Force synchronous onchange() call.
    chai.assert.equal(block.getParent(), parent,
        'Successful connection to parent');
    if (opt_thenInput) {
      chai.assert.equal(opt_thenInput.getParent(), block,
          'Input THEN still connected after connecting parent');
    }
    if (opt_elseInput) {
      chai.assert.equal(opt_elseInput.getParent(), block,
          'Input ELSE still connected after connecting parent');
    }
  }
  function connectThenInputAndCheckConnections(
      block, thenInput, opt_elseInput, opt_parent) {
    block.getInput('THEN').connection.connect(thenInput.outputConnection);
    Blockly.Events.fireNow_();  // Force synchronous onchange() call.
    chai.assert.equal(thenInput.getParent(), block, 'THEN is connected');
    if (opt_parent) {
      chai.assert.equal(block.getParent(), opt_parent,
          'Still connected to parent after connecting THEN');
    }
    if (opt_elseInput) {
      chai.assert.equal(opt_elseInput.getParent(), block,
          'Input ELSE still connected after connecting THEN');
    }
  }
  function connectElseInputAndCheckConnections(
      block, elseInput, opt_thenInput, opt_parent) {
    block.getInput('ELSE').connection.connect(elseInput.outputConnection);
    Blockly.Events.fireNow_();  // Force synchronous onchange() call.
    chai.assert.equal(elseInput.getParent(), block, 'ELSE is connected');
    if (opt_parent) {
      chai.assert.equal(block.getParent(), opt_parent,
          'Still connected to parent after connecting ELSE');
    }
    if (opt_thenInput) {
      chai.assert.equal(opt_thenInput.getParent(), block,
          'Input THEN still connected after connecting ELSE');
    }
  }
  function connectInputsAndCheckConnections(
      block, thenInput, elseInput, opt_parent) {
    connectThenInputAndCheckConnections(block, thenInput, null, opt_parent);
    connectElseInputAndCheckConnections(block, elseInput, thenInput, opt_parent);
  }
  suite('No parent', function() {
    test('Attach inputs same type', function() {
      var string1 = this.workspace.newBlock('text');
      var string2 = this.workspace.newBlock('text_charAt');

      connectInputsAndCheckConnections(this.block, string1, string2);
    });
    test('Attach inputs different types', function() {
      var string = this.workspace.newBlock('text');
      var number = this.workspace.newBlock('math_number');

      connectInputsAndCheckConnections(this.block, string, number);
    });
  });
  suite('With parent already attached', function() {
    test('Attach inputs same type with matching parent', function() {
      var parent = this.workspace.newBlock('text_trim');

      connectParentAndCheckConnections(this.block, parent, 'TEXT');

      var string1 = this.workspace.newBlock('text');
      var string2 = this.workspace.newBlock('text_charAt');

      connectInputsAndCheckConnections(this.block, string1, string2, parent);
    });
    test('Attach inputs different types with unchecked parent', function() {
      var parent = this.workspace.newBlock('text_print');

      connectParentAndCheckConnections(this.block, parent, 'TEXT');

      var string = this.workspace.newBlock('text');
      var number = this.workspace.newBlock('math_number');

      connectInputsAndCheckConnections(this.block, string, number, parent);
    });
    test('Attach inputs different types with permissive parent', function() {
      var parent = this.workspace.newBlock('text_length');  // Allows String or Array

      connectParentAndCheckConnections(this.block, parent, 'VALUE');

      var string = this.workspace.newBlock('text');
      var array = this.workspace.newBlock('lists_create_empty');

      connectInputsAndCheckConnections(this.block, string, array, parent);
    });
    test('Attach mismatch type to then causes break with parent', function() {
      var parent = this.workspace.newBlock('text_length');  // Allows String or Array

      connectParentAndCheckConnections(this.block, parent, 'VALUE');

      var string = this.workspace.newBlock('text');
      var number = this.workspace.newBlock('math_number');

      connectElseInputAndCheckConnections(this.block, string, null, parent);

      // Adding mismatching number.
      connectThenInputAndCheckConnections(this.block, number, string);
      chai.assert.equal(this.block.getRootBlock(), this.block,
          'Disconnected from parent');
    });
    test('Attach mismatch type to else causes break with parent', function() {
      var parent = this.workspace.newBlock('text_length');  // Allows String or Array

      connectParentAndCheckConnections(this.block, parent, 'VALUE');

      var string = this.workspace.newBlock('text');
      var number = this.workspace.newBlock('math_number');

      connectThenInputAndCheckConnections(this.block, string, null, parent);

      // Adding mismatching number.
      connectElseInputAndCheckConnections(this.block, number, string);
      chai.assert.equal(this.block.getRootBlock(), this.block,
          'Disconnected from parent');
    });
  });
  suite('Attaching parent after inputs', function() {
    test('Unchecked parent with inputs different types', function() {
      var string = this.workspace.newBlock('text');
      var number = this.workspace.newBlock('math_number');

      connectInputsAndCheckConnections(this.block, string, number);

      var parent = this.workspace.newBlock('text_print');
      connectParentAndCheckConnections(
          this.block, parent, 'TEXT', string, number);
    });
    test('Permissive parent with inputs different types', function() {
      var string = this.workspace.newBlock('text');
      var array = this.workspace.newBlock('lists_create_empty');

      connectInputsAndCheckConnections(this.block, string, array);

      var parent = this.workspace.newBlock('text_print');
      connectParentAndCheckConnections(
          this.block, parent, 'TEXT', string, array);
    });
    test('Mismatch with then causes break with then', function() {
      var number = this.workspace.newBlock('math_number');
      var string = this.workspace.newBlock('text');

      connectInputsAndCheckConnections(this.block, number, string);

      var parent = this.workspace.newBlock('text_trim');
      connectParentAndCheckConnections(
          this.block, parent, 'TEXT', null, string);
      chai.assert.equal(number.getRootBlock(), number,
          'Input THEN disconnected');
    });
    test('Mismatch with else causes break with else', function() {
      var string = this.workspace.newBlock('text');
      var number = this.workspace.newBlock('math_number');

      connectInputsAndCheckConnections(this.block, string, number);

      var parent = this.workspace.newBlock('text_trim');
      connectParentAndCheckConnections(this.block, parent, 'TEXT', string);
      chai.assert.equal(number.getRootBlock(), number,
          'Input ELSE disconnected');
    });
  });
});
<commit_msg>Add tests to logic ternary block (#4427)<commit_after>/**
 * @license
 * Copyright 2020 Google LLC
 * SPDX-License-Identifier: Apache-2.0
 */

suite('Logic ternary', function() {
  setup(function() {
    sharedTestSetup.call(this);
    this.workspace = new Blockly.Workspace();
  });

  teardown(function() {
    sharedTestTeardown.call(this);
  });

  /**
   * Asserts that the logic ternary block has the expected inputs and fields.
   * @param {!Blockly.Block} block The block to check.
   * @param {boolean=} inputsInline Whether the inputs are expected to be
   *    inline.
   */
  function assertBlockStructure(block, inputsInline = false) {
    chai.assert.equal(block.type, 'logic_ternary');
    const inputs = block.inputList;
    chai.assert.exists(inputs, 'Has inputList');
    chai.assert.lengthOf(inputs, 3);
    const ifInput = block.getInput('IF');
    chai.assert.exists(ifInput, 'Has "IF" input');
    chai.assert.equal(ifInput.connection.check_.length, 1);
    chai.assert.equal(ifInput.connection.check_[0], 'Boolean');
    chai.assert.exists(block.onchangeWrapper_, 'Has onchange handler');
    if (inputsInline) {
      chai.assert.isTrue(block.inputsInline);
    } else {
      // inputsInline can be undefined
      chai.assert.isNotTrue(block.inputsInline);
    }
  }

  test('Structure', function() {
    const block = this.workspace.newBlock('logic_ternary');
    assertBlockStructure(block);
  });

  /**
   * Test cases for serialization tests.
   * @type {Array<SerializationTestCase>}
   */
  const testCases = [
    {title: 'Empty XML', xml: '<block type="logic_ternary"/>',
      expectedXml:
          '<block xmlns="https://developers.google.com/blockly/xml" ' +
          'type="logic_ternary" id="1"></block>',
      assertBlockStructure:
          (block) => {
            assertBlockStructure(block);
          },
    },
    {title: 'Inputs inline',
      xml:
          '<block xmlns="https://developers.google.com/blockly/xml" ' +
          'type="logic_ternary" id="1" inline="true"></block>',
      assertBlockStructure:
          (block) => {
            assertBlockStructure(block, true);
          },
    },
  ];
  testHelpers.runSerializationTestSuite(testCases);

  suite('Connections', function() {
    function connectParentAndCheckConnections(
        block, parent, parentInputName, opt_thenInput, opt_elseInput) {
      parent.getInput(parentInputName).connection.connect(block.outputConnection);
      Blockly.Events.fireNow_();  // Force synchronous onchange() call.
      chai.assert.equal(block.getParent(), parent,
          'Successful connection to parent');
      if (opt_thenInput) {
        chai.assert.equal(opt_thenInput.getParent(), block,
            'Input THEN still connected after connecting parent');
      }
      if (opt_elseInput) {
        chai.assert.equal(opt_elseInput.getParent(), block,
            'Input ELSE still connected after connecting parent');
      }
    }
    function connectThenInputAndCheckConnections(
        block, thenInput, opt_elseInput, opt_parent) {
      block.getInput('THEN').connection.connect(thenInput.outputConnection);
      Blockly.Events.fireNow_();  // Force synchronous onchange() call.
      chai.assert.equal(thenInput.getParent(), block, 'THEN is connected');
      if (opt_parent) {
        chai.assert.equal(block.getParent(), opt_parent,
            'Still connected to parent after connecting THEN');
      }
      if (opt_elseInput) {
        chai.assert.equal(opt_elseInput.getParent(), block,
            'Input ELSE still connected after connecting THEN');
      }
    }
    function connectElseInputAndCheckConnections(
        block, elseInput, opt_thenInput, opt_parent) {
      block.getInput('ELSE').connection.connect(elseInput.outputConnection);
      Blockly.Events.fireNow_();  // Force synchronous onchange() call.
      chai.assert.equal(elseInput.getParent(), block, 'ELSE is connected');
      if (opt_parent) {
        chai.assert.equal(block.getParent(), opt_parent,
            'Still connected to parent after connecting ELSE');
      }
      if (opt_thenInput) {
        chai.assert.equal(opt_thenInput.getParent(), block,
            'Input THEN still connected after connecting ELSE');
      }
    }
    function connectInputsAndCheckConnections(
        block, thenInput, elseInput, opt_parent) {
      connectThenInputAndCheckConnections(block, thenInput, null, opt_parent);
      connectElseInputAndCheckConnections(block, elseInput, thenInput, opt_parent);
    }
    setup(function() {
      this.block = this.workspace.newBlock('logic_ternary');
    });
    suite('No parent', function() {
      test('Attach inputs same type', function() {
        var string1 = this.workspace.newBlock('text');
        var string2 = this.workspace.newBlock('text_charAt');

        connectInputsAndCheckConnections(this.block, string1, string2);
      });
      test('Attach inputs different types', function() {
        var string = this.workspace.newBlock('text');
        var number = this.workspace.newBlock('math_number');

        connectInputsAndCheckConnections(this.block, string, number);
      });
    });
    suite('With parent already attached', function() {
      test('Attach inputs same type with matching parent', function() {
        var parent = this.workspace.newBlock('text_trim');

        connectParentAndCheckConnections(this.block, parent, 'TEXT');

        var string1 = this.workspace.newBlock('text');
        var string2 = this.workspace.newBlock('text_charAt');

        connectInputsAndCheckConnections(this.block, string1, string2, parent);
      });
      test('Attach inputs different types with unchecked parent', function() {
        var parent = this.workspace.newBlock('text_print');

        connectParentAndCheckConnections(this.block, parent, 'TEXT');

        var string = this.workspace.newBlock('text');
        var number = this.workspace.newBlock('math_number');

        connectInputsAndCheckConnections(this.block, string, number, parent);
      });
      test('Attach inputs different types with permissive parent', function() {
        var parent = this.workspace.newBlock('text_length');  // Allows String or Array

        connectParentAndCheckConnections(this.block, parent, 'VALUE');

        var string = this.workspace.newBlock('text');
        var array = this.workspace.newBlock('lists_create_empty');

        connectInputsAndCheckConnections(this.block, string, array, parent);
      });
      test('Attach mismatch type to then causes break with parent', function() {
        var parent = this.workspace.newBlock('text_length');  // Allows String or Array

        connectParentAndCheckConnections(this.block, parent, 'VALUE');

        var string = this.workspace.newBlock('text');
        var number = this.workspace.newBlock('math_number');

        connectElseInputAndCheckConnections(this.block, string, null, parent);

        // Adding mismatching number.
        connectThenInputAndCheckConnections(this.block, number, string);
        chai.assert.equal(this.block.getRootBlock(), this.block,
            'Disconnected from parent');
      });
      test('Attach mismatch type to else causes break with parent', function() {
        var parent = this.workspace.newBlock('text_length');  // Allows String or Array

        connectParentAndCheckConnections(this.block, parent, 'VALUE');

        var string = this.workspace.newBlock('text');
        var number = this.workspace.newBlock('math_number');

        connectThenInputAndCheckConnections(this.block, string, null, parent);

        // Adding mismatching number.
        connectElseInputAndCheckConnections(this.block, number, string);
        chai.assert.equal(this.block.getRootBlock(), this.block,
            'Disconnected from parent');
      });
    });
    suite('Attaching parent after inputs', function() {
      test('Unchecked parent with inputs different types', function() {
        var string = this.workspace.newBlock('text');
        var number = this.workspace.newBlock('math_number');

        connectInputsAndCheckConnections(this.block, string, number);

        var parent = this.workspace.newBlock('text_print');
        connectParentAndCheckConnections(
            this.block, parent, 'TEXT', string, number);
      });
      test('Permissive parent with inputs different types', function() {
        var string = this.workspace.newBlock('text');
        var array = this.workspace.newBlock('lists_create_empty');

        connectInputsAndCheckConnections(this.block, string, array);

        var parent = this.workspace.newBlock('text_print');
        connectParentAndCheckConnections(
            this.block, parent, 'TEXT', string, array);
      });
      test('Mismatch with then causes break with then', function() {
        var number = this.workspace.newBlock('math_number');
        var string = this.workspace.newBlock('text');

        connectInputsAndCheckConnections(this.block, number, string);

        var parent = this.workspace.newBlock('text_trim');
        connectParentAndCheckConnections(
            this.block, parent, 'TEXT', null, string);
        chai.assert.equal(number.getRootBlock(), number,
            'Input THEN disconnected');
      });
      test('Mismatch with else causes break with else', function() {
        var string = this.workspace.newBlock('text');
        var number = this.workspace.newBlock('math_number');

        connectInputsAndCheckConnections(this.block, string, number);

        var parent = this.workspace.newBlock('text_trim');
        connectParentAndCheckConnections(this.block, parent, 'TEXT', string);
        chai.assert.equal(number.getRootBlock(), number,
            'Input ELSE disconnected');
      });
    });
  });
});
<|endoftext|>


<commit_before>/* global LIMITS AUTH_CONFIG */
import assets from '../common/assets';
import { getFileList, setFileList } from './api';
import { encryptStream, decryptStream } from './ece';
import { arrayToB64, b64ToArray, streamToArrayBuffer } from './utils';
import { blobStream } from './streams';
import { getFileListKey, prepareScopedBundleKey, preparePkce } from './fxa';
import storage from './storage';

const textEncoder = new TextEncoder();
const textDecoder = new TextDecoder();

export default class User {
  constructor(storage) {
    this.storage = storage;
    this.data = storage.user || {};
  }

  get avatar() {
    const defaultAvatar = assets.get('user.svg');
    if (this.data.avatarDefault) {
      return assets.get('firefox_logo-only.svg');
    }
    return this.data.avatar || defaultAvatar;
  }

  get name() {
    return this.data.displayName;
  }

  get email() {
    return this.data.email;
  }

  get loggedIn() {
    return !!this.data.access_token;
  }

  get bearerToken() {
    return this.data.access_token;
  }

  get maxSize() {
    return this.loggedIn ? LIMITS.MAX_FILE_SIZE : LIMITS.ANON.MAX_FILE_SIZE;
  }

  get maxExpireSeconds() {
    return this.loggedIn
      ? LIMITS.MAX_EXPIRE_SECONDS
      : LIMITS.ANON.MAX_EXPIRE_SECONDS;
  }

  get maxDownloads() {
    return this.loggedIn ? LIMITS.MAX_DOWNLOADS : LIMITS.ANON.MAX_DOWNLOADS;
  }

  async login(email) {
    const state = arrayToB64(crypto.getRandomValues(new Uint8Array(16)));
    storage.set('oauthState', state);
    const keys_jwk = await prepareScopedBundleKey(this.storage);
    const code_challenge = await preparePkce(this.storage);
    const options = {
      client_id: AUTH_CONFIG.client_id,
      code_challenge,
      code_challenge_method: 'S256',
      response_type: 'code',
      scope: 'profile https://identity.mozilla.com/apps/send', //TODO param
      state,
      keys_jwk
    };
    if (email) {
      options.email = email;
    }
    const params = new URLSearchParams(options);
    location.assign(
      `${AUTH_CONFIG.authorization_endpoint}?${params.toString()}`
    );
  }

  async finishLogin(code, state) {
    const localState = storage.get('oauthState');
    storage.remove('oauthState');
    if (state !== localState) {
      throw new Error('state mismatch');
    }
    const tokenResponse = await fetch(AUTH_CONFIG.token_endpoint, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({
        code,
        client_id: AUTH_CONFIG.client_id,
        code_verifier: this.storage.get('pkceVerifier')
      })
    });
    const auth = await tokenResponse.json();
    const infoResponse = await fetch(AUTH_CONFIG.userinfo_endpoint, {
      method: 'GET',
      headers: {
        Authorization: `Bearer ${auth.access_token}`
      }
    });
    const userInfo = await infoResponse.json();
    userInfo.access_token = auth.access_token;
    userInfo.fileListKey = await getFileListKey(this.storage, auth.keys_jwe);
    this.storage.user = userInfo;
    this.data = userInfo;
    this.storage.remove('pkceVerifier');
  }

  logout() {
    this.storage.user = null;
    this.storage.clearLocalFiles();
    this.data = {};
  }

  async syncFileList() {
    let changes = { incoming: false, outgoing: false, downloadCount: false };
    if (!this.loggedIn) {
      return this.storage.merge();
    }
    let list = [];
    try {
      const encrypted = await getFileList(this.bearerToken);
      const decrypted = await streamToArrayBuffer(
        decryptStream(encrypted, b64ToArray(this.data.fileListKey))
      );
      list = JSON.parse(textDecoder.decode(decrypted));
    } catch (e) {
      //
    }
    changes = await this.storage.merge(list);
    if (!changes.outgoing) {
      return changes;
    }
    try {
      const blob = new Blob([
        textEncoder.encode(JSON.stringify(this.storage.files))
      ]);
      const encrypted = await streamToArrayBuffer(
        encryptStream(blobStream(blob), b64ToArray(this.data.fileListKey))
      );
      await setFileList(this.bearerToken, encrypted);
    } catch (e) {
      //
    }
    return changes;
  }

  toJSON() {
    return this.data;
  }
}
<commit_msg>log out when the auth token is expired<commit_after>/* global LIMITS AUTH_CONFIG */
import assets from '../common/assets';
import { getFileList, setFileList } from './api';
import { encryptStream, decryptStream } from './ece';
import { arrayToB64, b64ToArray, streamToArrayBuffer } from './utils';
import { blobStream } from './streams';
import { getFileListKey, prepareScopedBundleKey, preparePkce } from './fxa';
import storage from './storage';

const textEncoder = new TextEncoder();
const textDecoder = new TextDecoder();

export default class User {
  constructor(storage) {
    this.storage = storage;
    this.data = storage.user || {};
  }

  get avatar() {
    const defaultAvatar = assets.get('user.svg');
    if (this.data.avatarDefault) {
      return assets.get('firefox_logo-only.svg');
    }
    return this.data.avatar || defaultAvatar;
  }

  get name() {
    return this.data.displayName;
  }

  get email() {
    return this.data.email;
  }

  get loggedIn() {
    return !!this.data.access_token;
  }

  get bearerToken() {
    return this.data.access_token;
  }

  get maxSize() {
    return this.loggedIn ? LIMITS.MAX_FILE_SIZE : LIMITS.ANON.MAX_FILE_SIZE;
  }

  get maxExpireSeconds() {
    return this.loggedIn
      ? LIMITS.MAX_EXPIRE_SECONDS
      : LIMITS.ANON.MAX_EXPIRE_SECONDS;
  }

  get maxDownloads() {
    return this.loggedIn ? LIMITS.MAX_DOWNLOADS : LIMITS.ANON.MAX_DOWNLOADS;
  }

  async login(email) {
    const state = arrayToB64(crypto.getRandomValues(new Uint8Array(16)));
    storage.set('oauthState', state);
    const keys_jwk = await prepareScopedBundleKey(this.storage);
    const code_challenge = await preparePkce(this.storage);
    const options = {
      client_id: AUTH_CONFIG.client_id,
      code_challenge,
      code_challenge_method: 'S256',
      response_type: 'code',
      scope: 'profile https://identity.mozilla.com/apps/send', //TODO param
      state,
      keys_jwk
    };
    if (email) {
      options.email = email;
    }
    const params = new URLSearchParams(options);
    location.assign(
      `${AUTH_CONFIG.authorization_endpoint}?${params.toString()}`
    );
  }

  async finishLogin(code, state) {
    const localState = storage.get('oauthState');
    storage.remove('oauthState');
    if (state !== localState) {
      throw new Error('state mismatch');
    }
    const tokenResponse = await fetch(AUTH_CONFIG.token_endpoint, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({
        code,
        client_id: AUTH_CONFIG.client_id,
        code_verifier: this.storage.get('pkceVerifier')
      })
    });
    const auth = await tokenResponse.json();
    const infoResponse = await fetch(AUTH_CONFIG.userinfo_endpoint, {
      method: 'GET',
      headers: {
        Authorization: `Bearer ${auth.access_token}`
      }
    });
    const userInfo = await infoResponse.json();
    userInfo.access_token = auth.access_token;
    userInfo.fileListKey = await getFileListKey(this.storage, auth.keys_jwe);
    this.storage.user = userInfo;
    this.data = userInfo;
    this.storage.remove('pkceVerifier');
  }

  logout() {
    this.storage.user = null;
    this.storage.clearLocalFiles();
    this.data = {};
  }

  async syncFileList() {
    let changes = { incoming: false, outgoing: false, downloadCount: false };
    if (!this.loggedIn) {
      return this.storage.merge();
    }
    let list = [];
    try {
      const encrypted = await getFileList(this.bearerToken);
      const decrypted = await streamToArrayBuffer(
        decryptStream(encrypted, b64ToArray(this.data.fileListKey))
      );
      list = JSON.parse(textDecoder.decode(decrypted));
    } catch (e) {
      if (e.message === '401') {
        return this.logout();
      }
    }
    changes = await this.storage.merge(list);
    if (!changes.outgoing) {
      return changes;
    }
    try {
      const blob = new Blob([
        textEncoder.encode(JSON.stringify(this.storage.files))
      ]);
      const encrypted = await streamToArrayBuffer(
        encryptStream(blobStream(blob), b64ToArray(this.data.fileListKey))
      );
      await setFileList(this.bearerToken, encrypted);
    } catch (e) {
      //
    }
    return changes;
  }

  toJSON() {
    return this.data;
  }
}
<|endoftext|>


<commit_before>var tcpEventify = require('../../utils/tcpEventify'),
    net = require('net'),
    fs = require('fs'),
    basepath = __dirname + '/../../sockets/';

/** Creates a simple global mapper that routes every event to its listeners
 * @param {string} name - The name of the map
 * @returns {object} map - The global map created
 */
module.exports = function (name) {
    var map = {
        name: name || 'globalNamespace',
        sockets: [],
        events: {},     // A set of the emitted events
        trees: {}       // A set of arrays of listeners of events
    };
    var path = basepath + name + '.sock';

    // Check if a UNIX socket has been created previously and unlink it
    if (fs.existsSync(path)) {
        fs.unlinkSync(path);
    }

    // Create a UNIX socket server
    map.server = net.createServer(map.onConnection = function (unixSocket) {
        // Store the socket for further event mapping
        var socket = tcpEventify(unixSocket);
        map.sockets.push(socket);

        // Subscribe a listener to a particular event
        socket.on('on', function (eventName) {
            if (!(map.trees[eventName] instanceof Array)) {
                map.trees[eventName] = [];
            }
            map.trees[eventName].push(socket);
        });

        // Map emitted events to all listeners by sending the event through their corresponding sockets
        socket.on('event', function (event) {
            var eventName = event.name;
            // Register the event if this is the first time it is emitted
            if (!map.events[eventName]) {
                map.events[eventName] = socket;
            }

            // If there are any registered listeners to this particular event:
            var listeningSockets = map.trees[eventName];
            if (listeningSockets) {
                listeningSockets.forEach(function (listener) {
                    if (typeof listener === 'object' && listener !== socket &&
                        typeof listener.send === 'function') {
                        listener.send(eventName, event.message);
                    }
                });
            }
        });
    }).listen(path, function () {
        console.log('Unix socket created: ' + name + '.sock');
    });

    // Map emitted events to all listeners by emitting the event on each of them
    map.emit = function () {

    };

    // Register a listener to a particular event
    map.on = function () {

    };

    return map;
}<commit_msg>Added some comments.<commit_after>/* NODE MODULES */
var tcpEventify = require('../../utils/tcpEventify'),
    net = require('net'),
    fs = require('fs'),
    basepath = __dirname + '/../../sockets/';

/** Creates a simple global mapper that routes every event to its listeners using unix sockets
 * @param {string} name - The name of the map
 * @returns {object} map - The global map created
 */
module.exports = function (name) {
    var map = {
        name: name || 'globalNamespace',
        sockets: [],
        events: {},     // A set of the emitted events
        trees: {}       // A set of arrays of listeners of events
    };
    var path = basepath + name + '.sock';

    // Check if a UNIX socket has been created previously and unlink it
    if (fs.existsSync(path)) {
        fs.unlinkSync(path);
    }

    // Create a UNIX socket server
    map.server = net.createServer(map.onConnection = function (unixSocket) {
        // Store the socket for further event mapping
        var socket = tcpEventify(unixSocket);
        map.sockets.push(socket);

        // Subscribe a listener to a particular event
        socket.on('on', function (eventName) {
            if (!(map.trees[eventName] instanceof Array)) {
                map.trees[eventName] = [];
            }
            map.trees[eventName].push(socket);
        });

        // Map emitted events to all listeners by sending the event through their corresponding sockets
        socket.on('event', function (event) {
            var eventName = event.name;
            // Register the event if this is the first time it is emitted
            if (!map.events[eventName]) {
                map.events[eventName] = socket;
            }

            // If there are any registered listeners to this particular event:
            var listeningSockets = map.trees[eventName];
            if (listeningSockets) {
                listeningSockets.forEach(function (listener) {
                    if (typeof listener === 'object' && listener !== socket &&
                        typeof listener.send === 'function') {
                        listener.send(eventName, event.message);
                    }
                });
            }
        });
    }).listen(path, function () {
        console.log('Unix socket created: ' + name + '.sock');
    });

    // Map emitted events to all listeners by emitting the event on each of them
    map.emit = function () {

    };

    // Register a listener to a particular event
    map.on = function () {

    };

    return map;
}<|endoftext|>


<commit_before>// ==UserScript==
// @name        userAgent
// @name:zh-CN
// @namespace   https://github.com/dodying/Dodying-UserJs
// @description:zh-CN
// @include     *
// @version     1
// @grant       none
// @author      Dodying
// @namespace   https://github.com/dodying/Dodying-UserJs
// @supportURL  https://github.com/dodying/Dodying-UserJs/issues
// @icon        https://raw.githubusercontent.com/dodying/UserJs/master/Logo.png
// @run-at      document-end
// ==/UserScript==
(function () {
if (/Android|iPhone|iPad/i.test(navigator.userAgent)) { //mobile 待续
    alert(1)
  }
if (/mobile/.test(navigator.userAgent)) { //mobile 待续
    alert(2)
  }
}) ();
<commit_msg>Delete userAgent.user.js<commit_after><|endoftext|>


<commit_before>/**
 * Gruntfile
 *
 * This Node script is executed when you run `grunt` or `sails lift`.
 * It's purpose is to load the Grunt tasks in your project's `tasks`
 * folder, and allow you to add and remove tasks as you see fit.
 * For more information on how this works, check out the `README.md`
 * file that was generated in your `tasks` folder.
 *
 * WARNING:
 * Unless you know what you're doing, you shouldn't change this file.
 * Check out the `tasks` directory instead.
 */

module.exports = function(grunt) {

	/**
	 * Loads Grunt configuration modules from the specified
	 * relative path. These modules should export a function
	 * that, when run, should either load/configure or register
	 * a Grunt task.
	 */
	function loadTasks(relPath) {
		return includeAll({
			dirname: require('path').resolve(__dirname, relPath),
			filter: /(.+)\.js$/
		}) || {};
	}

	/**
	 * Invokes the function from a Grunt configuration module with
	 * a single argument - the `grunt` object.
	 */
	function invokeConfigFn(tasks) {
		for (var taskName in tasks) {
			if (tasks.hasOwnProperty(taskName)) {
				tasks[taskName](grunt);
			}
		}
	}

	// Load the include-all library in order to require all of our grunt
	// configurations and task registrations dynamically.
	var includeAll;
	try {
		includeAll = require('include-all');
	} catch (e0) {
		try {
			includeAll = require('sails/node-modules/include-all');
		}
		catch(e1) {
			console.error('Grunt:\nCould not find `include-all` module.');
			console.error('Skipping grunt tasks...');
			console.error('To fix this, please run:');
			console.error('npm install include-all --save`');
			console.error();

			grunt.registerTask('default', []);
			return;
		}
	}

	// Load task functions
	var taskConfigurations = loadTasks('./tasks/config'),
		registerDefinitions = loadTasks('./tasks/register');

	// (ensure that a default task exists)
	if (!registerDefinitions.default) {
		registerDefinitions.default = function (grunt) { grunt.registerTask('default', []); };
	}

	// Run task functions to configure Grunt.
	invokeConfigFn(taskConfigurations);
	invokeConfigFn(registerDefinitions);

};
<commit_msg>Fix require path.<commit_after>/**
 * Gruntfile
 *
 * This Node script is executed when you run `grunt` or `sails lift`.
 * It's purpose is to load the Grunt tasks in your project's `tasks`
 * folder, and allow you to add and remove tasks as you see fit.
 * For more information on how this works, check out the `README.md`
 * file that was generated in your `tasks` folder.
 *
 * WARNING:
 * Unless you know what you're doing, you shouldn't change this file.
 * Check out the `tasks` directory instead.
 */

module.exports = function(grunt) {

	// Load the include-all library in order to require all of our grunt
	// configurations and task registrations dynamically.
	var includeAll;
	try {
		includeAll = require('include-all');
	} catch (e0) {
		try {
			includeAll = require('sails/node_modules/include-all');
		}
		catch(e1) {
			console.error('Could not find `include-all` module.');
			console.error('Skipping grunt tasks...');
			console.error('To fix this, please run:');
			console.error('npm install include-all --save`');
			console.error();

			grunt.registerTask('default', []);
			return;
		}
	}

	/**
	 * Loads Grunt configuration modules from the specified
	 * relative path. These modules should export a function
	 * that, when run, should either load/configure or register
	 * a Grunt task.
	 */
	function loadTasks(relPath) {
		return includeAll({
			dirname: require('path').resolve(__dirname, relPath),
			filter: /(.+)\.js$/
		}) || {};
	}

	/**
	 * Invokes the function from a Grunt configuration module with
	 * a single argument - the `grunt` object.
	 */
	function invokeConfigFn(tasks) {
		for (var taskName in tasks) {
			if (tasks.hasOwnProperty(taskName)) {
				tasks[taskName](grunt);
			}
		}
	}

	// Load task functions
	var taskConfigurations = loadTasks('./tasks/config'),
		registerDefinitions = loadTasks('./tasks/register');

	// (ensure that a default task exists)
	if (!registerDefinitions.default) {
		registerDefinitions.default = function (grunt) { grunt.registerTask('default', []); };
	}

	// Run task functions to configure Grunt.
	invokeConfigFn(taskConfigurations);
	invokeConfigFn(registerDefinitions);

};
<|endoftext|>


<commit_before>
// override so all errors thrown will be reported instead
// of hidden during development
SC.THROW_ALL_ERRORS = true;

Console = SC.Application.create(
  /** @scope Console.prototype */ {

  NAMESPACE: 'Console',
  VERSION: 'WHOTHEFUCKCARES'

});
<commit_msg>ahem.<commit_after>
// override so all errors thrown will be reported instead
// of hidden during development
SC.THROW_ALL_ERRORS = true;

Console = SC.Application.create(
  /** @scope Console.prototype */ {

  NAMESPACE: 'Console',
  VERSION: '1.0'

});
<|endoftext|>


<commit_before>/*
 * Copyright (c) 2014 ThoughtWorks, Inc.
 *
 * Pixelated is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Pixelated is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with Pixelated. If not, see <http://www.gnu.org/licenses/>.
 */
define(
  [
    'flight/lib/component',
    'mail_view/data/mail_builder',
    'page/events',
    'helpers/monitored_ajax',
    'features'
  ],
  function (defineComponent, mailBuilder, events, monitoredAjax, features) {
    'use strict';

    return defineComponent(mailSender);

    function mailSender() {
      function successSendingMail(on){
        return function(result) {
          on.trigger(document, events.mail.sent, result);
        };
      }

      function failureSendingMail(on) {
        return function(result) {
          on.trigger(document, events.mail.send_failed);
        };
      }

      function successSaveDraft(on){
        return function(result){
          on.trigger(document, events.mail.draftSaved, result);
        };
      }

      this.defaultAttrs({
        mailsResource: '/mails'
      });

      this.sendMail = function(event, data) {
        monitoredAjax.call(_, this, this.attr.mailsResource, {
          type: 'POST',
          dataType: 'json',
          contentType: 'application/json; charset=utf-8',
          data: JSON.stringify(data),
        }).done(successSendingMail(this)).fail(failureSendingMail(this));

      };

      this.saveMail = function(mail) {
        return monitoredAjax.call(_, this, this.attr.mailsResource, {
          type: 'PUT',
          dataType: 'json',
          contentType: 'application/json; charset=utf-8',
          data: JSON.stringify(mail),
          skipLoadingWarning: true,
          skipErrorMessage: true
        });
      };

      this.saveDraft = function(event, data) {
        this.saveMail(data)
          .done(successSaveDraft(this));
      };

      this.saveMailWithCallback = function(event, data) {
        this.saveMail(data.mail)
          .done(function(result) { return data.callback(result); })
          .fail(function(result) { return data.callback(result); });
      };

      this.after('initialize', function () {
        this.on(events.mail.send, this.sendMail);
        if(features.isEnabled('saveDraft')) {
          this.on(events.mail.saveDraft, this.saveDraft);
        }
        this.on(document, events.mail.save, this.saveMailWithCallback);
      });
    }
  });
<commit_msg>remove trailing comma<commit_after>/*
 * Copyright (c) 2014 ThoughtWorks, Inc.
 *
 * Pixelated is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Pixelated is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with Pixelated. If not, see <http://www.gnu.org/licenses/>.
 */
define(
  [
    'flight/lib/component',
    'mail_view/data/mail_builder',
    'page/events',
    'helpers/monitored_ajax',
    'features'
  ],
  function (defineComponent, mailBuilder, events, monitoredAjax, features) {
    'use strict';

    return defineComponent(mailSender);

    function mailSender() {
      function successSendingMail(on){
        return function(result) {
          on.trigger(document, events.mail.sent, result);
        };
      }

      function failureSendingMail(on) {
        return function(result) {
          on.trigger(document, events.mail.send_failed);
        };
      }

      function successSaveDraft(on){
        return function(result){
          on.trigger(document, events.mail.draftSaved, result);
        };
      }

      this.defaultAttrs({
        mailsResource: '/mails'
      });

      this.sendMail = function(event, data) {
        monitoredAjax.call(_, this, this.attr.mailsResource, {
          type: 'POST',
          dataType: 'json',
          contentType: 'application/json; charset=utf-8',
          data: JSON.stringify(data)
        }).done(successSendingMail(this)).fail(failureSendingMail(this));

      };

      this.saveMail = function(mail) {
        return monitoredAjax.call(_, this, this.attr.mailsResource, {
          type: 'PUT',
          dataType: 'json',
          contentType: 'application/json; charset=utf-8',
          data: JSON.stringify(mail),
          skipLoadingWarning: true,
          skipErrorMessage: true
        });
      };

      this.saveDraft = function(event, data) {
        this.saveMail(data)
          .done(successSaveDraft(this));
      };

      this.saveMailWithCallback = function(event, data) {
        this.saveMail(data.mail)
          .done(function(result) { return data.callback(result); })
          .fail(function(result) { return data.callback(result); });
      };

      this.after('initialize', function () {
        this.on(events.mail.send, this.sendMail);
        if(features.isEnabled('saveDraft')) {
          this.on(events.mail.saveDraft, this.saveDraft);
        }
        this.on(document, events.mail.save, this.saveMailWithCallback);
      });
    }
  });
<|endoftext|>


<commit_before>//= require jquery.scrollTo

window.FactRelationsView = Backbone.View.extend({
  tagName: "div",
  className: "page evidence-list fact-relations-container",

  template: 'fact_relations/fact_relations',

  initialize: function(options) {
    this._views = [];
    this._loading = true;

    this.collection.bind('add', this.addFactRelation, this);
    this.collection.bind('remove', this.removeFactRelation, this);
    this.collection.bind('reset', this.resetFactRelations, this);

    this.showNoEvidenceMessage();

    this.initializeSearchView();
  },

  initializeSearchView: function() {
    this.factRelationSearchView = new FactRelationSearchView({
      factRelations: this.collection,
      type: this.options.type
    });
  },

  highlightFactRelation: function(view) {
    $( 'ul.evidence-listing', this.el)
      .scrollTo(view.el, 800);

    view.highlight();
  },

  addFactRelation: function(factRelation, factRelations, options) {
    if ( !options ) {
      options = {};
    }

    var factRelationView = new FactRelationView({model: factRelation});

    this.hideNoEvidenceMessage();

    this._views.push(factRelationView);

    factRelationView.render();
    this.$el.find('ul.evidence-listing').append(factRelationView.el);

    if ( options.highlight ) {
      this.highlightFactRelation(factRelationView);
    }
  },

  resetFactRelations: function() {
    var self = this;

    this.collection.forEach(function(factRelation) {
      self.addFactRelation(factRelation);
    });

    if ( this.collection.length === 0 ) {
      this.showNoEvidenceMessage();
    }
  },

  removeFactRelation: function(factRelation) {
    console.info( "Removing one FactRelation" );
  },

  render: function() {
    this.$el
      .html(this.templateRender())
      .prepend(this.factRelationSearchView.render().el);

    return this;
  },

  hide: function() {
    this.$el.hide();
  },

  show: function() {
    this.$el.show();
  },

  showAndFetch: function() {
    var self = this;

    if ( ! this._fetched ) {
      this._fetched = true;

      this.collection.reset();

      this.showLoadingIndicator();

      this.collection.fetch({
        success: function() {
          self.hideLoadingIndicator();
        }
      });
    }

    this.$el.show();
  },

  showLoadingIndicator: function() {
    this.$el.find('.loading-evidence').show();
    this.hideNoEvidenceMessage();
  },

  hideLoadingIndicator: function() {
    this.$el.find('.loading-evidence').hide();
  },

  showNoEvidenceMessage: function() {
    this.$el.find('.no-evidence-message').show();
    this.hideLoadingIndicator();
  },

  hideNoEvidenceMessage: function() {
    this.$el.find('.no-evidence-message').hide();
  }
});
_.extend(FactRelationsView.prototype, TemplateMixin);<commit_msg>simplified jquery/backbone querying<commit_after>//= require jquery.scrollTo

window.FactRelationsView = Backbone.View.extend({
  tagName: "div",
  className: "page evidence-list fact-relations-container",

  template: 'fact_relations/fact_relations',

  initialize: function(options) {
    this._views = [];
    this._loading = true;

    this.collection.bind('add', this.addFactRelation, this);
    this.collection.bind('remove', this.removeFactRelation, this);
    this.collection.bind('reset', this.resetFactRelations, this);

    this.showNoEvidenceMessage();

    this.initializeSearchView();
  },

  initializeSearchView: function() {
    this.factRelationSearchView = new FactRelationSearchView({
      factRelations: this.collection,
      type: this.options.type
    });
  },

  highlightFactRelation: function(view) {
    $( 'ul.evidence-listing', this.el)
      .scrollTo(view.el, 800);

    view.highlight();
  },

  addFactRelation: function(factRelation, factRelations, options) {
    if ( !options ) {
      options = {};
    }

    var factRelationView = new FactRelationView({model: factRelation});

    this.hideNoEvidenceMessage();

    this._views.push(factRelationView);

    factRelationView.render();
    this.$el.find('ul.evidence-listing').append(factRelationView.el);

    if ( options.highlight ) {
      this.highlightFactRelation(factRelationView);
    }
  },

  resetFactRelations: function() {
    var self = this;

    this.collection.forEach(function(factRelation) {
      self.addFactRelation(factRelation);
    });

    if ( this.collection.length === 0 ) {
      this.showNoEvidenceMessage();
    }
  },

  removeFactRelation: function(factRelation) {
    console.info( "Removing one FactRelation" );
  },

  render: function() {
    this.$el
      .html(this.templateRender())
      .prepend(this.factRelationSearchView.render().el);

    return this;
  },

  hide: function() {
    this.$el.hide();
  },

  show: function() {
    this.$el.show();
  },

  showAndFetch: function() {
    var self = this;

    if ( ! this._fetched ) {
      this._fetched = true;

      this.collection.reset();

      this.showLoadingIndicator();

      this.collection.fetch({
        success: function() {
          self.hideLoadingIndicator();
        }
      });
    }

    this.$el.show();
  },

  showLoadingIndicator: function() {
    this.$('.loading-evidence').show();
    this.hideNoEvidenceMessage();
  },

  hideLoadingIndicator: function() {
    this.$('.loading-evidence').hide();
  },

  showNoEvidenceMessage: function() {
    this.$('.no-evidence-message').show();
    this.hideLoadingIndicator();
  },

  hideNoEvidenceMessage: function() {
    this.$('.no-evidence-message').hide();
  }
});
_.extend(FactRelationsView.prototype, TemplateMixin);<|endoftext|>


<commit_before>/*
 * Base script of the Ilios JavaScript library.
 *
 * Contains the foundation of the library, but also a variety of seemingly arbitrary (yet commonly used) functionality.
 *
 * Defines the following namespaces:
 *
 * ilios.alert
 * ilios.global
 *
 * Dependencies:
 *
 * Ilios i18n utility
 * YUI utilities
 * YUI containers
 * YUI IdleTimer
 *
 * @todo move YUI custom configuration into separate file
 * @todo modularize related code
 */

/**
 * The ilios global namespace object.
 * @class ilios
 * @static
 */
var ilios = {};

/**
 * Creates and returns the specified namespace within the ilios namespace.
 * Example:
 *

 * ilios.namespace("ui.widget");
 *

 * This will create the "ilios.ui" namespace object, and then create and return
 * the "ilios.ui.widget" namespace object
 * @method namespace
 * @static
 * @param {String} namespace the namespaces to create, delimited by "."
 * @return {Object} a reference to the last namespace object created
 */
ilios.namespace = function (namespace) {
    var token = namespace.split(".");
    var o = ilios;
    var i, n;

    for (i = 0, n = token.length; i < n; i++) {
        if (0 === i && token[0] === "ilios") {
            // "ilios" namespace is implied as base
        } else {
            o[token[i]] = o[token[i]] || {};
            o = o[token[i]];
        }
    }
    return o;
};

// use JavaScript JSON parse/stringify implementation
// since the browser-native implementations choke on our server-side generated JSON
// in some cases.
// @see Redmine issue #869
// @link http://developer.yahoo.com/yui/json/#native
if (YAHOO.lang.JSON) {
    YAHOO.lang.JSON.useNativeStringify = false;
    YAHOO.lang.JSON.useNativeParse = false;
}

/**
 * Provides overrides for the window object's various dialog-functions.
 * @class ilios.alert
 * @static
 */
ilios.namespace('alert');

/**
 * Queues to-be-displayed network-activity messages.
 * @property {Array} networkActivityI18NStrings
 */
ilios.alert.networkActivityI18NStrings = [];

/**
 * Displays the first message in the networkActivityI18NStrings queue, if applicable.
 * @method updateServerInteractionProgress
 */
ilios.alert.updateServerInteractionProgress = function () {
    var Element = YAHOO.util.Element;
    var element;
    var i18nStr;

    if (0 < ilios.alert.networkActivityI18NStrings.length) { // message queued for display, show the first
        i18nStr = ilios_i18nVendor.getI18NString(ilios.alert.networkActivityI18NStrings[0]);

        element = document.getElementById('save_in_progress_text');
        if (element) {
            element.appendChild(document.createTextNode(i18nStr));
            element = document.getElementById('save_in_progress_div');
            (new Element(element)).setStyle('display', 'block');
        }
    } else { // no messages, hide the display container
        element = document.getElementById('save_in_progress_div');
        if (element) {
            (new Element(element)).setStyle('display', 'none');
        }
    }
};

/**
 * Default button-click event handler for the "inform" and "alert" dialogs.
 * Closes the dialog that is has been invoked on.
 * @method simpleHidingHandler
 */
ilios.alert.simpleHidingHandler = function () {
    this.hide();
};

/**
 * Override-function for the window object's alert function.
 * @method alert
 * @param {String} str the dialog's message
 * @param {String} acceptBtnText defaults to general.terms.ok in the i18n file if null
 * @param {Function} acceptHandler defaults to the ilios.alert.simpleHidingHandler function which just closes the dialog
 * @param {Object} acceptHandlerArgs an optional argument to pass along to the accept handler; this will
 *     be ignored if the acceptHandler param is null.
 */
ilios.alert.alert = function (str, acceptBtnText, acceptHandler, acceptHandlerArgs) {
    var lang = YAHOO.lang;
    var btnConfig = [];

    acceptHandler = lang.isFunction(acceptHandler) ? acceptHandler : ilios.alert.simpleHidingHandler;
    acceptBtnText = acceptBtnText || ilios_i18nVendor.getI18NString('general.terms.ok');
    acceptHandlerArgs = lang.isObject(acceptHandlerArgs) ? acceptHandlerArgs : {};
    btnConfig.push({
        text: acceptBtnText,
        handler: {
            fn: acceptHandler,
            obj: acceptHandlerArgs,
            scope: ilios.alert.confirmDialog
        },
        isDefault: true
    });

    if (! ilios.alert.confirmDialog) { // lazy load
        ilios.alert.confirmDialog = ilios.alert.createConfirmDialog();
        ilios.alert.confirmDialog.render(document.body);
    }
    // configure and display
    ilios.alert.confirmDialog.configButtons(null, [btnConfig], null);
    ilios.alert.confirmDialog.setBody(str);
    ilios.alert.confirmDialog.cfg.setProperty('icon', YAHOO.widget.SimpleDialog.ICON_WARN);
    ilios.alert.confirmDialog.cfg.queueProperty('zIndex', 9999);
    ilios.alert.confirmDialog.bringToTop();
    ilios.alert.confirmDialog.show();
};

/**
 * Override-function for the window object's inform() function.
 * @method inform
 * @param {String} str the dialog's message
 * @param {String} acceptBtnText defaults to general.terms.yes if null
 * @param {Function} acceptHandler defaults to the ilios.alert.simpleHidingHandler function which just closes the dialog
 * @param {Object} acceptHandlerArgs an optional argument to pass along to the accept handler; this will
 *     be ignored if the acceptHandler param is null.
 * @param {String} declineBtnText defaults to general.terms.no if null
 * @param {Function} declineHandler defaults to the ilios.alert.simpleHidingHandler function which just closes the dialog
 * @param {Object} declineHandlerArgs an optional argument to pass along to the decline handler; this
 *     will be ignored if the declineHandler param is null.
 */
ilios.alert.inform = function (str, acceptBtnText, acceptHandler, acceptHandlerArgs, declineBtnText, declineHandler, declineHandlerArgs) {
    var lang = YAHOO.lang;
    var btnConfig = [];

    acceptHandler = lang.isFunction(acceptHandler) ? acceptHandler : ilios.alert.simpleHidingHandler;
    declineHandler = lang.isFunction(declineHandler) ? declineHandler : ilios.alert.simpleHidingHandler;
    acceptBtnText = acceptBtnText || ilios_i18nVendor.getI18NString('general.terms.yes');
    declineBtnText = declineBtnText || ilios_i18nVendor.getI18NString('general.terms.no');
    acceptHandlerArgs = lang.isObject(acceptHandlerArgs) ? acceptHandlerArgs : {};
    declineHandlerArgs = lang.isObject(declineHandlerArgs) ? declineHandlerArgs : {};

    btnConfig.push({
        text: acceptBtnText,
        handler: {
            fn: acceptHandler,
            obj: acceptHandlerArgs,
            scope: ilios.alert.informDialog
        }
    });
    btnConfig.push({
        text: declineBtnText,
        handler: {
            fn: declineHandler,
            obj: declineHandlerArgs,
            scope: ilios.alert.informDialog
        },
        isDefault: true
    });

    if (! ilios.alert.informDialog) { // lazy load
        ilios.alert.informDialog = ilios.alert.createInformDialog();
        ilios.alert.informDialog.render(document.body);
    }
    ilios.alert.informDialog.configButtons(null, [btnConfig], null);
    ilios.alert.informDialog.setBody(str);
    ilios.alert.informDialog.cfg.setProperty('icon', YAHOO.widget.SimpleDialog.ICON_WARN);
    ilios.alert.informDialog.cfg.queueProperty('zIndex', 9999);
    ilios.alert.informDialog.bringToTop();
    ilios.alert.informDialog.show();
};

/**
 * Creates and returns a simple "confirmation" dialog.
 * Does NOT render the dialog.
 * @return {YAHOO.widget.SimpleDialog}
 */
ilios.alert.createConfirmDialog = function () {
    var confirmDialog = new YAHOO.widget.SimpleDialog('ilios_alert_panel', {
        visible:false,
        width: '350px',
        zIndex: 9999,
        close: false,
        fixedcenter: 'contained',
        modal: true,
        draggable: true,
        constraintoviewport: true,
        icon: YAHOO.widget.SimpleDialog.ICON_WARN,
        buttons: [{
            text: ilios_i18nVendor.getI18NString('general.terms.ok'),
            handler: ilios.alert.simpleHidingHandler,
            isDefault: true
        }]
    });
    confirmDialog.setHeader(ilios_i18nVendor.getI18NString('general.terms.alert') + "!");
    confirmDialog.setBody('');
    return confirmDialog;
};

/**
 * Creates and returns a simple "info" ("Alert!") dialog.
 * Does NOT render the dialog.
 * @return {YAHOO.widget.SimpleDialog}
 */
ilios.alert.createInformDialog = function () {
    var informDialog = new YAHOO.widget.SimpleDialog('ilios_inform_panel', {
        visible:false,
        width: '350px',
        zIndex: 9999,
        close: false,
        fixedcenter: 'contained',
        modal: true,
        draggable: true,
        constraintoviewport: true,
        icon: YAHOO.widget.SimpleDialog.ICON_WARN,
        buttons: [{
            text: ilios_i18nVendor.getI18NString('general.terms.ok'),
            handler: ilios.alert.simpleHidingHandler,
            isDefault: true
        }]
    });
    informDialog.setHeader(ilios_i18nVendor.getI18NString('general.notification.dialog_title'));
    informDialog.setBody('');
    return informDialog;
};

/**
 * All kinds of crazy went into this object.
 * @class ilios.global
 * @todo clean up this mess.
 */
ilios.namespace('global');

/**
 * The page's preferences model.
 * Should be instantiated by calling ilios.global.installPreferencesModel().
 * @property {PreferencesModel} preferencesModel
 */
ilios.global.preferencesModel = null;

/**
 * Instantiates ilios.global.preferencesModel.
 * @method installPreferencesModel
 */
ilios.global.installPreferencesModel = function () {
    ilios.global.preferencesModel = new PreferencesModel();
};

/**
 * Instantiates and starts the idle timer, subscribes a timeout-handler function to it.
 * @method startIdleTimer
 * @param {Number} Timeout period in milliseconds
 * @param {String} logoutUrl logout action URL to redirect to on timeout
 */
ilios.global.startIdleTimer = function (timeout, logoutUrl) {
    var idleTimer = YAHOO.util.IdleTimer;
    timeout = YAHOO.lang.isNumber(timeout) ? timeout : 2700000; // default to 45 mins
    idleTimer.subscribe("idle", function () {
        if (! YAHOO.util.IdleTimer.isIdle()) {
            return;
        }
        ilios.alert.alert(
            ilios_i18nVendor.getI18NString('general.notification.idle_timeout_message'),
            ilios_i18nVendor.getI18NString('general.terms.ok'),
            function () { window.location.href = logoutUrl; }
        );
    });
    idleTimer.start(timeout, document);
};

/**
 * Default handler function for failed XHR calls.
 * Pops up an dialog displaying the given error message and exception.
 * @method defaultAJAXFailureHandler
 * @param {Object} resultObject the response object
 * @param {Exception} rootException a thrown exception
 */
ilios.global.defaultAJAXFailureHandler = function (resultObject, rootException) {
    var cause = (typeof rootException == 'undefined') ? resultObject.responseText : rootException.description;
    ilios.alert.alert(ilios_i18nVendor.getI18NString('general.error.fatal')  + " (" + cause + ")");
};

/**
 * Full names of week days (e.g. "Sunday", ..., "Saturday")
 * @property {Array} longDayOfWeekI18NStrings
 */
ilios.global.longDayOfWeekI18NStrings = null;

/**
 * Abbreviated names of week days (e.g. "Sun", ..., "Sat")
 * @property {Array} shortDayOfWeekI18NStrings
 */
ilios.global.shortDayOfWeekI18NStrings = null;

/**
 * Returns the I18Ned name of a given week day.
 * @method getI18NStringForDayOfWeek
 * @param {Number} day of the week index. Sunday = 0, ... , Saturday = 6
 * @param {Boolean} shortString TRUE to return an abbreviated name, FALSE for full name
 * @return {String} the name of the week day.
 */
ilios.global.getI18NStringForDayOfWeek = function (dayOfWeek, shortString) {
    var dayArray = null;

    if ((dayOfWeek < 0) || (dayOfWeek > 6)) { // boundary check
        return '';
    }

    if (shortString) { // full
        if (ilios.global.shortDayOfWeekI18NStrings == null) { // lazy load
            ilios.global.shortDayOfWeekI18NStrings = [
                ilios_i18nVendor.getI18NString('general.calendar.sunday_short'),
                ilios_i18nVendor.getI18NString('general.calendar.monday_short'),
                ilios_i18nVendor.getI18NString('general.calendar.tuesday_short'),
                ilios_i18nVendor.getI18NString('general.calendar.wednesday_short'),
                ilios_i18nVendor.getI18NString('general.calendar.thursday_short'),
                ilios_i18nVendor.getI18NString('general.calendar.friday_short'),
                ilios_i18nVendor.getI18NString('general.calendar.saturday_short')
            ];
        }
        dayArray = ilios.global.shortDayOfWeekI18NStrings;
    } else { // abbrev
        if (ilios.global.longDayOfWeekI18NStrings == null) { // lazy load
            ilios.global.longDayOfWeekI18NStrings = [
                ilios_i18nVendor.getI18NString('general.calendar.sunday_long'),
                ilios_i18nVendor.getI18NString('general.calendar.monday_long'),
                ilios_i18nVendor.getI18NString('general.calendar.tuesday_long'),
                ilios_i18nVendor.getI18NString('general.calendar.wednesday_long'),
                ilios_i18nVendor.getI18NString('general.calendar.thursday_long'),
                ilios_i18nVendor.getI18NString('general.calendar.friday_long'),
                ilios_i18nVendor.getI18NString('general.calendar.saturday_long')
            ];
        }
        dayArray = ilios.global.longDayOfWeekI18NStrings;
    }
    return dayArray[dayOfWeek]; // return the weekday
};
<commit_msg>don't append text, replace it; won't work in IE8 but a lot of our code won't actually... use innerText fallback if you really want to support IE8...<commit_after>/*
 * Base script of the Ilios JavaScript library.
 *
 * Contains the foundation of the library, but also a variety of seemingly arbitrary (yet commonly used) functionality.
 *
 * Defines the following namespaces:
 *
 * ilios.alert
 * ilios.global
 *
 * Dependencies:
 *
 * Ilios i18n utility
 * YUI utilities
 * YUI containers
 * YUI IdleTimer
 *
 * @todo move YUI custom configuration into separate file
 * @todo modularize related code
 */

/**
 * The ilios global namespace object.
 * @class ilios
 * @static
 */
var ilios = {};

/**
 * Creates and returns the specified namespace within the ilios namespace.
 * Example:
 *

 * ilios.namespace("ui.widget");
 *

 * This will create the "ilios.ui" namespace object, and then create and return
 * the "ilios.ui.widget" namespace object
 * @method namespace
 * @static
 * @param {String} namespace the namespaces to create, delimited by "."
 * @return {Object} a reference to the last namespace object created
 */
ilios.namespace = function (namespace) {
    var token = namespace.split(".");
    var o = ilios;
    var i, n;

    for (i = 0, n = token.length; i < n; i++) {
        if (0 === i && token[0] === "ilios") {
            // "ilios" namespace is implied as base
        } else {
            o[token[i]] = o[token[i]] || {};
            o = o[token[i]];
        }
    }
    return o;
};

// use JavaScript JSON parse/stringify implementation
// since the browser-native implementations choke on our server-side generated JSON
// in some cases.
// @see Redmine issue #869
// @link http://developer.yahoo.com/yui/json/#native
if (YAHOO.lang.JSON) {
    YAHOO.lang.JSON.useNativeStringify = false;
    YAHOO.lang.JSON.useNativeParse = false;
}

/**
 * Provides overrides for the window object's various dialog-functions.
 * @class ilios.alert
 * @static
 */
ilios.namespace('alert');

/**
 * Queues to-be-displayed network-activity messages.
 * @property {Array} networkActivityI18NStrings
 */
ilios.alert.networkActivityI18NStrings = [];

/**
 * Displays the first message in the networkActivityI18NStrings queue, if applicable.
 * @method updateServerInteractionProgress
 */
ilios.alert.updateServerInteractionProgress = function () {
    var Element = YAHOO.util.Element;
    var element;
    var i18nStr;

    if (0 < ilios.alert.networkActivityI18NStrings.length) { // message queued for display, show the first
        i18nStr = ilios_i18nVendor.getI18NString(ilios.alert.networkActivityI18NStrings[0]);

        element = document.getElementById('save_in_progress_text');
        if (element) {
            element.textContent = i18nStr;
            element = document.getElementById('save_in_progress_div');
            (new Element(element)).setStyle('display', 'block');
        }
    } else { // no messages, hide the display container
        element = document.getElementById('save_in_progress_div');
        if (element) {
            (new Element(element)).setStyle('display', 'none');
        }
    }
};

/**
 * Default button-click event handler for the "inform" and "alert" dialogs.
 * Closes the dialog that is has been invoked on.
 * @method simpleHidingHandler
 */
ilios.alert.simpleHidingHandler = function () {
    this.hide();
};

/**
 * Override-function for the window object's alert function.
 * @method alert
 * @param {String} str the dialog's message
 * @param {String} acceptBtnText defaults to general.terms.ok in the i18n file if null
 * @param {Function} acceptHandler defaults to the ilios.alert.simpleHidingHandler function which just closes the dialog
 * @param {Object} acceptHandlerArgs an optional argument to pass along to the accept handler; this will
 *     be ignored if the acceptHandler param is null.
 */
ilios.alert.alert = function (str, acceptBtnText, acceptHandler, acceptHandlerArgs) {
    var lang = YAHOO.lang;
    var btnConfig = [];

    acceptHandler = lang.isFunction(acceptHandler) ? acceptHandler : ilios.alert.simpleHidingHandler;
    acceptBtnText = acceptBtnText || ilios_i18nVendor.getI18NString('general.terms.ok');
    acceptHandlerArgs = lang.isObject(acceptHandlerArgs) ? acceptHandlerArgs : {};
    btnConfig.push({
        text: acceptBtnText,
        handler: {
            fn: acceptHandler,
            obj: acceptHandlerArgs,
            scope: ilios.alert.confirmDialog
        },
        isDefault: true
    });

    if (! ilios.alert.confirmDialog) { // lazy load
        ilios.alert.confirmDialog = ilios.alert.createConfirmDialog();
        ilios.alert.confirmDialog.render(document.body);
    }
    // configure and display
    ilios.alert.confirmDialog.configButtons(null, [btnConfig], null);
    ilios.alert.confirmDialog.setBody(str);
    ilios.alert.confirmDialog.cfg.setProperty('icon', YAHOO.widget.SimpleDialog.ICON_WARN);
    ilios.alert.confirmDialog.cfg.queueProperty('zIndex', 9999);
    ilios.alert.confirmDialog.bringToTop();
    ilios.alert.confirmDialog.show();
};

/**
 * Override-function for the window object's inform() function.
 * @method inform
 * @param {String} str the dialog's message
 * @param {String} acceptBtnText defaults to general.terms.yes if null
 * @param {Function} acceptHandler defaults to the ilios.alert.simpleHidingHandler function which just closes the dialog
 * @param {Object} acceptHandlerArgs an optional argument to pass along to the accept handler; this will
 *     be ignored if the acceptHandler param is null.
 * @param {String} declineBtnText defaults to general.terms.no if null
 * @param {Function} declineHandler defaults to the ilios.alert.simpleHidingHandler function which just closes the dialog
 * @param {Object} declineHandlerArgs an optional argument to pass along to the decline handler; this
 *     will be ignored if the declineHandler param is null.
 */
ilios.alert.inform = function (str, acceptBtnText, acceptHandler, acceptHandlerArgs, declineBtnText, declineHandler, declineHandlerArgs) {
    var lang = YAHOO.lang;
    var btnConfig = [];

    acceptHandler = lang.isFunction(acceptHandler) ? acceptHandler : ilios.alert.simpleHidingHandler;
    declineHandler = lang.isFunction(declineHandler) ? declineHandler : ilios.alert.simpleHidingHandler;
    acceptBtnText = acceptBtnText || ilios_i18nVendor.getI18NString('general.terms.yes');
    declineBtnText = declineBtnText || ilios_i18nVendor.getI18NString('general.terms.no');
    acceptHandlerArgs = lang.isObject(acceptHandlerArgs) ? acceptHandlerArgs : {};
    declineHandlerArgs = lang.isObject(declineHandlerArgs) ? declineHandlerArgs : {};

    btnConfig.push({
        text: acceptBtnText,
        handler: {
            fn: acceptHandler,
            obj: acceptHandlerArgs,
            scope: ilios.alert.informDialog
        }
    });
    btnConfig.push({
        text: declineBtnText,
        handler: {
            fn: declineHandler,
            obj: declineHandlerArgs,
            scope: ilios.alert.informDialog
        },
        isDefault: true
    });

    if (! ilios.alert.informDialog) { // lazy load
        ilios.alert.informDialog = ilios.alert.createInformDialog();
        ilios.alert.informDialog.render(document.body);
    }
    ilios.alert.informDialog.configButtons(null, [btnConfig], null);
    ilios.alert.informDialog.setBody(str);
    ilios.alert.informDialog.cfg.setProperty('icon', YAHOO.widget.SimpleDialog.ICON_WARN);
    ilios.alert.informDialog.cfg.queueProperty('zIndex', 9999);
    ilios.alert.informDialog.bringToTop();
    ilios.alert.informDialog.show();
};

/**
 * Creates and returns a simple "confirmation" dialog.
 * Does NOT render the dialog.
 * @return {YAHOO.widget.SimpleDialog}
 */
ilios.alert.createConfirmDialog = function () {
    var confirmDialog = new YAHOO.widget.SimpleDialog('ilios_alert_panel', {
        visible:false,
        width: '350px',
        zIndex: 9999,
        close: false,
        fixedcenter: 'contained',
        modal: true,
        draggable: true,
        constraintoviewport: true,
        icon: YAHOO.widget.SimpleDialog.ICON_WARN,
        buttons: [{
            text: ilios_i18nVendor.getI18NString('general.terms.ok'),
            handler: ilios.alert.simpleHidingHandler,
            isDefault: true
        }]
    });
    confirmDialog.setHeader(ilios_i18nVendor.getI18NString('general.terms.alert') + "!");
    confirmDialog.setBody('');
    return confirmDialog;
};

/**
 * Creates and returns a simple "info" ("Alert!") dialog.
 * Does NOT render the dialog.
 * @return {YAHOO.widget.SimpleDialog}
 */
ilios.alert.createInformDialog = function () {
    var informDialog = new YAHOO.widget.SimpleDialog('ilios_inform_panel', {
        visible:false,
        width: '350px',
        zIndex: 9999,
        close: false,
        fixedcenter: 'contained',
        modal: true,
        draggable: true,
        constraintoviewport: true,
        icon: YAHOO.widget.SimpleDialog.ICON_WARN,
        buttons: [{
            text: ilios_i18nVendor.getI18NString('general.terms.ok'),
            handler: ilios.alert.simpleHidingHandler,
            isDefault: true
        }]
    });
    informDialog.setHeader(ilios_i18nVendor.getI18NString('general.notification.dialog_title'));
    informDialog.setBody('');
    return informDialog;
};

/**
 * All kinds of crazy went into this object.
 * @class ilios.global
 * @todo clean up this mess.
 */
ilios.namespace('global');

/**
 * The page's preferences model.
 * Should be instantiated by calling ilios.global.installPreferencesModel().
 * @property {PreferencesModel} preferencesModel
 */
ilios.global.preferencesModel = null;

/**
 * Instantiates ilios.global.preferencesModel.
 * @method installPreferencesModel
 */
ilios.global.installPreferencesModel = function () {
    ilios.global.preferencesModel = new PreferencesModel();
};

/**
 * Instantiates and starts the idle timer, subscribes a timeout-handler function to it.
 * @method startIdleTimer
 * @param {Number} Timeout period in milliseconds
 * @param {String} logoutUrl logout action URL to redirect to on timeout
 */
ilios.global.startIdleTimer = function (timeout, logoutUrl) {
    var idleTimer = YAHOO.util.IdleTimer;
    timeout = YAHOO.lang.isNumber(timeout) ? timeout : 2700000; // default to 45 mins
    idleTimer.subscribe("idle", function () {
        if (! YAHOO.util.IdleTimer.isIdle()) {
            return;
        }
        ilios.alert.alert(
            ilios_i18nVendor.getI18NString('general.notification.idle_timeout_message'),
            ilios_i18nVendor.getI18NString('general.terms.ok'),
            function () { window.location.href = logoutUrl; }
        );
    });
    idleTimer.start(timeout, document);
};

/**
 * Default handler function for failed XHR calls.
 * Pops up an dialog displaying the given error message and exception.
 * @method defaultAJAXFailureHandler
 * @param {Object} resultObject the response object
 * @param {Exception} rootException a thrown exception
 */
ilios.global.defaultAJAXFailureHandler = function (resultObject, rootException) {
    var cause = (typeof rootException == 'undefined') ? resultObject.responseText : rootException.description;
    ilios.alert.alert(ilios_i18nVendor.getI18NString('general.error.fatal')  + " (" + cause + ")");
};

/**
 * Full names of week days (e.g. "Sunday", ..., "Saturday")
 * @property {Array} longDayOfWeekI18NStrings
 */
ilios.global.longDayOfWeekI18NStrings = null;

/**
 * Abbreviated names of week days (e.g. "Sun", ..., "Sat")
 * @property {Array} shortDayOfWeekI18NStrings
 */
ilios.global.shortDayOfWeekI18NStrings = null;

/**
 * Returns the I18Ned name of a given week day.
 * @method getI18NStringForDayOfWeek
 * @param {Number} day of the week index. Sunday = 0, ... , Saturday = 6
 * @param {Boolean} shortString TRUE to return an abbreviated name, FALSE for full name
 * @return {String} the name of the week day.
 */
ilios.global.getI18NStringForDayOfWeek = function (dayOfWeek, shortString) {
    var dayArray = null;

    if ((dayOfWeek < 0) || (dayOfWeek > 6)) { // boundary check
        return '';
    }

    if (shortString) { // full
        if (ilios.global.shortDayOfWeekI18NStrings == null) { // lazy load
            ilios.global.shortDayOfWeekI18NStrings = [
                ilios_i18nVendor.getI18NString('general.calendar.sunday_short'),
                ilios_i18nVendor.getI18NString('general.calendar.monday_short'),
                ilios_i18nVendor.getI18NString('general.calendar.tuesday_short'),
                ilios_i18nVendor.getI18NString('general.calendar.wednesday_short'),
                ilios_i18nVendor.getI18NString('general.calendar.thursday_short'),
                ilios_i18nVendor.getI18NString('general.calendar.friday_short'),
                ilios_i18nVendor.getI18NString('general.calendar.saturday_short')
            ];
        }
        dayArray = ilios.global.shortDayOfWeekI18NStrings;
    } else { // abbrev
        if (ilios.global.longDayOfWeekI18NStrings == null) { // lazy load
            ilios.global.longDayOfWeekI18NStrings = [
                ilios_i18nVendor.getI18NString('general.calendar.sunday_long'),
                ilios_i18nVendor.getI18NString('general.calendar.monday_long'),
                ilios_i18nVendor.getI18NString('general.calendar.tuesday_long'),
                ilios_i18nVendor.getI18NString('general.calendar.wednesday_long'),
                ilios_i18nVendor.getI18NString('general.calendar.thursday_long'),
                ilios_i18nVendor.getI18NString('general.calendar.friday_long'),
                ilios_i18nVendor.getI18NString('general.calendar.saturday_long')
            ];
        }
        dayArray = ilios.global.longDayOfWeekI18NStrings;
    }
    return dayArray[dayOfWeek]; // return the weekday
};
<|endoftext|>