<commit_before>/*!
 * context
 * Version: 0.0.1
 * Date: 2016/7/29
 * https://github.com/Nuintun/fengine
 *
 * Original Author: https://github.com/tmont/html-parser
 *
 * This is licensed under the MIT License (MIT).
 * For details, see: https://github.com/Nuintun/fengine/blob/master/LICENSE
 */

'use strict';

// lib
var util = require('./util');

// event types
var EVENTTYPES = [
  'openElement',
  'closeElement',
  'attribute',
  'comment',
  'cdata',
  'text',
  'docType',
  'xmlType',
  'closeOpenedElement'
];

/**
 * format rule
 * @param rule
 * @param porp
 */
function formatRule(rule, porp){
  switch (util.type(rule[porp])) {
    case 'string':
      rule[porp] = util.str2regex(rule[porp]);
      break;
    case 'regexp':
      if (rule[porp].global) {
        var attr = '';

        if (rule[porp].ignoreCase) {
          attr += 'i';
        }

        if (rule[porp].multiline) {
          attr += 'm';
        }

        rule[porp] = util.str2regex(rule[porp].source, attr);
      }
      break;
    default:
      throw new TypeError('rules.dataElements.' + porp + ' must be a sting or regexp.');
      break;
  }
}

/**
 * create read
 * @param raw
 * @param callbacks
 * @param rules
 * @returns {{
 *   text: string,
 *   peek: context.peek,
 *   read: context.read,
 *   readUntilNonWhitespace: context.readUntilNonWhitespace,
 *   readRule: context.readRule,
 *   peekIgnoreWhitespace: context.peekIgnoreWhitespace
 * }}
 */
module.exports.create = function (raw, callbacks, rules){
  var index = 0;
  var current = null;
  var substring = null;

  var context = {
    text: '',
    peek: function (count){
      count = count || 1;

      return this.raw.substr(this.index + 1, count);
    },
    read: function (count, peek){
      var next;

      count = count || 1;

      if (peek) {
        next = this.peek(count);
      }

      this.index += count;

      if (this.index > this.length) {
        this.index = this.length;
      }

      return next;
    },
    readUntilNonWhitespace: function (){
      var next;
      var value = '';

      while (!this.EOF) {
        next = this.read();
        value += next;

        if (!/\s$/.test(value)) {
          break;
        }
      }

      return value;
    },
    readRule: function (rule){
      var match = rule.exec(this.raw.substring(this.index));
      var value = match ? match[0] : '';

      this.index += (match ? match.index : 0) + value.length;

      return value;
    },
    peekIgnoreWhitespace: function (count){
      count = count || 1;

      var value = '';
      var next = '';
      var offset = 0;

      do {
        next = this.raw.charAt(this.index + ++offset);

        if (!next) {
          break;
        }

        if (!/\s/.test(next)) {
          value += next;
        }
      } while (value.length < count);

      return value;
    }
  };

  // end of file
  context.__defineGetter__('EOF', function (){
    return this.index >= this.length;
  });

  // current char
  context.__defineGetter__('current', function (){
    return this.EOF ? '' : current === null ? (current = this.raw.charAt(this.index)) : current;
  });

  // raw code
  context.__defineGetter__('raw', function (){
    return raw;
  });

  // raw length
  context.__defineGetter__('length', function (){
    return this.raw.length;
  });

  // index
  context.__defineGetter__('index', function (){
    return index;
  });

  // set index
  context.__defineSetter__('index', function (value){
    index = value;
    current = null;
    substring = null;
  });

  // non parsed code
  context.__defineGetter__('substring', function (){
    return substring === null ? (substring = this.raw.substring(this.index)) : substring;
  });

  // callbacks
  context.callbacks = {};

  // format event callbacks
  EVENTTYPES.forEach(function (value){
    context.callbacks[value] = util.noop;
  });

  // merge callbacks
  util.merge(context.callbacks, callbacks || {});

  // rules
  context.rules = {
    name: /[a-zA-Z_][\w:.-]*/,
    attribute: /[a-zA-Z_][\w:.-]*/,
    dataElements: {
      cdata: {
        start: '<![CDATA[',
        end: ']]>'
      },
      comment: {
        start: '<!--',
        end: '-->'
      },
      docType: {
        start: /^<!DOCTYPE /i,
        end: '>'
      }
    }
  };

  // merge rules
  util.merge(context.rules, rules || {});

  var rule;
  var dataElements = context.rules.dataElements;

  for (var data in dataElements) {
    if (dataElements.hasOwnProperty(data)) {
      rule = dataElements[data];

      formatRule(rule, 'start');
      formatRule(rule, 'end');
    }
  }

  // return read
  return context;
};
<commit_msg>update files<commit_after>/*!
 * context
 * Version: 0.0.1
 * Date: 2016/7/29
 * https://github.com/Nuintun/fengine
 *
 * Original Author: https://github.com/tmont/html-parser
 *
 * This is licensed under the MIT License (MIT).
 * For details, see: https://github.com/Nuintun/fengine/blob/master/LICENSE
 */

'use strict';

// lib
var util = require('./util');

// event types
var EVENTTYPES = [
  'openElement',
  'closeElement',
  'attribute',
  'comment',
  'cdata',
  'text',
  'docType',
  'xmlType',
  'closeOpenedElement'
];

/**
 * format rule
 * @param rule
 * @param porp
 */
function formatRule(rule, porp){
  switch (util.type(rule[porp])) {
    case 'string':
      rule[porp] = util.str2regex(rule[porp]);
      break;
    case 'regexp':
      if (rule[porp].global) {
        var attr = '';

        if (rule[porp].ignoreCase) {
          attr += 'i';
        }

        if (rule[porp].multiline) {
          attr += 'm';
        }

        rule[porp] = util.str2regex(rule[porp].source, attr);
      }
      break;
    default:
      throw new TypeError('rules.dataElements.' + porp + ' must be a sting or regexp.');
      break;
  }
}

/**
 * create read
 * @param raw
 * @param callbacks
 * @param rules
 * @returns {{
 *   text: string,
 *   peek: context.peek,
 *   read: context.read,
 *   readUntilNonWhitespace: context.readUntilNonWhitespace,
 *   readRule: context.readRule,
 *   peekIgnoreWhitespace: context.peekIgnoreWhitespace
 * }}
 */
module.exports.create = function (raw, callbacks, rules){
  var index = 0;
  var current = null;
  var substring = null;

  var context = {
    text: '',
    peek: function (count){
      count = count || 1;

      return this.raw.substr(this.index + 1, count);
    },
    read: function (count, peek){
      var next;

      count = count || 1;

      if (peek) {
        next = this.peek(count);
      }

      this.index += count;

      if (this.index > this.length) {
        this.index = this.length;
      }

      return next;
    },
    readUntilNonWhitespace: function (){
      var next;
      var value = '';

      while (!this.EOF) {
        next = this.read(1, true);

        value += next;

        if (!/\s$/.test(value)) {
          break;
        }
      }

      return value;
    },
    readRule: function (rule){
      var match = rule.exec(this.raw.substring(this.index));
      var value = match ? match[0] : '';

      this.index += (match ? match.index : 0) + value.length;

      return value;
    },
    peekIgnoreWhitespace: function (count){
      count = count || 1;

      var value = '';
      var next = '';
      var offset = 0;

      do {
        next = this.raw.charAt(this.index + ++offset);

        if (!next) {
          break;
        }

        if (!/\s/.test(next)) {
          value += next;
        }
      } while (value.length < count);

      return value;
    }
  };

  // end of file
  context.__defineGetter__('EOF', function (){
    return this.index >= this.length;
  });

  // current char
  context.__defineGetter__('current', function (){
    return this.EOF ? '' : current === null ? (current = this.raw.charAt(this.index)) : current;
  });

  // raw code
  context.__defineGetter__('raw', function (){
    return raw;
  });

  // raw length
  context.__defineGetter__('length', function (){
    return this.raw.length;
  });

  // index
  context.__defineGetter__('index', function (){
    return index;
  });

  // set index
  context.__defineSetter__('index', function (value){
    index = value;
    current = null;
    substring = null;
  });

  // non parsed code
  context.__defineGetter__('substring', function (){
    return substring === null ? (substring = this.raw.substring(this.index)) : substring;
  });

  // callbacks
  context.callbacks = {};

  // format event callbacks
  EVENTTYPES.forEach(function (value){
    context.callbacks[value] = util.noop;
  });

  // merge callbacks
  util.merge(context.callbacks, callbacks || {});

  // rules
  context.rules = {
    name: /[a-zA-Z_][\w:.-]*/,
    attribute: /[a-zA-Z_][\w:.-]*/,
    dataElements: {
      cdata: {
        start: '<![CDATA[',
        end: ']]>'
      },
      comment: {
        start: '<!--',
        end: '-->'
      },
      docType: {
        start: /^<!DOCTYPE /i,
        end: '>'
      }
    }
  };

  // merge rules
  util.merge(context.rules, rules || {});

  var rule;
  var dataElements = context.rules.dataElements;

  for (var data in dataElements) {
    if (dataElements.hasOwnProperty(data)) {
      rule = dataElements[data];

      formatRule(rule, 'start');
      formatRule(rule, 'end');
    }
  }

  // return read
  return context;
};
<|endoftext|>


<commit_before>const { Parser } = require('gherkin');
const { Context, Suite, Test } = require('mocha');

const { matchStep } = require('./bdd');
const { isAsyncFunction } = require('../utils');
const event = require('../event');
const scenario = require('../scenario');
const Step = require('../step');
const DataTableArgument = require('../data/dataTableArgument');

const parser = new Parser();
parser.stopAtFirstError = false;

module.exports = (text) => {
  const ast = parser.parse(text);

  const suite = new Suite(ast.feature.name, new Context());
  const tags = ast.feature.tags.map(t => t.name);
  suite.title = `${suite.title} ${tags.join(' ')}`.trim();
  suite.tags = tags || [];
  suite.comment = ast.feature.description;
  suite.feature = ast.feature;
  suite.timeout(0);

  suite.beforeEach('codeceptjs.before', () => scenario.setup(suite));
  suite.afterEach('codeceptjs.after', () => scenario.teardown(suite));
  suite.beforeAll('codeceptjs.beforeSuite', () => scenario.suiteSetup(suite));
  suite.afterAll('codeceptjs.afterSuite', () => scenario.suiteTeardown(suite));

  const runSteps = async (steps) => {
    for (const step of steps) {
      const metaStep = new Step.MetaStep(null, step.text);
      metaStep.actor = step.keyword.trim();
      const setMetaStep = (step) => {
        if (step.metaStep) step = step.metaStep; // assign metastep to metastep for nested steps
        step.metaStep = metaStep;
      };
      const fn = matchStep(step.text);
      if (step.argument) {
        step.argument.parse = () => {
          return new DataTableArgument(step.argument);
        };
        fn.params.push(step.argument);
        if (step.argument.type === 'DataTable') metaStep.comment = `\n${transformTable(step.argument)}`;
        if (step.argument.content) metaStep.comment = `\n${step.argument.content}`;
      }
      event.dispatcher.on(event.step.before, setMetaStep);
      try {
        await fn(...fn.params);
      } finally {
        event.dispatcher.removeListener(event.step.before, setMetaStep);
      }
    }
  };

  for (const child of ast.feature.children) {
    if (child.type === 'Background') {
      suite.beforeEach('Before', scenario.injected(async () => runSteps(child.steps), suite, 'before'));
      continue;
    }
    if (child.type === 'ScenarioOutline') {
      for (const examples of child.examples) {
        const fields = examples.tableHeader.cells.map(c => c.value);
        for (const example of examples.tableBody) {
          let exampleSteps = [...child.steps];
          const current = {};
          for (const index in example.cells) {
            const placeholder = fields[index];
            const value = example.cells[index].value;
            current[placeholder] = value;
            exampleSteps = exampleSteps.map((step) => {
              step = { ...step };
              step.text = step.text.replace(`<${placeholder}>`, value);
              return step;
            });
          }
          const tags = child.tags.map(t => t.name);
          const title = `${child.name} ${JSON.stringify(current)} ${tags.join(' ')}`.trim();
          const test = new Test(title, async () => runSteps(addExampleInTable(exampleSteps, current)));
          test.tags = suite.tags.concat(tags);
          suite.addTest(scenario.test(test));
        }
      }
      continue;
    }
    const tags = child.tags.map(t => t.name);
    const title = `${child.name} ${tags.join(' ')}`.trim();
    const test = new Test(title, async () => runSteps(child.steps));
    test.tags = suite.tags.concat(tags);
    suite.addTest(scenario.test(test));
  }

  return suite;
};

function transformTable(table) {
  let str = '';
  for (const id in table.rows) {
    const cells = table.rows[id].cells;
    str += cells.map(c => c.value).map(c => c.slice(0, 15).padEnd(15)).join(' | ');
    str += '\n';
  }
  return str;
}
function addExampleInTable(exampleSteps, placeholders) {
  const steps = JSON.parse(JSON.stringify(exampleSteps));
  for (const placeholder in placeholders) {
    steps.map((step) => {
      step = { ...step };
      if (step.argument && step.argument.type === 'DataTable') {
        for (const id in step.argument.rows) {
          const cells = step.argument.rows[id].cells;
          cells.map(c => (c.value = c.value.replace(`<${placeholder}>`, placeholders[placeholder])));
        }
      }
      return step;
    });
  }
  return steps;
}
<commit_msg>Gherkin - including examples tags into test tags<commit_after>const { Parser } = require('gherkin');
const { Context, Suite, Test } = require('mocha');

const { matchStep } = require('./bdd');
const { isAsyncFunction } = require('../utils');
const event = require('../event');
const scenario = require('../scenario');
const Step = require('../step');
const DataTableArgument = require('../data/dataTableArgument');

const parser = new Parser();
parser.stopAtFirstError = false;

module.exports = (text) => {
  const ast = parser.parse(text);

  const suite = new Suite(ast.feature.name, new Context());
  const tags = ast.feature.tags.map(t => t.name);
  suite.title = `${suite.title} ${tags.join(' ')}`.trim();
  suite.tags = tags || [];
  suite.comment = ast.feature.description;
  suite.feature = ast.feature;
  suite.timeout(0);

  suite.beforeEach('codeceptjs.before', () => scenario.setup(suite));
  suite.afterEach('codeceptjs.after', () => scenario.teardown(suite));
  suite.beforeAll('codeceptjs.beforeSuite', () => scenario.suiteSetup(suite));
  suite.afterAll('codeceptjs.afterSuite', () => scenario.suiteTeardown(suite));

  const runSteps = async (steps) => {
    for (const step of steps) {
      const metaStep = new Step.MetaStep(null, step.text);
      metaStep.actor = step.keyword.trim();
      const setMetaStep = (step) => {
        if (step.metaStep) step = step.metaStep; // assign metastep to metastep for nested steps
        step.metaStep = metaStep;
      };
      const fn = matchStep(step.text);
      if (step.argument) {
        step.argument.parse = () => {
          return new DataTableArgument(step.argument);
        };
        fn.params.push(step.argument);
        if (step.argument.type === 'DataTable') metaStep.comment = `\n${transformTable(step.argument)}`;
        if (step.argument.content) metaStep.comment = `\n${step.argument.content}`;
      }
      event.dispatcher.on(event.step.before, setMetaStep);
      try {
        await fn(...fn.params);
      } finally {
        event.dispatcher.removeListener(event.step.before, setMetaStep);
      }
    }
  };

  for (const child of ast.feature.children) {
    if (child.type === 'Background') {
      suite.beforeEach('Before', scenario.injected(async () => runSteps(child.steps), suite, 'before'));
      continue;
    }
    if (child.type === 'ScenarioOutline') {
      for (const examples of child.examples) {
        const fields = examples.tableHeader.cells.map(c => c.value);
        for (const example of examples.tableBody) {
          let exampleSteps = [...child.steps];
          const current = {};
          for (const index in example.cells) {
            const placeholder = fields[index];
            const value = example.cells[index].value;
            current[placeholder] = value;
            exampleSteps = exampleSteps.map((step) => {
              step = { ...step };
              step.text = step.text.replace(`<${placeholder}>`, value);
              return step;
            });
          }
          const tags = child.tags.map(t => t.name).concat(examples.tags.map(t => t.name));
          const title = `${child.name} ${JSON.stringify(current)} ${tags.join(' ')}`.trim();
          const test = new Test(title, async () => runSteps(addExampleInTable(exampleSteps, current)));
          test.tags = suite.tags.concat(tags);
          suite.addTest(scenario.test(test));
        }
      }
      continue;
    }
    const tags = child.tags.map(t => t.name);
    const title = `${child.name} ${tags.join(' ')}`.trim();
    const test = new Test(title, async () => runSteps(child.steps));
    test.tags = suite.tags.concat(tags);
    suite.addTest(scenario.test(test));
  }

  return suite;
};

function transformTable(table) {
  let str = '';
  for (const id in table.rows) {
    const cells = table.rows[id].cells;
    str += cells.map(c => c.value).map(c => c.slice(0, 15).padEnd(15)).join(' | ');
    str += '\n';
  }
  return str;
}
function addExampleInTable(exampleSteps, placeholders) {
  const steps = JSON.parse(JSON.stringify(exampleSteps));
  for (const placeholder in placeholders) {
    steps.map((step) => {
      step = { ...step };
      if (step.argument && step.argument.type === 'DataTable') {
        for (const id in step.argument.rows) {
          const cells = step.argument.rows[id].cells;
          cells.map(c => (c.value = c.value.replace(`<${placeholder}>`, placeholders[placeholder])));
        }
      }
      return step;
    });
  }
  return steps;
}
<|endoftext|>


<commit_before>define(function (require) {
    'use strict';

    var DesktopNotificationDuration = require('common/enum/desktopNotificationDuration');

    //  Use the chrome.notifications API to create rich notifications using templates and show these notifications to users in the system tray.
    //  Availability: Stable since Chrome 28, but getPermissionLevel since Chrome 32
    //  Permissions: "notifications"
    //  Note: This API is currently available on ChromeOS, Windows, and Mac.
    //  URL: https://developer.chrome.com/extensions/notifications
    var ChromeNotificationsManager = Backbone.Model.extend({
        defaults: {
            shownNotificationId: '',
            closeNotificationTimeout: null,
            defaultNotificationOptions: {
                type: 'basic',
                title: '',
                message: '',
                //  iconUrl is required -- if none given, default to Streamus' icon.
                iconUrl: 'img/streamus_icon128.png',
                //  Priority 0 is a 7-second notification by default and priority 1-2 allows for 25 seconds
                priority: 1
            },
            tabManager: null,
            settings: null
        },

        initialize: function () {
            this.listenTo(Streamus.channels.notification.commands, 'show:notification', this._showNotification);
            //  Background notifications will only show up via desktop notification, normal notification commands will be rendered in the UI if it is open.
            this.listenTo(Streamus.channels.backgroundNotification.commands, 'show:notification', this._showNotification);
        },

        _showNotification: function (notificationOptions) {
            //  Pass along the notification to the foreground if it's open. Otherwise, use desktop notifications to notify the user.
            this._isForegroundActive(this._onIsForegroundActiveResponse.bind(this, notificationOptions));
        },

        _onIsForegroundActiveResponse: function (notificationOptions, foregroundActive) {
            if (!foregroundActive) {
                var chromeNotificationOptions = _.pick(notificationOptions, ['message', 'title', 'iconUrl']);

                if (this._notificationsEnabled() ) {
                    chrome.notifications.getPermissionLevel(this._onGetPermissionLevel.bind(this, chromeNotificationOptions));
                } else {
                    Streamus.channels.error.commands.trigger('log:error', new Error('ChromeNotificationsManager cannot use notifications api'));
                }
            }
        },

        _onGetPermissionLevel: function (options, permissionLevel) {
            if (permissionLevel === 'granted') {
                this._createNotification(options);
            }
        },

        _notificationsEnabled: function () {
            return !_.isUndefined(chrome.notifications) && this.get('settings').get('desktopNotificationsEnabled');
        },

        _createNotification: function (options) {
            this._clearCloseNotificationTimeout();

            var notificationOptions = _.extend({}, this.get('defaultNotificationOptions'), options);
            //  Calling create with a notificationId will cause the existing notification to be cleared.
            chrome.notifications.create(this.get('shownNotificationId'), notificationOptions, this._onNotificationCreate.bind(this));

            this._setCloseNotificationTimeout();
        },

        _onNotificationCreate: function(notificationId) {
            this.set('shownNotificationId', notificationId);
        },

        _onCloseNotificationTimeout: function() {
            var shownNotificationId = this.get('shownNotificationId');

            if (shownNotificationId !== '') {
                this._clearNotification(shownNotificationId);
            }
        },

        _onNotificationClear: function() {
            this.set('shownNotificationId', '');
        },

        _clearNotification: function(notificationId) {
            chrome.notifications.clear(notificationId, this._onNotificationClear.bind(this));
        },

        _clearCloseNotificationTimeout: function() {
            clearTimeout(this.get('closeNotificationTimeout'));
            this.set('closeNotificationTimeout', null);
        },

        _setCloseNotificationTimeout: function () {
            var notificationDuration = this._getNotificationDuration(this.get('settings').get('desktopNotificationDuration'));
            var closeNotificationTimeout = setTimeout(this._onCloseNotificationTimeout.bind(this), notificationDuration);
            this.set('closeNotificationTimeout', closeNotificationTimeout);
        },

        _isForegroundActive: function (callback) {
            var foreground = chrome.extension.getViews({ type: "popup" });

            if (foreground.length === 0) {
                this.get('tabManager').isStreamusTabActive(function (streamusTabActive) {
                    callback(streamusTabActive);
                });
            } else {
                callback(true);
            }
        },

        //  Converts the DesktopNotificationDuration enum into milliseconds for use in setTimeout.
        _getNotificationDuration: function (desktopNotificationDuration) {
            var notificationDuration;

            switch(desktopNotificationDuration) {
                case DesktopNotificationDuration.OneSecond:
                    notificationDuration = 1000;
                    break;
                case DesktopNotificationDuration.TwoSeconds:
                    notificationDuration = 2000;
                    break;
                case DesktopNotificationDuration.ThreeSeconds:
                    notificationDuration = 3000;
                    break;
                case DesktopNotificationDuration.FourSeconds:
                    notificationDuration = 4000;
                    break;
                case DesktopNotificationDuration.FiveSeconds:
                    notificationDuration = 5000;
                    break;
                case DesktopNotificationDuration.TenSeconds:
                    notificationDuration = 10000;
                    break;
                default:
                    throw new Error('Unhandled DesktopNotificationDuration: ' + desktopNotificationDuration);
            }

            return notificationDuration;
        }
    });

    return ChromeNotificationsManager;
});<commit_msg>Error message isn't so useful now<commit_after>define(function (require) {
    'use strict';

    var DesktopNotificationDuration = require('common/enum/desktopNotificationDuration');

    //  Use the chrome.notifications API to create rich notifications using templates and show these notifications to users in the system tray.
    //  Availability: Stable since Chrome 28, but getPermissionLevel since Chrome 32
    //  Permissions: "notifications"
    //  Note: This API is currently available on ChromeOS, Windows, and Mac.
    //  URL: https://developer.chrome.com/extensions/notifications
    var ChromeNotificationsManager = Backbone.Model.extend({
        defaults: {
            shownNotificationId: '',
            closeNotificationTimeout: null,
            defaultNotificationOptions: {
                type: 'basic',
                title: '',
                message: '',
                //  iconUrl is required -- if none given, default to Streamus' icon.
                iconUrl: 'img/streamus_icon128.png',
                //  Priority 0 is a 7-second notification by default and priority 1-2 allows for 25 seconds
                priority: 1
            },
            tabManager: null,
            settings: null
        },

        initialize: function () {
            this.listenTo(Streamus.channels.notification.commands, 'show:notification', this._showNotification);
            //  Background notifications will only show up via desktop notification, normal notification commands will be rendered in the UI if it is open.
            this.listenTo(Streamus.channels.backgroundNotification.commands, 'show:notification', this._showNotification);
        },

        _showNotification: function (notificationOptions) {
            //  Pass along the notification to the foreground if it's open. Otherwise, use desktop notifications to notify the user.
            this._isForegroundActive(this._onIsForegroundActiveResponse.bind(this, notificationOptions));
        },

        _onIsForegroundActiveResponse: function (notificationOptions, foregroundActive) {
            if (!foregroundActive) {
                var chromeNotificationOptions = _.pick(notificationOptions, ['message', 'title', 'iconUrl']);

                if (this._notificationsEnabled() ) {
                    chrome.notifications.getPermissionLevel(this._onGetPermissionLevel.bind(this, chromeNotificationOptions));
                }
            }
        },

        _onGetPermissionLevel: function (options, permissionLevel) {
            if (permissionLevel === 'granted') {
                this._createNotification(options);
            }
        },

        _notificationsEnabled: function () {
            return !_.isUndefined(chrome.notifications) && this.get('settings').get('desktopNotificationsEnabled');
        },

        _createNotification: function (options) {
            this._clearCloseNotificationTimeout();

            var notificationOptions = _.extend({}, this.get('defaultNotificationOptions'), options);
            //  Calling create with a notificationId will cause the existing notification to be cleared.
            chrome.notifications.create(this.get('shownNotificationId'), notificationOptions, this._onNotificationCreate.bind(this));

            this._setCloseNotificationTimeout();
        },

        _onNotificationCreate: function(notificationId) {
            this.set('shownNotificationId', notificationId);
        },

        _onCloseNotificationTimeout: function() {
            var shownNotificationId = this.get('shownNotificationId');

            if (shownNotificationId !== '') {
                this._clearNotification(shownNotificationId);
            }
        },

        _onNotificationClear: function() {
            this.set('shownNotificationId', '');
        },

        _clearNotification: function(notificationId) {
            chrome.notifications.clear(notificationId, this._onNotificationClear.bind(this));
        },

        _clearCloseNotificationTimeout: function() {
            clearTimeout(this.get('closeNotificationTimeout'));
            this.set('closeNotificationTimeout', null);
        },

        _setCloseNotificationTimeout: function () {
            var notificationDuration = this._getNotificationDuration(this.get('settings').get('desktopNotificationDuration'));
            var closeNotificationTimeout = setTimeout(this._onCloseNotificationTimeout.bind(this), notificationDuration);
            this.set('closeNotificationTimeout', closeNotificationTimeout);
        },

        _isForegroundActive: function (callback) {
            var foreground = chrome.extension.getViews({ type: "popup" });

            if (foreground.length === 0) {
                this.get('tabManager').isStreamusTabActive(function (streamusTabActive) {
                    callback(streamusTabActive);
                });
            } else {
                callback(true);
            }
        },

        //  Converts the DesktopNotificationDuration enum into milliseconds for use in setTimeout.
        _getNotificationDuration: function (desktopNotificationDuration) {
            var notificationDuration;

            switch(desktopNotificationDuration) {
                case DesktopNotificationDuration.OneSecond:
                    notificationDuration = 1000;
                    break;
                case DesktopNotificationDuration.TwoSeconds:
                    notificationDuration = 2000;
                    break;
                case DesktopNotificationDuration.ThreeSeconds:
                    notificationDuration = 3000;
                    break;
                case DesktopNotificationDuration.FourSeconds:
                    notificationDuration = 4000;
                    break;
                case DesktopNotificationDuration.FiveSeconds:
                    notificationDuration = 5000;
                    break;
                case DesktopNotificationDuration.TenSeconds:
                    notificationDuration = 10000;
                    break;
                default:
                    throw new Error('Unhandled DesktopNotificationDuration: ' + desktopNotificationDuration);
            }

            return notificationDuration;
        }
    });

    return ChromeNotificationsManager;
});<|endoftext|>


<commit_before>import { createActions } from 'framework/store'
import { EntityType } from 'model'
import stateOperations from './stateOperations'

let actions = createActions({
    // Participant:
    selectParticipant: participantId => ({ participantId }),
    addParticipant: (participantId, data) => ({ participantId, data }),
    startEditingParticipant: participantId => ({ participantId }),
    editParticipant: (participantId, updateCommand) => ({ participantId, updateCommand }),
    submitEditingParticipant: () => ({}),
    cancelEditingParticipant: () => ({}),
    startDeletingParticipant: participantId => ({ participantId }),
    submitDeletingParticipant: () => ({}),
    cancelDeletingParticipant: () => ({}),
    // Item:
    selectItem: itemId => ({ itemId }),
    addItem: itemId => ({ itemId }),
    updateItem: (itemId, updateCommand) => ({ itemId, updateCommand })
});

let reducer = (state, action) => {
    switch (action.type) {

    // Participant:
    case actions.selectParticipant.type:
        return stateOperations.selectEntity(EntityType.participant, action.data.participantId, state);

    case actions.addParticipant.type:
        return stateOperations.addEntity(EntityType.participant, action.data.participantId, action.data.data, state);

    case actions.startEditingParticipant.type:
        return stateOperations.editEntity.start(EntityType.participant, action.data.participantId, state);

    case actions.editParticipant.type:
        return stateOperations.editEntity.updateFocused(EntityType.participant, action.data.updateCommand, state);

    case actions.submitEditingParticipant.type:
        return stateOperations.editEntity.submitFocused(EntityType.participant, state);

    case actions.cancelEditingParticipant.type:
        return stateOperations.editEntity.cancelFocused(EntityType.participant, state);

    case actions.startDeletingParticipant.type:
        return stateOperations.deleteEntity.start(EntityType.participant, action.data.participantId, state);

    case actions.submitDeletingParticipant.type:
        return stateOperations.deleteEntity.submitFocused(EntityType.participant, state);

    case actions.cancelDeletingParticipant.type:
        return stateOperations.deleteEntity.cancelFocused(EntityType.participant, state);

    // Item:
    case actions.selectItem.type:
        return stateOperations.selectItem(action.data.itemId, state);

    case actions.addItem.type:
        return stateOperations.addItem(action.data.itemId, state);

    case actions.updateItem.type:
        return stateOperations.updateItem(action.data.itemId, action.data.updateCommand, state);

    default:
        return state;
    }
};

export { actions, reducer };
<commit_msg>Created generic actions<commit_after>import { createActions } from 'framework/store'
import { EntityType } from 'model'
import stateOperations from './stateOperations'

let actions = createActions({
    selectEntity: (entity, id) => ({ entity, id }),
    addEntity: (entity, id, data) => ({ entity, id, data }),
    editEntity_start: (entity, id) => ({ entity, id }),
    editEntity_updateFocused: (entity, updateCommand) => ({ entity, updateCommand }),
    editEntity_submitFocused: entity => ({ entity }),
    editEntity_cancelFocused: entity => ({ entity }),
    deleteEntity_start: (entity, id) => ({ entity, id }),
    deleteEntity_submitFocused: entity => ({ entity }),
    deleteEntity_cancelFocused: entity => ({ entity }),
    // Participant:
    selectParticipant: participantId => ({ participantId }),
    addParticipant: (participantId, data) => ({ participantId, data }),
    startEditingParticipant: participantId => ({ participantId }),
    editParticipant: (participantId, updateCommand) => ({ participantId, updateCommand }),
    submitEditingParticipant: () => ({}),
    cancelEditingParticipant: () => ({}),
    startDeletingParticipant: participantId => ({ participantId }),
    submitDeletingParticipant: () => ({}),
    cancelDeletingParticipant: () => ({}),
    // Item:
    selectItem: itemId => ({ itemId }),
    addItem: itemId => ({ itemId }),
    updateItem: (itemId, updateCommand) => ({ itemId, updateCommand })
});

let reducer = (state, action) => {
    switch (action.type) {

    case actions.selectEntity.type:
        return stateOperations.selectEntity(action.data.entity, action.data.id, state);

    case actions.addEntity.type:
        return stateOperations.addEntity(action.data.entity, action.data.id, action.data.data, state);

    case actions.editEntity_start.type:
        return stateOperations.editEntity.start(action.data.entity, action.data.id, state);

    case actions.editEntity_updateFocused.type:
        return stateOperations.editEntity.updateFocused(action.data.entity, action.data.updateCommand, state);

    case actions.editEntity_submitFocused.type:
        return stateOperations.editEntity.submitFocused(action.data.entity, state);

    case actions.editEntity_cancelFocused.type:
        return stateOperations.editEntity.cancelFocused(action.data.entity, state);

    case actions.deleteEntity_start.type:
        return stateOperations.deleteEntity.start(action.data.entity, action.data.id, state);

    case actions.deleteEntity_submitFocused.type:
        return stateOperations.deleteEntity.submitFocused(action.data.entity, state);

    case actions.deleteEntity_cancelFocused.type:
        return stateOperations.deleteEntity.cancelFocused(action.data.entity, state);

    // Participant:
    case actions.selectParticipant.type:
        return stateOperations.selectEntity(EntityType.participant, action.data.participantId, state);

    case actions.addParticipant.type:
        return stateOperations.addEntity(EntityType.participant, action.data.participantId, action.data.data, state);

    case actions.startEditingParticipant.type:
        return stateOperations.editEntity.start(EntityType.participant, action.data.participantId, state);

    case actions.editParticipant.type:
        return stateOperations.editEntity.updateFocused(EntityType.participant, action.data.updateCommand, state);

    case actions.submitEditingParticipant.type:
        return stateOperations.editEntity.submitFocused(EntityType.participant, state);

    case actions.cancelEditingParticipant.type:
        return stateOperations.editEntity.cancelFocused(EntityType.participant, state);

    case actions.startDeletingParticipant.type:
        return stateOperations.deleteEntity.start(EntityType.participant, action.data.participantId, state);

    case actions.submitDeletingParticipant.type:
        return stateOperations.deleteEntity.submitFocused(EntityType.participant, state);

    case actions.cancelDeletingParticipant.type:
        return stateOperations.deleteEntity.cancelFocused(EntityType.participant, state);

    // Item:
    case actions.selectItem.type:
        return stateOperations.selectItem(action.data.itemId, state);

    case actions.addItem.type:
        return stateOperations.addItem(action.data.itemId, state);

    case actions.updateItem.type:
        return stateOperations.updateItem(action.data.itemId, action.data.updateCommand, state);

    default:
        return state;
    }
};

export { actions, reducer };
<|endoftext|>


<commit_before>class @Sequence
  constructor: (element) ->
    @updatedProblems = {}
    @requestToken = $(element).data('request-token')
    @el = $(element).find('.sequence')
    @path = $('.path')
    @contents = @$('.seq_contents')
    @content_container = @$('#seq_content')
    @sr_container = @$('.sr-is-focusable')
    @num_contents = @contents.length
    @id = @el.data('id')
    @ajaxUrl = @el.data('ajax-url')
    @nextUrl = @el.data('next-url')
    @prevUrl = @el.data('prev-url')
    @base_page_title = " | " + document.title
    @initProgress()
    @bind()
    @render parseInt(@el.data('position'))

  $: (selector) ->
    $(selector, @el)

  bind: ->
    @$('#sequence-list .nav-item').click @goto
    @el.on 'bookmark:add', @addBookmarkIconToActiveNavItem
    @el.on 'bookmark:remove', @removeBookmarkIconFromActiveNavItem
    @$('#sequence-list .nav-item').on('focus mouseenter', @displayTabTooltip)
    @$('#sequence-list .nav-item').on('blur mouseleave', @hideTabTooltip)

  displayTabTooltip: (event) =>
    $(event.currentTarget).find('.sequence-tooltip').removeClass('sr')

  hideTabTooltip: (event) =>
    $(event.currentTarget).find('.sequence-tooltip').addClass('sr')

  initProgress: ->
    @progressTable = {}  # "#problem_#{id}" -> progress

  updatePageTitle: ->
    # update the page title to include the current section
    position_link = @link_for(@position)
    if position_link and position_link.data('page-title')
        document.title = position_link.data('page-title') + @base_page_title

  hookUpContentStateChangeEvent: ->
    $('.problems-wrapper').bind(
      'contentChanged',
      (event, problem_id, new_content_state, new_state) =>
        @addToUpdatedProblems problem_id, new_content_state, new_state
    )

  addToUpdatedProblems: (problem_id, new_content_state, new_state) =>
    # Used to keep updated problem's state temporarily.
    # params:
    #   'problem_id' is problem id.
    #   'new_content_state' is the updated content of the problem.
    #   'new_state' is the updated state of the problem.

    # initialize for the current sequence if there isn't any updated problem
    # for this position.
    if not @anyUpdatedProblems @position
      @updatedProblems[@position] = {}

    # Now, put problem content and score against problem id for current active sequence.
    @updatedProblems[@position][problem_id] = [new_content_state, new_state]

  anyUpdatedProblems:(position) ->
    # check for the updated problems for given sequence position.
    # params:
    #   'position' can be any sequence position.
    return @updatedProblems[position] != undefined

  hookUpProgressEvent: ->
    $('.problems-wrapper').bind 'progressChanged', @updateProgress

  mergeProgress: (p1, p2) ->
    # if either is "NA", return the other one
    if p1 == "NA"
      return p2
    if p2 == "NA"
      return p1

    # Both real progresses
    if p1 == "done" and p2 == "done"
      return "done"

    # not done, so if any progress on either, in_progress
    w1 = p1 == "done" or p1 == "in_progress"
    w2 = p2 == "done" or p2 == "in_progress"
    if w1 or w2
      return "in_progress"

    return "none"

  updateProgress: =>
    new_progress = "NA"
    _this = this
    $('.problems-wrapper').each (index) ->
      progress = $(this).data 'progress_status'
      new_progress = _this.mergeProgress progress, new_progress

    @progressTable[@position] = new_progress

  enableButton: (button_class, button_action) ->
    @$(button_class).removeClass('disabled').removeAttr('disabled').click(button_action)

  disableButton: (button_class) ->
    @$(button_class).addClass('disabled').attr('disabled', true)

  setButtonLabel: (button_class, button_label) ->
    @$(button_class + ' .sr').html(button_label)

  updateButtonState: (button_class, button_action, action_label_prefix, is_at_boundary, boundary_url) ->
    if is_at_boundary and boundary_url == 'None'
      @disableButton(button_class)
    else
      button_label = action_label_prefix + (if is_at_boundary then ' Subsection' else ' Unit')
      @setButtonLabel(button_class, button_label)
      @enableButton(button_class, button_action)

  toggleArrows: =>
    @$('.sequence-nav-button').unbind('click')

    # previous button
    is_first_tab = @position == 1
    previous_button_class = '.sequence-nav-button.button-previous'
    @updateButtonState(
      previous_button_class,
      @selectPrevious,
      'Previous',
      is_first_tab,
      @prevUrl
    )

    # next button
    is_last_tab = @position >= @contents.length  # use inequality in case contents.length is 0 and position is 1.
    next_button_class = '.sequence-nav-button.button-next'
    @updateButtonState(
      next_button_class,
      @selectNext,
      'Next',
      is_last_tab,
      @nextUrl
    )

  render: (new_position) ->
    if @position != new_position
      if @position != undefined
        @mark_visited @position
        modx_full_url = "#{@ajaxUrl}/goto_position"
        $.postWithPrefix modx_full_url, position: new_position

      # On Sequence change, fire custom event "sequence:change" on element.
      # Added for aborting video bufferization, see ../video/10_main.js
      @el.trigger "sequence:change"
      @mark_active new_position

      current_tab = @contents.eq(new_position - 1)

      bookmarked = if @el.find('.active .bookmark-icon').hasClass('bookmarked') then true else false
      @content_container.html(current_tab.text()).attr("aria-labelledby", current_tab.attr("aria-labelledby")).data('bookmarked', bookmarked)

      # update the data-attributes with latest contents only for updated problems.
      if @anyUpdatedProblems new_position
        $.each @updatedProblems[new_position], (problem_id, latest_data) =>
          latest_content = latest_data[0]
          latest_response = latest_data[1]
          @content_container
          .find("[data-problem-id='#{ problem_id }']")
          .data('content', latest_content)
          .data('problem-score', latest_response.current_score)
          .data('problem-total-possible', latest_response.total_possible)
          .data('attempts-used', latest_response.attempts_used)

      XBlock.initializeBlocks(@content_container, @requestToken)

      window.update_schematics() # For embedded circuit simulator exercises in 6.002x

      @position = new_position
      @toggleArrows()
      @hookUpContentStateChangeEvent()
      @hookUpProgressEvent()
      @updatePageTitle()

      sequence_links = @content_container.find('a.seqnav')
      sequence_links.click @goto

      @path.text(@el.find('.nav-item.active').data('path'))

      @sr_container.focus()

  goto: (event) =>
    event.preventDefault()
    if $(event.currentTarget).hasClass 'seqnav' # Links from courseware ..., was .target
      new_position = $(event.currentTarget).attr('href')
    else # Tab links generated by backend template
      new_position = $(event.currentTarget).data('element')

    if (1 <= new_position) and (new_position <= @num_contents)
      is_bottom_nav = $(event.target).closest('nav[class="sequence-bottom"]').length > 0
      if is_bottom_nav
        widget_placement = 'bottom'
      else
        widget_placement = 'top'
      Logger.log "edx.ui.lms.sequence.tab_selected",  # Formerly known as seq_goto
        current_tab: @position
        target_tab: new_position
        tab_count: @num_contents
        id: @id
        widget_placement: widget_placement

      # On Sequence change, destroy any existing polling thread
      # for queued submissions, see ../capa/display.js
      if window.queuePollerID
        window.clearTimeout(window.queuePollerID)
        delete window.queuePollerID

      @render new_position
    else
      alert_template = gettext("Sequence error! Cannot navigate to %(tab_name)s in the current SequenceModule. Please contact the course staff.")
      alert_text = interpolate(alert_template, {tab_name: new_position}, true)
      alert alert_text

  selectNext: (event) => @_change_sequential 'next', event

  selectPrevious: (event) => @_change_sequential 'previous', event

  # `direction` can be 'previous' or 'next'
  _change_sequential: (direction, event) =>
    # silently abort if direction is invalid.
    return unless direction in ['previous', 'next']

    event.preventDefault()

    analytics_event_name = "edx.ui.lms.sequence.#{direction}_selected"
    is_bottom_nav = $(event.target).closest('nav[class="sequence-bottom"]').length > 0

    if is_bottom_nav
      widget_placement = 'bottom'
    else
      widget_placement = 'top'

    Logger.log analytics_event_name,  # Formerly known as seq_next and seq_prev
      id: @id
      current_tab: @position
      tab_count: @num_contents
      widget_placement: widget_placement

    if (direction == 'next') and (@position >= @contents.length)
      window.location.href = @nextUrl
    else if (direction == 'previous') and (@position == 1)
      window.location.href = @prevUrl
    else
      # If the bottom nav is used, scroll to the top of the page on change.
      if is_bottom_nav
        $.scrollTo 0, 150
      offset =
        next: 1
        previous: -1
      new_position = @position + offset[direction]
      @render new_position

  link_for: (position) ->
    @$("#sequence-list .nav-item[data-element=#{position}]")

  mark_visited: (position) ->
    # Don't overwrite class attribute to avoid changing Progress class
    element = @link_for(position)
    element.removeClass("inactive")
    .removeClass("active")
    .addClass("visited")

  mark_active: (position) ->
    # Don't overwrite class attribute to avoid changing Progress class
    element = @link_for(position)
    element.removeClass("inactive")
    .removeClass("visited")
    .addClass("active")

  addBookmarkIconToActiveNavItem: (event) =>
    event.preventDefault()
    @el.find('.nav-item.active .bookmark-icon').removeClass('is-hidden').addClass('bookmarked')
    @el.find('.nav-item.active .bookmark-icon-sr').text(gettext('Bookmarked'))

  removeBookmarkIconFromActiveNavItem: (event) =>
    event.preventDefault()
    @el.find('.nav-item.active .bookmark-icon').removeClass('bookmarked').addClass('is-hidden')
    @el.find('.nav-item.active .bookmark-icon-sr').text('')
<commit_msg>Convert display.js to javascript, preserving comments<commit_after>// Generated by CoffeeScript 1.6.1
(function() {
  var _this = this;

  this.Sequence = (function() {

    function Sequence(element) {
      var _this = this;
      this.removeBookmarkIconFromActiveNavItem = function(event) {
        return Sequence.prototype.removeBookmarkIconFromActiveNavItem.apply(_this, arguments);
      };
      this.addBookmarkIconToActiveNavItem = function(event) {
        return Sequence.prototype.addBookmarkIconToActiveNavItem.apply(_this, arguments);
      };
      this._change_sequential = function(direction, event) {
        return Sequence.prototype._change_sequential.apply(_this, arguments);
      };
      this.selectPrevious = function(event) {
        return Sequence.prototype.selectPrevious.apply(_this, arguments);
      };
      this.selectNext = function(event) {
        return Sequence.prototype.selectNext.apply(_this, arguments);
      };
      this.goto = function(event) {
        return Sequence.prototype.goto.apply(_this, arguments);
      };
      this.toggleArrows = function() {
        return Sequence.prototype.toggleArrows.apply(_this, arguments);
      };
      this.updateProgress = function() {
        return Sequence.prototype.updateProgress.apply(_this, arguments);
      };
      this.addToUpdatedProblems = function(problem_id, new_content_state, new_state) {
        return Sequence.prototype.addToUpdatedProblems.apply(_this, arguments);
      };
      this.hideTabTooltip = function(event) {
        return Sequence.prototype.hideTabTooltip.apply(_this, arguments);
      };
      this.displayTabTooltip = function(event) {
        return Sequence.prototype.displayTabTooltip.apply(_this, arguments);
      };
      this.updatedProblems = {};
      this.requestToken = $(element).data('request-token');
      this.el = $(element).find('.sequence');
      this.path = $('.path');
      this.contents = this.$('.seq_contents');
      this.content_container = this.$('#seq_content');
      this.sr_container = this.$('.sr-is-focusable');
      this.num_contents = this.contents.length;
      this.id = this.el.data('id');
      this.ajaxUrl = this.el.data('ajax-url');
      this.nextUrl = this.el.data('next-url');
      this.prevUrl = this.el.data('prev-url');
      this.base_page_title = " | " + document.title;
      this.initProgress();
      this.bind();
      this.render(parseInt(this.el.data('position')));
    }

    Sequence.prototype.$ = function(selector) {
      return $(selector, this.el);
    };

    Sequence.prototype.bind = function() {
      this.$('#sequence-list .nav-item').click(this.goto);
      this.el.on('bookmark:add', this.addBookmarkIconToActiveNavItem);
      this.el.on('bookmark:remove', this.removeBookmarkIconFromActiveNavItem);
      this.$('#sequence-list .nav-item').on('focus mouseenter', this.displayTabTooltip);
      return this.$('#sequence-list .nav-item').on('blur mouseleave', this.hideTabTooltip);
    };

    Sequence.prototype.displayTabTooltip = function(event) {
      return $(event.currentTarget).find('.sequence-tooltip').removeClass('sr');
    };

    Sequence.prototype.hideTabTooltip = function(event) {
      return $(event.currentTarget).find('.sequence-tooltip').addClass('sr');
    };

    Sequence.prototype.initProgress = function() {
      /*
      "#problem_#{id}" -> progress
      */
      return this.progressTable = {};
    };

    Sequence.prototype.updatePageTitle = function() {
      /*
      update the page title to include the current section
      */

      var position_link;
      position_link = this.link_for(this.position);
      if (position_link && position_link.data('page-title')) {
        return document.title = position_link.data('page-title') + this.base_page_title;
      }
    };

    Sequence.prototype.hookUpContentStateChangeEvent = function() {
      var _this = this;
      return $('.problems-wrapper').bind('contentChanged', function(event, problem_id, new_content_state, new_state) {
        return _this.addToUpdatedProblems(problem_id, new_content_state, new_state);
      });
    };

    Sequence.prototype.addToUpdatedProblems = function(problem_id, new_content_state, new_state) {
      /*
      Used to keep updated problem's state temporarily.
      */

      /*
      params:
      */

      /*
        'problem_id' is problem id.
      */

      /*
        'new_content_state' is the updated content of the problem.
      */

      /*
        'new_state' is the updated state of the problem.
      */

      /*
      initialize for the current sequence if there isn't any updated problem
      */

      /*
      for this position.
      */
      if (!this.anyUpdatedProblems(this.position)) {
        this.updatedProblems[this.position] = {};
      }
      /*
      Now, put problem content and score against problem id for current active sequence.
      */

      return this.updatedProblems[this.position][problem_id] = [new_content_state, new_state];
    };

    Sequence.prototype.anyUpdatedProblems = function(position) {
      /*
      check for the updated problems for given sequence position.
      */

      /*
      params:
      */

      /*
        'position' can be any sequence position.
      */
      return this.updatedProblems[position] !== void 0;
    };

    Sequence.prototype.hookUpProgressEvent = function() {
      return $('.problems-wrapper').bind('progressChanged', this.updateProgress);
    };

    Sequence.prototype.mergeProgress = function(p1, p2) {
      /*
      if either is "NA", return the other one
      */

      var w1, w2;
      if (p1 === "NA") {
        return p2;
      }
      if (p2 === "NA") {
        return p1;
      }
      /*
      Both real progresses
      */

      if (p1 === "done" && p2 === "done") {
        return "done";
      }
      /*
      not done, so if any progress on either, in_progress
      */

      w1 = p1 === "done" || p1 === "in_progress";
      w2 = p2 === "done" || p2 === "in_progress";
      if (w1 || w2) {
        return "in_progress";
      }
      return "none";
    };

    Sequence.prototype.updateProgress = function() {
      var new_progress;
      new_progress = "NA";
      _this = this;
      $('.problems-wrapper').each(function(index) {
        var progress;
        progress = $(this).data('progress_status');
        return new_progress = _this.mergeProgress(progress, new_progress);
      });
      return this.progressTable[this.position] = new_progress;
    };

    Sequence.prototype.enableButton = function(button_class, button_action) {
      return this.$(button_class).removeClass('disabled').removeAttr('disabled').click(button_action);
    };

    Sequence.prototype.disableButton = function(button_class) {
      return this.$(button_class).addClass('disabled').attr('disabled', true);
    };

    Sequence.prototype.setButtonLabel = function(button_class, button_label) {
      return this.$(button_class + ' .sr').html(button_label);
    };

    Sequence.prototype.updateButtonState = function(button_class, button_action, action_label_prefix, is_at_boundary, boundary_url) {
      var button_label;
      if (is_at_boundary && boundary_url === 'None') {
        return this.disableButton(button_class);
      } else {
        button_label = action_label_prefix + (is_at_boundary ? ' Subsection' : ' Unit');
        this.setButtonLabel(button_class, button_label);
        return this.enableButton(button_class, button_action);
      }
    };

    Sequence.prototype.toggleArrows = function() {
      var is_first_tab, is_last_tab, next_button_class, previous_button_class;
      this.$('.sequence-nav-button').unbind('click');
      /*
      previous button
      */

      is_first_tab = this.position === 1;
      previous_button_class = '.sequence-nav-button.button-previous';
      this.updateButtonState(previous_button_class, this.selectPrevious, 'Previous', is_first_tab, this.prevUrl);
      /*
      next button
      */

      /*
      use inequality in case contents.length is 0 and position is 1.
      */

      is_last_tab = this.position >= this.contents.length;
      next_button_class = '.sequence-nav-button.button-next';
      return this.updateButtonState(next_button_class, this.selectNext, 'Next', is_last_tab, this.nextUrl);
    };

    Sequence.prototype.render = function(new_position) {
      var bookmarked, current_tab, modx_full_url, sequence_links,
        _this = this;
      if (this.position !== new_position) {
        if (this.position !== void 0) {
          this.mark_visited(this.position);
          modx_full_url = "" + this.ajaxUrl + "/goto_position";
          $.postWithPrefix(modx_full_url, {
            position: new_position
          });
        }
        /*
        On Sequence change, fire custom event "sequence:change" on element.
        */

        /*
        Added for aborting video bufferization, see ../video/10_main.js
        */

        this.el.trigger("sequence:change");
        this.mark_active(new_position);
        current_tab = this.contents.eq(new_position - 1);
        bookmarked = this.el.find('.active .bookmark-icon').hasClass('bookmarked') ? true : false;
        this.content_container.html(current_tab.text()).attr("aria-labelledby", current_tab.attr("aria-labelledby")).data('bookmarked', bookmarked);
        /*
        update the data-attributes with latest contents only for updated problems.
        */

        if (this.anyUpdatedProblems(new_position)) {
          $.each(this.updatedProblems[new_position], function(problem_id, latest_data) {
            var latest_content, latest_response;
            latest_content = latest_data[0];
            latest_response = latest_data[1];
            return _this.content_container.find("[data-problem-id='" + problem_id + "']").data('content', latest_content).data('problem-score', latest_response.current_score).data('problem-total-possible', latest_response.total_possible).data('attempts-used', latest_response.attempts_used);
          });
        }
        XBlock.initializeBlocks(this.content_container, this.requestToken);
        /*
        For embedded circuit simulator exercises in 6.002x
        */

        window.update_schematics();
        this.position = new_position;
        this.toggleArrows();
        this.hookUpContentStateChangeEvent();
        this.hookUpProgressEvent();
        this.updatePageTitle();
        sequence_links = this.content_container.find('a.seqnav');
        sequence_links.click(this.goto);
        this.path.text(this.el.find('.nav-item.active').data('path'));
        return this.sr_container.focus();
      }
    };

    Sequence.prototype.goto = function(event) {
      var alert_template, alert_text, is_bottom_nav, new_position, widget_placement;
      event.preventDefault();
      /*
      Links from courseware ..., was .target
      */

      if ($(event.currentTarget).hasClass('seqnav')) {
        new_position = $(event.currentTarget).attr('href');
        /*
        Tab links generated by backend template
        */

      } else {
        new_position = $(event.currentTarget).data('element');
      }
      if ((1 <= new_position) && (new_position <= this.num_contents)) {
        is_bottom_nav = $(event.target).closest('nav[class="sequence-bottom"]').length > 0;
        if (is_bottom_nav) {
          widget_placement = 'bottom';
        } else {
          widget_placement = 'top';
        }
        /*
        Formerly known as seq_goto
        */

        Logger.log("edx.ui.lms.sequence.tab_selected", {
          current_tab: this.position,
          target_tab: new_position,
          tab_count: this.num_contents,
          id: this.id,
          widget_placement: widget_placement
        });
        /*
        On Sequence change, destroy any existing polling thread
        */

        /*
        for queued submissions, see ../capa/display.js
        */

        if (window.queuePollerID) {
          window.clearTimeout(window.queuePollerID);
          delete window.queuePollerID;
        }
        return this.render(new_position);
      } else {
        alert_template = gettext("Sequence error! Cannot navigate to %(tab_name)s in the current SequenceModule. Please contact the course staff.");
        alert_text = interpolate(alert_template, {
          tab_name: new_position
        }, true);
        return alert(alert_text);
      }
    };

    Sequence.prototype.selectNext = function(event) {
      return this._change_sequential('next', event);
    };

    Sequence.prototype.selectPrevious = function(event) {
      return this._change_sequential('previous', event);
    };

    /*
    `direction` can be 'previous' or 'next'
    */

    Sequence.prototype._change_sequential = function(direction, event) {
      /*
      silently abort if direction is invalid.
      */

      var analytics_event_name, is_bottom_nav, new_position, offset, widget_placement;
      if (direction !== 'previous' && direction !== 'next') {
        return;
      }
      event.preventDefault();
      analytics_event_name = "edx.ui.lms.sequence." + direction + "_selected";
      is_bottom_nav = $(event.target).closest('nav[class="sequence-bottom"]').length > 0;
      if (is_bottom_nav) {
        widget_placement = 'bottom';
      } else {
        widget_placement = 'top';
      }
      /*
      Formerly known as seq_next and seq_prev
      */

      Logger.log(analytics_event_name, {
        id: this.id,
        current_tab: this.position,
        tab_count: this.num_contents,
        widget_placement: widget_placement
      });
      if ((direction === 'next') && (this.position >= this.contents.length)) {
        return window.location.href = this.nextUrl;
      } else if ((direction === 'previous') && (this.position === 1)) {
        return window.location.href = this.prevUrl;
      } else {
        /*
        If the bottom nav is used, scroll to the top of the page on change.
        */

        if (is_bottom_nav) {
          $.scrollTo(0, 150);
        }
        offset = {
          next: 1,
          previous: -1
        };
        new_position = this.position + offset[direction];
        return this.render(new_position);
      }
    };

    Sequence.prototype.link_for = function(position) {
      return this.$("#sequence-list .nav-item[data-element=" + position + "]");
    };

    Sequence.prototype.mark_visited = function(position) {
      /*
      Don't overwrite class attribute to avoid changing Progress class
      */

      var element;
      element = this.link_for(position);
      return element.removeClass("inactive").removeClass("active").addClass("visited");
    };

    Sequence.prototype.mark_active = function(position) {
      /*
      Don't overwrite class attribute to avoid changing Progress class
      */

      var element;
      element = this.link_for(position);
      return element.removeClass("inactive").removeClass("visited").addClass("active");
    };

    Sequence.prototype.addBookmarkIconToActiveNavItem = function(event) {
      event.preventDefault();
      this.el.find('.nav-item.active .bookmark-icon').removeClass('is-hidden').addClass('bookmarked');
      return this.el.find('.nav-item.active .bookmark-icon-sr').text(gettext('Bookmarked'));
    };

    Sequence.prototype.removeBookmarkIconFromActiveNavItem = function(event) {
      event.preventDefault();
      this.el.find('.nav-item.active .bookmark-icon').removeClass('bookmarked').addClass('is-hidden');
      return this.el.find('.nav-item.active .bookmark-icon-sr').text('');
    };

    return Sequence;

  })();

}).call(this);
<|endoftext|>


<commit_before>import { combineReducers } from "redux";
import { actions } from './actions'

let input = (state = { value: '', length: 0 }, action) => {
    if (action.type === actions.CHANGE_INPUT) {
        return {
            value: action.value,
            length: action.value.length
        };
    } else {
        return state;
    }
};

let info = (state = { inputLength: 0 }, action) => {
    if (action.type === actions.UPDATE_INPUT_INFO) {
        return {
            inputLength: action.length
        };
    } else {
        return state;
    }
};

export default combineReducers({ input, info });
<commit_msg>Refactored Info reducer to take input length from state<commit_after>import { actions } from './actions'

export default (state, action) => {
    switch (action.type) {
    case actions.CHANGE_INPUT:
        let input = {
            value: action.value,
            length: action.value.length
        };

        return Object.assign({}, state, { input });
    case actions.UPDATE_INPUT_INFO:
        let info = {
            inputLength: state.input.value.length
        };

        return Object.assign({}, state, { info });
    default:
        return state;
    }
};
<|endoftext|>


<commit_before>"use strict";

const failchat = {
    maxMessages: 50,
    messageCount: 0,
    iconsPath: "../_shared/icons/", //could be overrided in skin.html
    origins: ["peka2tv", "twitch", "goodgame", "youtube", "cybergame"],
    deletedTextPlaceholder: "message deleted"
};

$(function () {
    const socket = new ReconnectingWebSocket("ws://localhost:10880");
    socket.maxReconnectInterval = 5000;
    failchat.socket = socket;

    const bodyWrapper = $("#body-wrapper");
    const messageContainer = $("#message-container");
    const scroller = $(failchat.baronParams.scroller);
    const scrollBar = $(failchat.baronParams.bar);
    let autoScroll = true;
    const nativeClient = (navigator.userAgent.search("failchat") >= 0);
    // var nativeClient = true; //todo think about debugging
    let showStatusMessages = true; // show if origin-status message received before client-configuration message

    // viewers bar
    const viewersBar = $(".viewers-bar");
    const viewersCountItems = {};

    failchat.origins.forEach(function (origin) {
        const viewersBarHtml = templates.originViewersBar.render({origin: origin, iconsPath: failchat.iconsPath});
        $(".viewers-origins").append(viewersBarHtml);
        viewersCountItems[origin] = {
            bar: $("#" + origin + "-origin"),
            counter: $("#" + origin + "-viewers")
        };

        //set default value
        viewersCountItems[origin].counter.text("?")
    });

    // scroll bar
    baron(failchat.baronParams);

    // auto scroll
    new ResizeSensor(messageContainer, function() {
        if (autoScroll) {
            scroller.scrollTop(messageContainer.height());
        }
    });

    socket.onopen = function () {
        const connectedMessage = {"origin": "failchat", "status": "connected", "timestamp": Date.now()};
        handleStatusMessage(connectedMessage);
        appendToMessageContainer(connectedMessage);

        socket.send(JSON.stringify({type: "client-configuration", content: {}}));
        socket.send(JSON.stringify({type: "viewers-count", content: {}}));
    };

    socket.onclose = function () {
        const disconnectedMessage = {origin: "failchat", status: "disconnected", timestamp: Date.now()};
        handleStatusMessage(disconnectedMessage);
        appendToMessageContainer(disconnectedMessage);
    };

    socket.onmessage = function (event) {
        const wsm = JSON.parse(event.data);
        const type = wsm.type;
        const content = wsm.content;

        switch (type) {
            case "message":
                handleChatMessage(content);
                break;
            case "origin-status":
                handleStatusMessage(content);
                break;
            case "delete-message":
                handleDeleteMessage(content);
                break;
            case "client-configuration":
                handleClientConfigurationMessage(content);
                break;
        }

        if (content.textHtml !== undefined) {
            appendToMessageContainer(wsm.content);
            return
        }

        if (!nativeClient) return;

        switch (type) {
            case "viewers-count":
                updateViewersValues(content);
                break;
        }
    };

    function handleChatMessage(content) {
        const elementsArray = content.elements;

        for (let i = 0; i < elementsArray.length; i++) {
            const element = elementsArray[i];
            let elementHtml;
            switch(element.type) {
                case "emoticon":
                    if (element.format === "vector") {
                        elementHtml = templates.vectorSmile.render(element);
                    } else {
                        elementHtml = templates.rasterSmile.render(element);
                    }
                    break;
                case "link":
                    elementHtml = templates.link.render(element);
                    break;
                case "image":
                    elementHtml = templates.image.render(element);
                    break;
            }

            content.text = content.text.replace("{!" + i + "}", elementHtml);
        }

        content.iconsPath = failchat.iconsPath;
        content.textHtml = templates.message.render(content);
    }

    function handleStatusMessage(content) {
        if (!showStatusMessages) return;

        content.iconsPath = failchat.iconsPath;
        content.textHtml = templates.statusMessage.render(content);
    }

    function handleClientConfigurationMessage(content) {
        const statusMessageMode = content.statusMessageMode;
        if ((statusMessageMode === "everywhere") ||
            (nativeClient && statusMessageMode === "native_client")) {
            showStatusMessages = true;
        } else {
            showStatusMessages = false;
        }

        if (nativeClient) {
            bodyWrapper.css("background-color", "rgba(" + hexToRgba(content.nativeClientBgColor.substring(1)) + ")");
        } else {
            bodyWrapper.css("background-color", "rgba(" + hexToRgba(content.externalClientBgColor.substring(1)) + ")");
        }

        if (!nativeClient) return;
        // handle viewers bar configuration

        if (content.showViewersCount === true) {
            viewersBar.addClass("viewers-bar-on");
        } else {
            viewersBar.removeClass("viewers-bar-on");
        }

        const enabledOrigins = content.enabledOrigins;
        failchat.origins.forEach(function (origin) {
            if (!enabledOrigins.hasOwnProperty(origin)) return;
            if (enabledOrigins[origin] === true) {
                viewersCountItems[origin].bar.addClass("viewers-origin-on");
            } else {
                viewersCountItems[origin].bar.removeClass("viewers-origin-on");
            }
        });
    }

    function handleDeleteMessage(content) {
        const message = $("#message-" + content.messageId);
        message.addClass("deleted-message");

        const messageText = $("#message-" + content.messageId + " .text");
        messageText.removeClass("highlighted");
        messageText.text(failchat.deletedTextPlaceholder);
    }

    function updateViewersValues(counters) {
        failchat.origins.forEach(function (origin) {
            if (!counters.hasOwnProperty(origin)) return;

            const count = counters[origin];
            const counter = viewersCountItems[origin].counter;
            if (count === null) {
                counter.text("?");
            } else {
                counter.text(count);
            }
        });
    }

    function appendToMessageContainer(message) {
        failchat.messageCount++;
        if (failchat.messageCount > failchat.maxMessages && autoScroll) {
            $(failchat.messageSelector + ":lt(" + (failchat.messageCount - failchat.maxMessages) + ")").remove();
            failchat.messageCount = failchat.maxMessages;
        }
        messageContainer.append(message.textHtml);
    }

    function scrollIfRequired() {
        if (autoScroll) {
            scroller.scrollTop(messageContainer.height());
        }
    }

    $("body,html").bind("keydown wheel mousewheel", function (e) {
        //checks for disabling autoscroll
        if (autoScroll) {
            if (e.originalEvent.deltaY < 0 ||
                (e.type === "keydown" && (e.keyCode === 38 || e.keyCode === 36 || e.keyCode === 33)) // 38-up;36-home;33-pageup
            ) {
                autoScroll = false;
                scrollBar.css("visibility", "visible");
            }
            if (e.type === "mousewheel") { // for old browsers
                autoScroll = scroller.scrollTop() + scroller.height() >= messageContainer.height();
                if (!autoScroll) {
                    scrollBar.css("visibility", "visible");
                }
            }
        }
    });

    scroller.scroll(function (e) {
        //checks for enabling autoscroll
        if (!autoScroll) {
            autoScroll = scroller.scrollTop() + scroller.height() >= messageContainer.height();
            if (autoScroll) {
                scrollBar.css("visibility", "hidden");
            }
        }
    });
});

// Add user to ignore list and delete message
function ignore(messageNode) {
    deleteMessage(messageNode);
    failchat.socket.send(JSON.stringify(
        {
            type: "ignore-author",
            content: {
                authorId: messageNode.getAttribute("author-id")
            }
        }
    ));
}

// Just delete message
function deleteMessage(messageNode) {
    failchat.socket.send(JSON.stringify(
        {
            type: "delete-message",
            content: {
                messageId: messageNode.getAttribute("message-id")
            }
        }
    ));
}

function hexToRgba(hex) {
    const int = parseInt(hex, 16);
    const r = (int >> 24) & 255;
    const g = (int >> 16) & 255;
    const b = (int >> 8) & 255;
    const a = (int & 255) / 255;

    return r + "," + g + "," + b + "," + a;
}

$.views.converters("time", function(val) {
    const d = new Date(val);
    const h = d.getHours().toString();
    let m = d.getMinutes().toString();
    let s = d.getSeconds().toString();
    if (m.length === 1) {
        m = "0" + m;
    }
    if (s.length === 1) {
        s = "0" + s;
    }
    return h + ":" + m + ":" +  s;
});

// noinspection HtmlUnknownAttribute
const templates = {
    message: $.templates(
        '
\n' +
        '    \n' +
        '    {{:author.name}}: \n' +
        '    \n' +
        '        ❌\n' +
        '        🚫\n' +
        '    \n' +
        '    {{:text}}\n' +
        '
'
    ),

    rasterSmile: $.templates(''),
    vectorSmile: $.templates(''),
    link: $.templates('{{:domain}}'),
    image: $.templates('

'),

    statusMessage: $.templates('' +
        '
\n' +
        '    \n' +
        '    {{:origin}} \n' +
        '    {{:status}}\n' +
        '
'
    ),

    originViewersBar: $.templates('' +
        '\n' +
        '     \n' +
        ''
    )
};
<commit_msg>Use lamdas in js<commit_after>"use strict";

const failchat = {
    maxMessages: 50,
    messageCount: 0,
    iconsPath: "../_shared/icons/", //could be overrided in skin.html
    origins: ["peka2tv", "twitch", "goodgame", "youtube", "cybergame"],
    deletedTextPlaceholder: "message deleted"
};

$(() => {
    const socket = new ReconnectingWebSocket("ws://localhost:10880");
    socket.maxReconnectInterval = 5000;
    failchat.socket = socket;

    const bodyWrapper = $("#body-wrapper");
    const messageContainer = $("#message-container");
    const scroller = $(failchat.baronParams.scroller);
    const scrollBar = $(failchat.baronParams.bar);
    let autoScroll = true;
    const nativeClient = (navigator.userAgent.search("failchat") >= 0);
    // var nativeClient = true; //todo think about debugging
    let showStatusMessages = true; // show if origin-status message received before client-configuration message

    // viewers bar
    const viewersBar = $(".viewers-bar");
    const viewersCountItems = {};

    failchat.origins.forEach(origin => {
        const viewersBarHtml = templates.originViewersBar.render({origin: origin, iconsPath: failchat.iconsPath});
        $(".viewers-origins").append(viewersBarHtml);
        viewersCountItems[origin] = {
            bar: $("#" + origin + "-origin"),
            counter: $("#" + origin + "-viewers")
        };

        //set default value
        viewersCountItems[origin].counter.text("?")
    });

    // scroll bar
    baron(failchat.baronParams);

    // auto scroll
    new ResizeSensor(messageContainer, () => {
        if (autoScroll) {
            scroller.scrollTop(messageContainer.height());
        }
    });

    socket.onopen = () => {
        const connectedMessage = {"origin": "failchat", "status": "connected", "timestamp": Date.now()};
        handleStatusMessage(connectedMessage);
        appendToMessageContainer(connectedMessage);

        socket.send(JSON.stringify({type: "client-configuration", content: {}}));
        socket.send(JSON.stringify({type: "viewers-count", content: {}}));
    };

    socket.onclose = () => {
        const disconnectedMessage = {origin: "failchat", status: "disconnected", timestamp: Date.now()};
        handleStatusMessage(disconnectedMessage);
        appendToMessageContainer(disconnectedMessage);
    };

    socket.onmessage = () => {
        const wsm = JSON.parse(event.data);
        const type = wsm.type;
        const content = wsm.content;

        switch (type) {
            case "message":
                handleChatMessage(content);
                break;
            case "origin-status":
                handleStatusMessage(content);
                break;
            case "delete-message":
                handleDeleteMessage(content);
                break;
            case "client-configuration":
                handleClientConfigurationMessage(content);
                break;
        }

        if (content.textHtml !== undefined) {
            appendToMessageContainer(wsm.content);
            return
        }

        if (!nativeClient) return;

        switch (type) {
            case "viewers-count":
                updateViewersValues(content);
                break;
        }
    };

    function handleChatMessage(content) {
        const elementsArray = content.elements;

        for (let i = 0; i < elementsArray.length; i++) {
            const element = elementsArray[i];
            let elementHtml;
            switch(element.type) {
                case "emoticon":
                    if (element.format === "vector") {
                        elementHtml = templates.vectorSmile.render(element);
                    } else {
                        elementHtml = templates.rasterSmile.render(element);
                    }
                    break;
                case "link":
                    elementHtml = templates.link.render(element);
                    break;
                case "image":
                    elementHtml = templates.image.render(element);
                    break;
            }

            content.text = content.text.replace("{!" + i + "}", elementHtml);
        }

        content.iconsPath = failchat.iconsPath;
        content.textHtml = templates.message.render(content);
    }

    function handleStatusMessage(content) {
        if (!showStatusMessages) return;

        content.iconsPath = failchat.iconsPath;
        content.textHtml = templates.statusMessage.render(content);
    }

    function handleClientConfigurationMessage(content) {
        const statusMessageMode = content.statusMessageMode;
        if ((statusMessageMode === "everywhere") ||
            (nativeClient && statusMessageMode === "native_client")) {
            showStatusMessages = true;
        } else {
            showStatusMessages = false;
        }

        let bgHexColor;
        if (nativeClient) {
            bgHexColor = content.nativeClientBgColor;
        } else {
            bgHexColor = content.externalClientBgColor;
        }

        bodyWrapper.css("background-color", "rgba(" + hexToRgba(bgHexColor.substring(1)) + ")");

        if (!nativeClient) return;
        // handle viewers bar configuration

        if (content.showViewersCount === true) {
            viewersBar.addClass("viewers-bar-on");
        } else {
            viewersBar.removeClass("viewers-bar-on");
        }

        const enabledOrigins = content.enabledOrigins;
        failchat.origins.forEach(origin => {
            if (!enabledOrigins.hasOwnProperty(origin)) return;
            if (enabledOrigins[origin] === true) {
                viewersCountItems[origin].bar.addClass("viewers-origin-on");
            } else {
                viewersCountItems[origin].bar.removeClass("viewers-origin-on");
            }
        });
    }

    function handleDeleteMessage(content) {
        const message = $("#message-" + content.messageId);
        message.addClass("deleted-message");

        const messageText = $("#message-" + content.messageId + " .text");
        messageText.removeClass("highlighted");
        messageText.text(failchat.deletedTextPlaceholder);
    }

    function updateViewersValues(counters) {
        failchat.origins.forEach(origin => {
            if (!counters.hasOwnProperty(origin)) return;

            const count = counters[origin];
            const counter = viewersCountItems[origin].counter;
            if (count === null) {
                counter.text("?");
            } else {
                counter.text(count);
            }
        });
    }

    function appendToMessageContainer(message) {
        failchat.messageCount++;
        if (failchat.messageCount > failchat.maxMessages && autoScroll) {
            $(failchat.messageSelector + ":lt(" + (failchat.messageCount - failchat.maxMessages) + ")").remove();
            failchat.messageCount = failchat.maxMessages;
        }
        messageContainer.append(message.textHtml);
    }

    $("body,html").bind("keydown wheel mousewheel", function (e) {
        //checks for disabling autoscroll
        if (autoScroll) {
            if (e.originalEvent.deltaY < 0 ||
                (e.type === "keydown" && (e.keyCode === 38 || e.keyCode === 36 || e.keyCode === 33)) // 38-up;36-home;33-pageup
            ) {
                autoScroll = false;
                scrollBar.css("visibility", "visible");
            }
            if (e.type === "mousewheel") { // for old browsers
                autoScroll = scroller.scrollTop() + scroller.height() >= messageContainer.height();
                if (!autoScroll) {
                    scrollBar.css("visibility", "visible");
                }
            }
        }
    });

    scroller.scroll(e => {
        //checks for enabling autoscroll
        if (!autoScroll) {
            autoScroll = scroller.scrollTop() + scroller.height() >= messageContainer.height();
            if (autoScroll) {
                scrollBar.css("visibility", "hidden");
            }
        }
    });
});

// Add user to ignore list and delete message
function ignore(messageNode) {
    deleteMessage(messageNode);
    failchat.socket.send(JSON.stringify(
        {
            type: "ignore-author",
            content: {
                authorId: messageNode.getAttribute("author-id")
            }
        }
    ));
}

// Just delete message
function deleteMessage(messageNode) {
    failchat.socket.send(JSON.stringify(
        {
            type: "delete-message",
            content: {
                messageId: messageNode.getAttribute("message-id")
            }
        }
    ));
}

function hexToRgba(hex) {
    const int = parseInt(hex, 16);
    const r = (int >> 24) & 255;
    const g = (int >> 16) & 255;
    const b = (int >> 8) & 255;
    const a = (int & 255) / 255;

    return r + "," + g + "," + b + "," + a;
}

$.views.converters("time", val => {
    const d = new Date(val);
    const h = d.getHours().toString();
    let m = d.getMinutes().toString();
    let s = d.getSeconds().toString();
    if (m.length === 1) {
        m = "0" + m;
    }
    if (s.length === 1) {
        s = "0" + s;
    }
    return h + ":" + m + ":" +  s;
});

// noinspection HtmlUnknownAttribute
const templates = {
    message: $.templates(
        '
\n' +
        '    \n' +
        '    {{:author.name}}: \n' +
        '    \n' +
        '        ❌\n' +
        '        🚫\n' +
        '    \n' +
        '    {{:text}}\n' +
        '
'
    ),

    rasterSmile: $.templates(''),
    vectorSmile: $.templates(''),
    link: $.templates('{{:domain}}'),
    image: $.templates('

'),

    statusMessage: $.templates('' +
        '
\n' +
        '    \n' +
        '    {{:origin}} \n' +
        '    {{:status}}\n' +
        '
'
    ),

    originViewersBar: $.templates('' +
        '\n' +
        '     \n' +
        ''
    )
};
<|endoftext|>