<commit_before>import React from 'react';

import { Khadaj, niseko, Yajinni } from 'CONTRIBUTORS';
import retryingPromise from 'common/retryingPromise';
import SPECS from 'game/SPECS';

import CHANGELOG from './CHANGELOG';

export default {
  // The people that have contributed to this spec recently. People don't have to sign up to be long-time maintainers to be included in this list. If someone built a large part of the spec or contributed something recently to that spec, they can be added to the contributors list. If someone goes MIA, they may be removed after major changes or during a new expansion.
  contributors: [niseko, Yajinni, Khadaj],
  // The WoW client patch this spec was last updated to be fully compatible with.
  patchCompatibility: '8.0.1',
  // If set to  false`, the spec will show up as unsupported.
  isSupported: true,
  // Explain the status of this spec's analysis here. Try to mention how complete it is, and perhaps show links to places users can learn more.
  // If this spec's analysis does not show a complete picture please mention this in the `<Warning>` component.
  description: (
    <>
      Welcome to the Holy Priest analyzer. If you are new to holy, here are a few guides to get you started:

      Icy-Veins

      Warcraftpriests

      This page is maintained with help from the Holy Team at the Warcraft Priests Discord. You can join the #holy channel to ask any priestly questions you may have.
    </>
  ),
  // A recent example report to see interesting parts of the spec. Will be shown on the homepage.
  exampleReport: '/report/A3X2gamPht4vRdDV/6-Heroic+Vectis+-+Kill+(5:36)/4-Flinkz',

  // Don't change anything below this line;
  // The current spec identifier. This is the only place (in code) that specifies which spec this parser is about.
  spec: SPECS.HOLY_PRIEST,
  // The contents of your changelog.
  changelog: CHANGELOG,
  // The CombatLogParser class for your spec.
  parser: () => retryingPromise(() => import('./CombatLogParser' /* webpackChunkName: "HolyPriest" */).then(exports => exports.default)),
  // The path to the current directory (relative form project root). This is used for generating a GitHub link directly to your spec's code.
  path: __dirname,
};
<commit_msg>upping version to 8.1<commit_after>import React from 'react';

import { Khadaj, niseko, Yajinni } from 'CONTRIBUTORS';
import retryingPromise from 'common/retryingPromise';
import SPECS from 'game/SPECS';

import CHANGELOG from './CHANGELOG';

export default {
  // The people that have contributed to this spec recently. People don't have to sign up to be long-time maintainers to be included in this list. If someone built a large part of the spec or contributed something recently to that spec, they can be added to the contributors list. If someone goes MIA, they may be removed after major changes or during a new expansion.
  contributors: [niseko, Yajinni, Khadaj],
  // The WoW client patch this spec was last updated to be fully compatible with.
  patchCompatibility: '8.1.0',
  // If set to  false`, the spec will show up as unsupported.
  isSupported: true,
  // Explain the status of this spec's analysis here. Try to mention how complete it is, and perhaps show links to places users can learn more.
  // If this spec's analysis does not show a complete picture please mention this in the `<Warning>` component.
  description: (
    <>
      Welcome to the Holy Priest analyzer. If you are new to holy, here are a few guides to get you started:

      Icy-Veins

      Warcraftpriests

      This page is maintained with help from the Holy Team at the Warcraft Priests Discord. You can join the #holy channel to ask any priestly questions you may have.
    </>
  ),
  // A recent example report to see interesting parts of the spec. Will be shown on the homepage.
  exampleReport: '/report/A3X2gamPht4vRdDV/6-Heroic+Vectis+-+Kill+(5:36)/4-Flinkz',

  // Don't change anything below this line;
  // The current spec identifier. This is the only place (in code) that specifies which spec this parser is about.
  spec: SPECS.HOLY_PRIEST,
  // The contents of your changelog.
  changelog: CHANGELOG,
  // The CombatLogParser class for your spec.
  parser: () => retryingPromise(() => import('./CombatLogParser' /* webpackChunkName: "HolyPriest" */).then(exports => exports.default)),
  // The path to the current directory (relative form project root). This is used for generating a GitHub link directly to your spec's code.
  path: __dirname,
};
<|endoftext|>


<commit_before>'use strict';

var model = require('../models/restaurant');

module.exports = {
    sync: sync,
    drop: drop,
    insert: insert
};

/**
 * Creates table if its not existed.
 *
 * @param [isForced=false] if `true`, it drops the table and create a new one.
 * @returns {*|Promise}
 */
function sync(isForced) {
    isForced = isForced || false;

    return model.sync({force: isForced});
}

/**
 * Simply drops the table in context.
 *
 * @returns {*|Promise}
 */
function drop() {
    return model.drop();
}

/**
 * Loosely syncs the table with database then inserts a row in the table.
 *
 * @param obj contains values to be inserted in an object literal.
 * @returns {*|Promise}
 */
function insert(obj) {
    return this.sync().then(function () {
        return model.create({
            url: obj.url,
            type: obj.type,
            name: obj.name,
            rating: parseInt(obj.rating)
        });
    });
}
<commit_msg>Drastically re-write. JK, webstorm fucked up indenting.<commit_after>'use strict';

var model = require('../models/restaurant');

module.exports = {
  sync: sync,
  drop: drop,
  insert: insert
};

/**
 * Creates table if its not existed.
 *
 * @param [isForced=false] if `true`, it drops the table and create a new one.
 * @returns {*|Promise}
 */
function sync(isForced) {
  isForced = isForced || false;

  return model.sync({force: isForced});
}

/**
 * Simply drops the table in context.
 *
 * @returns {*|Promise}
 */
function drop() {
  return model.drop();
}

/**
 * Loosely syncs the table with database then inserts a row in the table.
 *
 * @param obj contains values to be inserted in an object literal.
 * @returns {*|Promise}
 */
function insert(obj) {
  return this.sync().then(function () {
    return model.create({
      url: obj.url,
      type: obj.type,
      name: obj.name,
      rating: parseInt(obj.rating)
    });
  });
}
<|endoftext|>


<commit_before>function addFileUrl(name, url) {
    var fileObj = $("#fileUrl").val();
    $("#" + fileObj).val(url);
    $("#showPic").attr("src", url);
}
var $ = jQuery, $list = $('#fileList');
var ValidateIndex;
var uploader = WebUploader.create({

    // 选完文件后，是否自动上传。
    auto:true,

    // swf文件路径
    swf: '/Res/webuploader/Uploader.swf',

    // 文件接收服务端。
    server: '/FileUploader/HDpic',

    // 选择文件的按钮。可选。
    // 内部根据当前运行是创建，可能是input元素，也可能是flash.
    pick: '#filePicker',
    resize: true,
    // 只允许选择图片文件。
    accept: {
        title: '图片文件',
        extensions: 'gif,jpg,jpeg,bmp,png',
        mimeTypes: 'image/*'
    },
    fileNumLimit: 1,
    fileSizeLimit:6 * 1024 * 1024,
    fileSingleSizeLimit: 5 * 1024 * 1024
});
uploader.on('beforeFileQueued', function (file) {
    switch (file.ext) {
        case "gif": return true; break;
        case "jpg": return true; break;
        case "jpeg": return true; break;
        case "bmp": return true; break;
        case "png": return true; break;
    }
    alert("请选择图片文件");
    return false;
});
uploader.on('fileQueued', function (file) {
    var $li = $(
            '
' +
                '' +
                '
' + file.name + '
' +
            '
'
            ),
        $img = $li.find('img');

    // $list为容器jQuery实例
    $list.append($li);
});
// 文件上传过程中创建进度条实时显示。
uploader.on('uploadProgress', function (file, percentage) {
    var $li = $('#' + file.id),
        $percent = $li.find('.progress span');

    // 避免重复创建
    if (!$percent.length) {
        $percent = $('

')
                .appendTo($li)
                .find('span');
    }

    $percent.css('width', percentage * 100 + '%');
});

// 文件上传成功，给item添加成功class, 用样式标记上传成功。
uploader.on('uploadSuccess', function (file, responseText) {
    //$('#' + file.id).remove();
    var validate;
    for (var d in responseText) {
        validate = responseText[d];
    }
    ValidateIndex = validate.indexOf("


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

  angular.module('ImplicitClient')
    .factory('UserService', UserService);

  function UserService() {
    var userService = this;

    userService.getUsers = function(page, jumlah) {
      return 'http://localhost:3000/api/user?page=' + page + '&jumlah=' + jumlah;
    };

    return userService;

  }

})();
<commit_msg>add url<commit_after>(function() {
  'use strict';

  angular.module('ImplicitClient')
    .factory('UserService', UserService);

  function UserService() {
    var userService = this;

    userService.getUsers = function(page, jumlah) {
      return 'http://localhost:3000/api/user?page=' + page + '&jumlah=' + jumlah;
    };
    userService.deleteUser = function(id) {
      return 'http://localhost:3000/api/user/' + id;
    };

    return userService;

  }

})();
<|endoftext|>


<commit_before>(function() {
var top = window.top;

var listenFor = (function() {
  var boundEvents = {};

  window.addEventListener("message", function(messageObject) {
    var messageKey = messageObject.data.message;

    if ( typeof boundEvents[messageKey] === "function" ) {
      boundEvents[messageKey].apply(this, arguments);
    }
  }, false);

  return function(message, handler) {
    boundEvents[message] = handler;
  };
})();

function passThroughClick(elem, messageKey) {
  elem.bind('click', function() {
    top.postMessage({message: messageKey}, "*");
  });
}

window.updateHeight = function updateHeight(){
  var height = document.body.scrollHeight;

  top.postMessage({message: "changeHeight", height: height}, "*");
};

listenFor("annotate", function(messageObject) {
  $('#annotate_button>input').prop('checked',true);
});

listenFor("setFormData", function(messageObject) {
  if ( messageObject.data.text.length > 0 ) {
    $('#new_fact_form').show();
    $('#createFactlinkHelp').hide();
  }

  $('#title').val(messageObject.data.title);
  $('.fact>textarea').val(messageObject.data.text);
  $('.fact>p').html(messageObject.data.text);
  $('#url').val(messageObject.data.url);

  updateHeight();
});

listenFor("blacklist", function(messageObject) {
  $('#highlight').prop('disabled', true);
  $('#blacklisted').show();
  $('#annotate_button').hide();

  console.info( "Blacklisting" );
  updateHeight();
});

listenFor("hideError", function(messageObject) {
  $('#highlight').prop('disabled', false);
  $('#error_loading_factlink').hide();
  $('#annotate_button>input').prop('disabled', false);

  updateHeight();
});

listenFor("error", function(messageObject) {
  $('#highlight').prop('disabled', true);
  $('#error_loading_factlink').show();
  $('#annotate_button>input').prop('disabled', true);

  updateHeight();
});

passThroughClick( $('#annotate_button>input'), "annotateToggle");

// When the cancel button is clicked, close the popup
$('#cancel').on('click', function (e) {
  e.preventDefault();

  top.window.close();
});

$(window).on('resize', function(e) {
  console.info( "Resize" );
  updateHeight();
});
})();
<commit_msg>update height on first load before the help did not always show, or sometimes only after a few seconds<commit_after>(function() {
var top = window.top;

var listenFor = (function() {
  var boundEvents = {};

  window.addEventListener("message", function(messageObject) {
    var messageKey = messageObject.data.message;

    if ( typeof boundEvents[messageKey] === "function" ) {
      boundEvents[messageKey].apply(this, arguments);
    }
  }, false);

  return function(message, handler) {
    boundEvents[message] = handler;
  };
})();

function passThroughClick(elem, messageKey) {
  elem.bind('click', function() {
    top.postMessage({message: messageKey}, "*");
  });
}

window.updateHeight = function updateHeight(){
  var height = document.body.scrollHeight;

  top.postMessage({message: "changeHeight", height: height}, "*");
};

listenFor("annotate", function(messageObject) {
  $('#annotate_button>input').prop('checked',true);
});

listenFor("setFormData", function(messageObject) {
  if ( messageObject.data.text.length > 0 ) {
    $('#new_fact_form').show();
    $('#createFactlinkHelp').hide();
  }

  $('#title').val(messageObject.data.title);
  $('.fact>textarea').val(messageObject.data.text);
  $('.fact>p').html(messageObject.data.text);
  $('#url').val(messageObject.data.url);

  updateHeight();
});

listenFor("blacklist", function(messageObject) {
  $('#highlight').prop('disabled', true);
  $('#blacklisted').show();
  $('#annotate_button').hide();

  console.info( "Blacklisting" );
  updateHeight();
});

listenFor("hideError", function(messageObject) {
  $('#highlight').prop('disabled', false);
  $('#error_loading_factlink').hide();
  $('#annotate_button>input').prop('disabled', false);

  updateHeight();
});

listenFor("error", function(messageObject) {
  $('#highlight').prop('disabled', true);
  $('#error_loading_factlink').show();
  $('#annotate_button>input').prop('disabled', true);

  updateHeight();
});

passThroughClick( $('#annotate_button>input'), "annotateToggle");

// When the cancel button is clicked, close the popup
$('#cancel').on('click', function (e) {
  e.preventDefault();

  top.window.close();
});

$(window).on('resize', function(e) {
  console.info( "Resize" );
  updateHeight();
});
})();

updateHeight();
<|endoftext|>


<commit_before>'use strict';

var stripBom = require('strip-bom-string');
var utils = module.exports = exports;
utils.typeOf = require('kind-of');

/**
 * Cast `val` to an array.
 */

utils.isBuffer = function(val) {
  return utils.typeOf(val) === 'buffer';
};

/**
 * Cast `val` to an array.
 */

utils.isObject = function(val) {
  return utils.typeOf(val) === 'object';
};

/**
 * Cast `val` to an array.
 */

utils.toBuffer = function(input) {
  if (typeof input === 'string') {
    return new Buffer(input);
  }
  return input;
};

/**
 * Cast `val` to an array.
 */

utils.toString = function(input) {
  if (utils.isBuffer(input)) {
    return stripBom(String(input));
  }
  if (typeof input !== 'string') {
    throw new TypeError('expected input to be a string or buffer');
  }
  return stripBom(input);
};

/**
 * Cast `val` to an array.
 */

utils.arrayify = function(val) {
  return val ? (Array.isArray(val) ? val : [val]) : [];
};

/**
 * Return true if the givne string starts with the specified substring
 */

utils.startsWith = function(str, substr, len) {
  if (typeof len !== 'number') len = substr.length;
  return str.slice(0, len) === substr;
};
<commit_msg>fix code comments<commit_after>'use strict';

var stripBom = require('strip-bom-string');
var utils = module.exports = exports;
utils.typeOf = require('kind-of');

/**
 * Returns true if `val` is a buffer
 */

utils.isBuffer = function(val) {
  return utils.typeOf(val) === 'buffer';
};

/**
 * Returns true if `val` is an object
 */

utils.isObject = function(val) {
  return utils.typeOf(val) === 'object';
};

/**
 * Cast `input` to a buffer
 */

utils.toBuffer = function(input) {
  if (typeof input === 'string') {
    return new Buffer(input);
  }
  return input;
};

/**
 * Cast `val` to a string.
 */

utils.toString = function(input) {
  if (utils.isBuffer(input)) {
    return stripBom(String(input));
  }
  if (typeof input !== 'string') {
    throw new TypeError('expected input to be a string or buffer');
  }
  return stripBom(input);
};

/**
 * Cast `val` to an array.
 */

utils.arrayify = function(val) {
  return val ? (Array.isArray(val) ? val : [val]) : [];
};

/**
 * Returns true if `str` starts with `substr`.
 */

utils.startsWith = function(str, substr, len) {
  if (typeof len !== 'number') len = substr.length;
  return str.slice(0, len) === substr;
};
<|endoftext|>


<commit_before>import Activator from './mixins/activator'
import Detachable from '~mixins/detachable'
import Generators from './mixins/generators'
import Position from './mixins/position'
import Utils from './mixins/utils'
import Toggleable from '~mixins/toggleable'
import Keyable from './mixins/keyable'

import { ClickOutside } from '~directives'

export default {
  name: 'v-menu',

  mixins: [
    Activator,
    Detachable,
    Generators,
    Keyable,
    Position,
    Utils,
    Toggleable
  ],

  directives: {
    ClickOutside
  },

  data () {
    return {
      autoIndex: null,
      dimensions: {
        activator: {
          top: 0, left: 0,
          bottom: 0, right: 0,
          width: 0, height: 0,
          offsetTop: 0, scrollHeight: 0
        },
        content: {
          top: 0, left: 0,
          bottom: 0, right: 0,
          width: 0, height: 0,
          offsetTop: 0, scrollHeight: 0
        },
        list: null,
        selected: null
      },
      direction: { vert: 'bottom', horiz: 'right' },
      isContentActive: false,
      isBooted: false,
      maxHeightAutoDefault: '200px',
      resizeTimeout: {},
      startIndex: 3,
      stopIndex: 0,
      tileLength: 0,
      window: {},
      absoluteX: 0,
      absoluteY: 0,
      insideContent: false,
      hasJustFocused: false,
      focusedTimeout: {}
    }
  },

  props: {
    top: Boolean,
    left: Boolean,
    bottom: Boolean,
    right: Boolean,
    fullWidth: Boolean,
    auto: Boolean,
    offsetX: Boolean,
    offsetY: Boolean,
    disabled: Boolean,
    maxHeight: {
      default: 'auto'
    },
    nudgeTop: {
      type: Number,
      default: 0
    },
    nudgeBottom: {
      type: Number,
      default: 0
    },
    nudgeLeft: {
      type: Number,
      default: 0
    },
    nudgeRight: {
      type: Number,
      default: 0
    },
    nudgeWidth: {
      type: Number,
      default: 0
    },
    openOnClick: {
      type: Boolean,
      default: true
    },
    openOnHover: {
      type: Boolean,
      default: false
    },
    lazy: Boolean,
    closeOnClick: {
      type: Boolean,
      default: true
    },
    closeOnContentClick: {
      type: Boolean,
      default: true
    },
    activator: {
      default: null
    },
    origin: {
      type: String,
      default: 'top left'
    },
    transition: {
      type: String,
      default: 'menu-transition'
    },
    positionX: {
      type: Number,
      default: null
    },
    positionY: {
      type: Number,
      default: null
    },
    positionAbsolutely: {
      type: Boolean,
      default: false
    },
    maxWidth: [Number, String],
    minWidth: [Number, String]
  },

  computed: {
    calculatedMinWidth () {
      const minWidth = parseInt(this.minWidth) ||
        this.dimensions.activator.width + this.nudgeWidth + (this.auto ? 16 : 0)

      if (!this.maxWidth) return minWidth

      const maxWidth = parseInt(this.maxWidth)

      return maxWidth < minWidth ? maxWidth : minWidth
    },
    styles () {
      return {
        maxHeight: this.auto ? '200px' : isNaN(this.maxHeight) ? this.maxHeight : `${this.maxHeight}px`,
        minWidth: `${this.calculatedMinWidth}px`,
        maxWidth: `${parseInt(this.maxWidth)}px`,
        top: `${this.calcTop()}px`,
        left: `${this.calcLeft()}px`
      }
    },
    hasActivator () {
      return !!this.$slots.activator || this.activator
    }
  },

  watch: {
    activator (newActivator, oldActivator) {
      this.removeActivatorEvents(oldActivator)
      this.addActivatorEvents(newActivator)
    },
    disabled (val) {
      val && this.deactivate()
    },
    hasJustFocused (val) {
      if (!val) return

      clearTimeout(this.focusedTimeout)
      this.focusedTimeout = setTimeout(() => (this.hasJustFocused = false), 100)
    },
    isActive (val) {
      if (this.disabled) return

      val && this.activate() || this.deactivate()
    },
    windowResizeHandler () {
      this.isBooted = false
    }
  },

  mounted () {
    window.addEventListener('resize', this.onResize, { passive: true })
  },

  beforeDestroy () {
    window.removeEventListener('resize', this.onResize, { passive: true })
    window.removeEventListener('resize', this.windowResizeHandler)
  },

  methods: {
    activate () {
      if (typeof window === 'undefined') return
      this.isBooted = true
      this.insideContent = true
      this.getTiles()
      this.updateDimensions()
      requestAnimationFrame(this.startTransition)
    },
    deactivate () {
      this.isContentActive = false
    },
    onResize () {
      clearTimeout(this.resizeTimeout)
      if (!this.isActive) return
      this.resizeTimeout = setTimeout(this.updateDimensions, 200)
    },
    startTransition () {
      requestAnimationFrame(() => (this.isContentActive = true))
      requestAnimationFrame(this.calculateScroll)
    }
  },

  render (h) {
    const directives = !this.openOnHover ? [{
      name: 'click-outside',
      value: () => this.closeOnClick
    }] : []

    const data = {
      'class': 'menu',
      style: {
        display: this.fullWidth ? 'block' : 'inline-block'
      },
      directives,
      on: {
        keydown: e => {
          if (e.keyCode === 27) this.isActive = false
          else this.changeListIndex(e)
        }
      }
    }

    return h('div', data, [
      this.genActivator(),
      this.genTransition()
    ])
  }
}
<commit_msg>Add activate and deactivate events (#1163)<commit_after>import Activator from './mixins/activator'
import Detachable from '~mixins/detachable'
import Generators from './mixins/generators'
import Position from './mixins/position'
import Utils from './mixins/utils'
import Toggleable from '~mixins/toggleable'
import Keyable from './mixins/keyable'

import { ClickOutside } from '~directives'

export default {
  name: 'v-menu',

  mixins: [
    Activator,
    Detachable,
    Generators,
    Keyable,
    Position,
    Utils,
    Toggleable
  ],

  directives: {
    ClickOutside
  },

  data () {
    return {
      autoIndex: null,
      dimensions: {
        activator: {
          top: 0, left: 0,
          bottom: 0, right: 0,
          width: 0, height: 0,
          offsetTop: 0, scrollHeight: 0
        },
        content: {
          top: 0, left: 0,
          bottom: 0, right: 0,
          width: 0, height: 0,
          offsetTop: 0, scrollHeight: 0
        },
        list: null,
        selected: null
      },
      direction: { vert: 'bottom', horiz: 'right' },
      isContentActive: false,
      isBooted: false,
      maxHeightAutoDefault: '200px',
      resizeTimeout: {},
      startIndex: 3,
      stopIndex: 0,
      tileLength: 0,
      window: {},
      absoluteX: 0,
      absoluteY: 0,
      insideContent: false,
      hasJustFocused: false,
      focusedTimeout: {}
    }
  },

  props: {
    top: Boolean,
    left: Boolean,
    bottom: Boolean,
    right: Boolean,
    fullWidth: Boolean,
    auto: Boolean,
    offsetX: Boolean,
    offsetY: Boolean,
    disabled: Boolean,
    maxHeight: {
      default: 'auto'
    },
    nudgeTop: {
      type: Number,
      default: 0
    },
    nudgeBottom: {
      type: Number,
      default: 0
    },
    nudgeLeft: {
      type: Number,
      default: 0
    },
    nudgeRight: {
      type: Number,
      default: 0
    },
    nudgeWidth: {
      type: Number,
      default: 0
    },
    openOnClick: {
      type: Boolean,
      default: true
    },
    openOnHover: {
      type: Boolean,
      default: false
    },
    lazy: Boolean,
    closeOnClick: {
      type: Boolean,
      default: true
    },
    closeOnContentClick: {
      type: Boolean,
      default: true
    },
    activator: {
      default: null
    },
    origin: {
      type: String,
      default: 'top left'
    },
    transition: {
      type: String,
      default: 'menu-transition'
    },
    positionX: {
      type: Number,
      default: null
    },
    positionY: {
      type: Number,
      default: null
    },
    positionAbsolutely: {
      type: Boolean,
      default: false
    },
    maxWidth: [Number, String],
    minWidth: [Number, String]
  },

  computed: {
    calculatedMinWidth () {
      const minWidth = parseInt(this.minWidth) ||
        this.dimensions.activator.width + this.nudgeWidth + (this.auto ? 16 : 0)

      if (!this.maxWidth) return minWidth

      const maxWidth = parseInt(this.maxWidth)

      return maxWidth < minWidth ? maxWidth : minWidth
    },
    styles () {
      return {
        maxHeight: this.auto ? '200px' : isNaN(this.maxHeight) ? this.maxHeight : `${this.maxHeight}px`,
        minWidth: `${this.calculatedMinWidth}px`,
        maxWidth: `${parseInt(this.maxWidth)}px`,
        top: `${this.calcTop()}px`,
        left: `${this.calcLeft()}px`
      }
    },
    hasActivator () {
      return !!this.$slots.activator || this.activator
    }
  },

  watch: {
    activator (newActivator, oldActivator) {
      this.removeActivatorEvents(oldActivator)
      this.addActivatorEvents(newActivator)
    },
    disabled (val) {
      val && this.deactivate()
    },
    hasJustFocused (val) {
      if (!val) return

      clearTimeout(this.focusedTimeout)
      this.focusedTimeout = setTimeout(() => (this.hasJustFocused = false), 100)
    },
    isActive (val) {
      if (this.disabled) return

      val && this.activate() || this.deactivate()
    },
    windowResizeHandler () {
      this.isBooted = false
    }
  },

  mounted () {
    window.addEventListener('resize', this.onResize, { passive: true })
  },

  beforeDestroy () {
    window.removeEventListener('resize', this.onResize, { passive: true })
    window.removeEventListener('resize', this.windowResizeHandler)
  },

  methods: {
    activate () {
      if (typeof window === 'undefined') return
      this.isBooted = true
      this.insideContent = true
      this.getTiles()
      this.updateDimensions()
      requestAnimationFrame(this.startTransition)
      this.$emit('activate')
    },
    deactivate () {
      this.isContentActive = false
      this.$emit('deactivate')
    },
    onResize () {
      clearTimeout(this.resizeTimeout)
      if (!this.isActive) return
      this.resizeTimeout = setTimeout(this.updateDimensions, 200)
    },
    startTransition () {
      requestAnimationFrame(() => (this.isContentActive = true))
      requestAnimationFrame(this.calculateScroll)
    }
  },

  render (h) {
    const directives = !this.openOnHover ? [{
      name: 'click-outside',
      value: () => this.closeOnClick
    }] : []

    const data = {
      'class': 'menu',
      style: {
        display: this.fullWidth ? 'block' : 'inline-block'
      },
      directives,
      on: {
        keydown: e => {
          if (e.keyCode === 27) this.isActive = false
          else this.changeListIndex(e)
        }
      }
    }

    return h('div', data, [
      this.genActivator(),
      this.genTransition()
    ])
  }
}
<|endoftext|>


<commit_before>import { DefaultDraftInlineStyle } from 'draft-js';

// Originally from https://github.com/draft-js-utils/draft-js-utils/blob/master/src/Constants.js.
export const BLOCK_TYPE = {
    // This is used to represent a normal text block (paragraph).
    UNSTYLED: 'unstyled',
    HEADER_ONE: 'header-one',
    HEADER_TWO: 'header-two',
    HEADER_THREE: 'header-three',
    HEADER_FOUR: 'header-four',
    HEADER_FIVE: 'header-five',
    HEADER_SIX: 'header-six',
    UNORDERED_LIST_ITEM: 'unordered-list-item',
    ORDERED_LIST_ITEM: 'ordered-list-item',
    BLOCKQUOTE: 'blockquote',
    CODE: 'code-block',
    ATOMIC: 'atomic',
};

// Originally from https://github.com/draft-js-utils/draft-js-utils/blob/master/src/Constants.js.
export const ENTITY_TYPE = {
    LINK: 'LINK',
    DOCUMENT: 'DOCUMENT',
    MODEL: 'MODEL',
    IMAGE: 'IMAGE',
    EMBED: 'EMBED',
    HORIZONTAL_RULE: 'HORIZONTAL_RULE',
};

// Originally from https://github.com/draft-js-utils/draft-js-utils/blob/master/src/Constants.js.
export const INLINE_STYLE = {
    BOLD: 'BOLD',
    ITALIC: 'ITALIC',
    CODE: 'CODE',
    UNDERLINE: 'UNDERLINE',
    STRIKETHROUGH: 'STRIKETHROUGH',
    MARK: 'MARK',
    QUOTATION: 'QUOTATION',
    SMALL: 'SMALL',
    SAMPLE: 'SAMPLE',
    INSERT: 'INSERT',
    DELETE: 'DELETE',
    KEYBOARD: 'KEYBOARD',
    SUPERSCRIPT: 'SUPERSCRIPT',
    SUBSCRIPT: 'SUBSCRIPT',
};

export const FONT_FAMILY_MONOSPACE =
    'Consolas, Menlo, Monaco, Lucida Console, Liberation Mono, DejaVu Sans Mono, Bitstream Vera Sans Mono, Courier New, monospace, sans-serif';

export const CUSTOM_STYLE_MAP = {
    [INLINE_STYLE.BOLD]: DefaultDraftInlineStyle[INLINE_STYLE.BOLD],
    [INLINE_STYLE.ITALIC]: DefaultDraftInlineStyle[INLINE_STYLE.ITALIC],
    [INLINE_STYLE.STRIKETHROUGH]:
        DefaultDraftInlineStyle[INLINE_STYLE.STRIKETHROUGH],
    [INLINE_STYLE.UNDERLINE]: DefaultDraftInlineStyle[INLINE_STYLE.UNDERLINE],

    [INLINE_STYLE.CODE]: {
        padding: '0.2em 0.3125em',
        margin: '0',
        fontSize: '85%',
        backgroundColor: 'rgba(27, 31, 35, 0.05)',
        fontFamily: FONT_FAMILY_MONOSPACE,
        borderRadius: '3px',
    },

    [INLINE_STYLE.MARK]: {
        backgroundColor: 'yellow',
    },
    [INLINE_STYLE.QUOTATION]: {
        fontStyle: 'italic',
    },
    [INLINE_STYLE.SMALL]: {
        fontSize: 'smaller',
    },
    [INLINE_STYLE.SAMPLE]: {
        fontFamily: FONT_FAMILY_MONOSPACE,
    },
    [INLINE_STYLE.INSERT]: {
        textDecoration: 'underline',
    },
    [INLINE_STYLE.DELETE]: {
        textDecoration: 'line-through',
    },
    [INLINE_STYLE.KEYBOARD]: {
        fontFamily: FONT_FAMILY_MONOSPACE,
        padding: '3px 5px',
        fontSize: '11px',
        lineHeight: '10px',
        color: '#444d56',
        verticalAlign: 'middle',
        backgroundColor: '#fafbfc',
        border: 'solid 1px #c6cbd1',
        borderBottomColor: '#959da5',
        borderRadius: '3px',
        boxShadow: 'inset 0 -1px 0 #959da5',
    },
    [INLINE_STYLE.SUPERSCRIPT]: {
        fontSize: '80%',
        verticalAlign: 'super',
        lineHeight: '1',
    },
    [INLINE_STYLE.SUBSCRIPT]: {
        fontSize: '80%',
        verticalAlign: 'sub',
        lineHeight: '1',
    },
};

export const BR_TYPE = 'BR';
export const UNDO_TYPE = 'undo';
export const REDO_TYPE = 'redo';

// Originally from https://github.com/facebook/draft-js/blob/master/src/component/utils/getDefaultKeyBinding.js.
export const KEY_CODES = {
    K: 75,
    B: 66,
    U: 85,
    J: 74,
    I: 73,
    X: 88,
    0: 48,
    1: 49,
    2: 50,
    3: 51,
    4: 52,
    5: 53,
    6: 54,
    7: 55,
    8: 56,
    '.': 190,
    ',': 188,
};

export const KEYBOARD_SHORTCUTS = {
    [BLOCK_TYPE.UNSTYLED]: {
        other: 'Ctrl+Alt+0',
        macOS: '⌘+Option+0',
    },
    [BLOCK_TYPE.HEADER_ONE]: {
        other: 'Ctrl+Alt+1',
        macOS: '⌘+Option+1',
    },
    [BLOCK_TYPE.HEADER_TWO]: {
        other: 'Ctrl+Alt+2',
        macOS: '⌘+Option+2',
    },
    [BLOCK_TYPE.HEADER_THREE]: {
        other: 'Ctrl+Alt+3',
        macOS: '⌘+Option+3',
    },
    [BLOCK_TYPE.HEADER_FOUR]: {
        other: 'Ctrl+Alt+4',
        macOS: '⌘+Option+4',
    },
    [BLOCK_TYPE.HEADER_FIVE]: {
        other: 'Ctrl+Alt+5',
        macOS: '⌘+Option+5',
    },
    [BLOCK_TYPE.HEADER_SIX]: {
        other: 'Ctrl+Alt+6',
        macOS: '⌘+Option+6',
    },
    [BLOCK_TYPE.UNORDERED_LIST_ITEM]: {
        other: 'Ctrl+Shift+8',
        macOS: '⌘+Shift+8',
    },
    [BLOCK_TYPE.ORDERED_LIST_ITEM]: {
        other: 'Ctrl+Shift+7',
        macOS: '⌘+Shift+7',
    },

    [INLINE_STYLE.BOLD]: { other: 'Ctrl+B', macOS: '⌘+B' },
    [INLINE_STYLE.ITALIC]: { other: 'Ctrl+I', macOS: '⌘+I' },
    [INLINE_STYLE.UNDERLINE]: {
        other: 'Ctrl+U',
        macOS: '⌘+U',
    },
    [INLINE_STYLE.STRIKETHROUGH]: {
        other: 'Ctrl+Shift+X',
        macOS: '⌘+Shift+X',
    },
    [INLINE_STYLE.SUPERSCRIPT]: {
        other: 'Ctrl+.',
        macOS: '⌘+.',
    },
    [INLINE_STYLE.SUBSCRIPT]: {
        other: 'Ctrl+,',
        macOS: '⌘+,',
    },

    [ENTITY_TYPE.LINK]: { other: 'Ctrl+K', macOS: '⌘+K' },

    [BR_TYPE]: {
        other: 'Shift+Enter',
        macOS: 'Shift+Enter',
    },

    [UNDO_TYPE]: {
        other: 'Ctrl+Z',
        macOS: '⌘+Z',
    },

    [REDO_TYPE]: {
        other: 'Ctrl+Shift+Z',
        macOS: '⌘+Shift+Z',
    },
};

export const NBSP = '\u00a0';

export const HANDLED = 'handled';
export const NOT_HANDLED = 'not-handled';

export const BEFORE_INPUT_MAP = {
    '* ': BLOCK_TYPE.UNORDERED_LIST_ITEM,
    '- ': BLOCK_TYPE.UNORDERED_LIST_ITEM,
    '1. ': BLOCK_TYPE.ORDERED_LIST_ITEM,
    // TODO Potentially to be commented out again.
    // See https://github.com/springload/draftail/issues/53
    '# ': BLOCK_TYPE.HEADER_ONE,
    '## ': BLOCK_TYPE.HEADER_TWO,
    '### ': BLOCK_TYPE.HEADER_THREE,
    '#### ': BLOCK_TYPE.HEADER_FOUR,
    '##### ': BLOCK_TYPE.HEADER_FIVE,
    '###### ': BLOCK_TYPE.HEADER_SIX,
    '``` ': BLOCK_TYPE.CODE,
};

export const BEFORE_INPUT_MAX_LENGTH = Object.keys(BEFORE_INPUT_MAP).reduce(
    (maxLength, input) => Math.max(maxLength, input.length),
    0,
);
<commit_msg>Add Markdown-style marker for blockquote<commit_after>import { DefaultDraftInlineStyle } from 'draft-js';

// Originally from https://github.com/draft-js-utils/draft-js-utils/blob/master/src/Constants.js.
export const BLOCK_TYPE = {
    // This is used to represent a normal text block (paragraph).
    UNSTYLED: 'unstyled',
    HEADER_ONE: 'header-one',
    HEADER_TWO: 'header-two',
    HEADER_THREE: 'header-three',
    HEADER_FOUR: 'header-four',
    HEADER_FIVE: 'header-five',
    HEADER_SIX: 'header-six',
    UNORDERED_LIST_ITEM: 'unordered-list-item',
    ORDERED_LIST_ITEM: 'ordered-list-item',
    BLOCKQUOTE: 'blockquote',
    CODE: 'code-block',
    ATOMIC: 'atomic',
};

// Originally from https://github.com/draft-js-utils/draft-js-utils/blob/master/src/Constants.js.
export const ENTITY_TYPE = {
    LINK: 'LINK',
    DOCUMENT: 'DOCUMENT',
    MODEL: 'MODEL',
    IMAGE: 'IMAGE',
    EMBED: 'EMBED',
    HORIZONTAL_RULE: 'HORIZONTAL_RULE',
};

// Originally from https://github.com/draft-js-utils/draft-js-utils/blob/master/src/Constants.js.
export const INLINE_STYLE = {
    BOLD: 'BOLD',
    ITALIC: 'ITALIC',
    CODE: 'CODE',
    UNDERLINE: 'UNDERLINE',
    STRIKETHROUGH: 'STRIKETHROUGH',
    MARK: 'MARK',
    QUOTATION: 'QUOTATION',
    SMALL: 'SMALL',
    SAMPLE: 'SAMPLE',
    INSERT: 'INSERT',
    DELETE: 'DELETE',
    KEYBOARD: 'KEYBOARD',
    SUPERSCRIPT: 'SUPERSCRIPT',
    SUBSCRIPT: 'SUBSCRIPT',
};

export const FONT_FAMILY_MONOSPACE =
    'Consolas, Menlo, Monaco, Lucida Console, Liberation Mono, DejaVu Sans Mono, Bitstream Vera Sans Mono, Courier New, monospace, sans-serif';

export const CUSTOM_STYLE_MAP = {
    [INLINE_STYLE.BOLD]: DefaultDraftInlineStyle[INLINE_STYLE.BOLD],
    [INLINE_STYLE.ITALIC]: DefaultDraftInlineStyle[INLINE_STYLE.ITALIC],
    [INLINE_STYLE.STRIKETHROUGH]:
        DefaultDraftInlineStyle[INLINE_STYLE.STRIKETHROUGH],
    [INLINE_STYLE.UNDERLINE]: DefaultDraftInlineStyle[INLINE_STYLE.UNDERLINE],

    [INLINE_STYLE.CODE]: {
        padding: '0.2em 0.3125em',
        margin: '0',
        fontSize: '85%',
        backgroundColor: 'rgba(27, 31, 35, 0.05)',
        fontFamily: FONT_FAMILY_MONOSPACE,
        borderRadius: '3px',
    },

    [INLINE_STYLE.MARK]: {
        backgroundColor: 'yellow',
    },
    [INLINE_STYLE.QUOTATION]: {
        fontStyle: 'italic',
    },
    [INLINE_STYLE.SMALL]: {
        fontSize: 'smaller',
    },
    [INLINE_STYLE.SAMPLE]: {
        fontFamily: FONT_FAMILY_MONOSPACE,
    },
    [INLINE_STYLE.INSERT]: {
        textDecoration: 'underline',
    },
    [INLINE_STYLE.DELETE]: {
        textDecoration: 'line-through',
    },
    [INLINE_STYLE.KEYBOARD]: {
        fontFamily: FONT_FAMILY_MONOSPACE,
        padding: '3px 5px',
        fontSize: '11px',
        lineHeight: '10px',
        color: '#444d56',
        verticalAlign: 'middle',
        backgroundColor: '#fafbfc',
        border: 'solid 1px #c6cbd1',
        borderBottomColor: '#959da5',
        borderRadius: '3px',
        boxShadow: 'inset 0 -1px 0 #959da5',
    },
    [INLINE_STYLE.SUPERSCRIPT]: {
        fontSize: '80%',
        verticalAlign: 'super',
        lineHeight: '1',
    },
    [INLINE_STYLE.SUBSCRIPT]: {
        fontSize: '80%',
        verticalAlign: 'sub',
        lineHeight: '1',
    },
};

export const BR_TYPE = 'BR';
export const UNDO_TYPE = 'undo';
export const REDO_TYPE = 'redo';

// Originally from https://github.com/facebook/draft-js/blob/master/src/component/utils/getDefaultKeyBinding.js.
export const KEY_CODES = {
    K: 75,
    B: 66,
    U: 85,
    J: 74,
    I: 73,
    X: 88,
    0: 48,
    1: 49,
    2: 50,
    3: 51,
    4: 52,
    5: 53,
    6: 54,
    7: 55,
    8: 56,
    '.': 190,
    ',': 188,
};

export const KEYBOARD_SHORTCUTS = {
    [BLOCK_TYPE.UNSTYLED]: {
        other: 'Ctrl+Alt+0',
        macOS: '⌘+Option+0',
    },
    [BLOCK_TYPE.HEADER_ONE]: {
        other: 'Ctrl+Alt+1',
        macOS: '⌘+Option+1',
    },
    [BLOCK_TYPE.HEADER_TWO]: {
        other: 'Ctrl+Alt+2',
        macOS: '⌘+Option+2',
    },
    [BLOCK_TYPE.HEADER_THREE]: {
        other: 'Ctrl+Alt+3',
        macOS: '⌘+Option+3',
    },
    [BLOCK_TYPE.HEADER_FOUR]: {
        other: 'Ctrl+Alt+4',
        macOS: '⌘+Option+4',
    },
    [BLOCK_TYPE.HEADER_FIVE]: {
        other: 'Ctrl+Alt+5',
        macOS: '⌘+Option+5',
    },
    [BLOCK_TYPE.HEADER_SIX]: {
        other: 'Ctrl+Alt+6',
        macOS: '⌘+Option+6',
    },
    [BLOCK_TYPE.UNORDERED_LIST_ITEM]: {
        other: 'Ctrl+Shift+8',
        macOS: '⌘+Shift+8',
    },
    [BLOCK_TYPE.ORDERED_LIST_ITEM]: {
        other: 'Ctrl+Shift+7',
        macOS: '⌘+Shift+7',
    },

    [INLINE_STYLE.BOLD]: { other: 'Ctrl+B', macOS: '⌘+B' },
    [INLINE_STYLE.ITALIC]: { other: 'Ctrl+I', macOS: '⌘+I' },
    [INLINE_STYLE.UNDERLINE]: {
        other: 'Ctrl+U',
        macOS: '⌘+U',
    },
    [INLINE_STYLE.STRIKETHROUGH]: {
        other: 'Ctrl+Shift+X',
        macOS: '⌘+Shift+X',
    },
    [INLINE_STYLE.SUPERSCRIPT]: {
        other: 'Ctrl+.',
        macOS: '⌘+.',
    },
    [INLINE_STYLE.SUBSCRIPT]: {
        other: 'Ctrl+,',
        macOS: '⌘+,',
    },

    [ENTITY_TYPE.LINK]: { other: 'Ctrl+K', macOS: '⌘+K' },

    [BR_TYPE]: {
        other: 'Shift+Enter',
        macOS: 'Shift+Enter',
    },

    [UNDO_TYPE]: {
        other: 'Ctrl+Z',
        macOS: '⌘+Z',
    },

    [REDO_TYPE]: {
        other: 'Ctrl+Shift+Z',
        macOS: '⌘+Shift+Z',
    },
};

export const NBSP = '\u00a0';

export const HANDLED = 'handled';
export const NOT_HANDLED = 'not-handled';

export const BEFORE_INPUT_MAP = {
    '* ': BLOCK_TYPE.UNORDERED_LIST_ITEM,
    '- ': BLOCK_TYPE.UNORDERED_LIST_ITEM,
    '1. ': BLOCK_TYPE.ORDERED_LIST_ITEM,
    // TODO Potentially to be commented out again.
    // See https://github.com/springload/draftail/issues/53
    '# ': BLOCK_TYPE.HEADER_ONE,
    '## ': BLOCK_TYPE.HEADER_TWO,
    '### ': BLOCK_TYPE.HEADER_THREE,
    '#### ': BLOCK_TYPE.HEADER_FOUR,
    '##### ': BLOCK_TYPE.HEADER_FIVE,
    '###### ': BLOCK_TYPE.HEADER_SIX,
    '``` ': BLOCK_TYPE.CODE,
    '> ': BLOCK_TYPE.BLOCKQUOTE,
};

export const BEFORE_INPUT_MAX_LENGTH = Object.keys(BEFORE_INPUT_MAP).reduce(
    (maxLength, input) => Math.max(maxLength, input.length),
    0,
);
<|endoftext|>


<commit_before>/** Subscribr, a tiny event subscriber. */
class Subscribr {

    constructor() {
        this._list = {};
        this._interceptors = [];
    }

    /**
    * Subscribe to an event.
    * @param {string} eventId - the event ID.
    * @param {function} handler - the callback function.
    * @return {function} Subscription destroyer.
    */
    on(eventId, handler) {
        if(!eventId || typeof eventId !== 'string') throw new Error('Invalid event identifier');
        if(!handler || typeof handler !== 'function') throw new Error('Invalid handler');
        if (eventId === '*') {
            const index = this._interceptors.push(handler) - 1;
            return () => this._interceptors.splice(index - 1);
        }
        if (!this._list[eventId]) this._list[eventId] = [];
        const index = this._list[eventId].push(handler) - 1;
        return () => this._list[eventId].splice(index, 1);
    }

    /**
    * Emit an event.
    * @param {string} eventId - the event ID.
    * @param {object} params - handler params.
    */
    emit(eventId, params) {
        if(!eventId || typeof eventId !== 'string') throw new Error('Invalid event identifier');
        [...this._interceptors, ...this._list[eventId]].forEach(handler => handler(params));
    }

    /**
    * List an event's handlers.
    * @param {string} eventId - the event ID.
    * @return {array} Event handlers.
    */
    list(eventId) {
        return eventId ? this._list[eventId] : this._list;
    }

    /**
    * Unsubscribe an event.
    * @param {string} eventId - the event ID.
    */
    remove(eventId) {
        delete this._list[eventId];
    }

    /**
    * Get interceptors.
    * @return {array} Interceptors.
    */
    get interceptors() { return this._interceptors; }

    /**
    * Get all events.
    * @return {object} Events.
    */
    get allEvents() { return this._list; }

}

module.exports = Subscribr;
<commit_msg>1 should be 2nd parameter of splice<commit_after>/** Subscribr, a tiny event subscriber. */
class Subscribr {

    constructor() {
        this._list = {};
        this._interceptors = [];
    }

    /**
    * Subscribe to an event.
    * @param {string} eventId - the event ID.
    * @param {function} handler - the callback function.
    * @return {function} Subscription destroyer.
    */
    on(eventId, handler) {
        if(!eventId || typeof eventId !== 'string') throw new Error('Invalid event identifier');
        if(!handler || typeof handler !== 'function') throw new Error('Invalid handler');
        if (eventId === '*') {
            const index = this._interceptors.push(handler) - 1;
            return () => this._interceptors.splice(index, 1);
        }
        if (!this._list[eventId]) this._list[eventId] = [];
        const index = this._list[eventId].push(handler) - 1;
        return () => this._list[eventId].splice(index, 1);
    }

    /**
    * Emit an event.
    * @param {string} eventId - the event ID.
    * @param {object} params - handler params.
    */
    emit(eventId, params) {
        if(!eventId || typeof eventId !== 'string') throw new Error('Invalid event identifier');
        [...this._interceptors, ...this._list[eventId]].forEach(handler => handler(params));
    }

    /**
    * List an event's handlers.
    * @param {string} eventId - the event ID.
    * @return {array} Event handlers.
    */
    list(eventId) {
        return eventId ? this._list[eventId] : this._list;
    }

    /**
    * Unsubscribe an event.
    * @param {string} eventId - the event ID.
    */
    remove(eventId) {
        delete this._list[eventId];
    }

    /**
    * Get interceptors.
    * @return {array} Interceptors.
    */
    get interceptors() { return this._interceptors; }

    /**
    * Get all events.
    * @return {object} Events.
    */
    get allEvents() { return this._list; }

}

module.exports = Subscribr;
<|endoftext|>


<commit_before>/**
 * PageSpeed Insights Module Component Stories.
 *
 * Site Kit by Google, Copyright 2020 Google LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * External dependencies
 */
import { storiesOf } from '@storybook/react';

/**
 * Internal dependencies
 */
import DashboardPageSpeed from '../assets/js/modules/pagespeed-insights/components/DashboardPageSpeed';
import { STORE_NAME } from '../assets/js/modules/pagespeed-insights/datastore';
import { STORE_NAME as CORE_SITE } from '../assets/js/googlesitekit/datastore/site/constants';
import * as fixtures from '../assets/js/modules/pagespeed-insights/datastore/__fixtures__';
import { STRATEGY_MOBILE, STRATEGY_DESKTOP } from '../assets/js/modules/pagespeed-insights/datastore/constants';
import { WithTestRegistry } from '../tests/js/utils';

storiesOf( 'PageSpeed Insights Module/Components', module )
	.add( 'Dashboard widget', () => {
		const url = fixtures.pagespeedMobile.loadingExperience.id;
		const setupRegistry = ( { dispatch } ) => {
			dispatch( STORE_NAME ).receiveReport( fixtures.pagespeedMobile, { url, strategy: STRATEGY_MOBILE } );
			dispatch( STORE_NAME ).receiveReport( fixtures.pagespeedDesktop, { url, strategy: STRATEGY_DESKTOP } );
			dispatch( CORE_SITE ).receiveSiteInfo( { referenceSiteURL: url } );
		};
		return (
			<WithTestRegistry callback={ setupRegistry }>
				<DashboardPageSpeed />
			</WithTestRegistry>
		);
	} )
;
<commit_msg>Update receive action for PSI report data.<commit_after>/**
 * PageSpeed Insights Module Component Stories.
 *
 * Site Kit by Google, Copyright 2020 Google LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * External dependencies
 */
import { storiesOf } from '@storybook/react';

/**
 * Internal dependencies
 */
import DashboardPageSpeed from '../assets/js/modules/pagespeed-insights/components/DashboardPageSpeed';
import { STORE_NAME } from '../assets/js/modules/pagespeed-insights/datastore';
import { STORE_NAME as CORE_SITE } from '../assets/js/googlesitekit/datastore/site/constants';
import * as fixtures from '../assets/js/modules/pagespeed-insights/datastore/__fixtures__';
import { STRATEGY_MOBILE, STRATEGY_DESKTOP } from '../assets/js/modules/pagespeed-insights/datastore/constants';
import { WithTestRegistry } from '../tests/js/utils';

storiesOf( 'PageSpeed Insights Module/Components', module )
	.add( 'Dashboard widget', () => {
		const url = fixtures.pagespeedMobile.loadingExperience.id;
		const setupRegistry = ( { dispatch } ) => {
			dispatch( STORE_NAME ).receiveGetReport( fixtures.pagespeedMobile, { url, strategy: STRATEGY_MOBILE } );
			dispatch( STORE_NAME ).receiveGetReport( fixtures.pagespeedDesktop, { url, strategy: STRATEGY_DESKTOP } );
			dispatch( CORE_SITE ).receiveSiteInfo( { referenceSiteURL: url } );
		};
		return (
			<WithTestRegistry callback={ setupRegistry }>
				<DashboardPageSpeed />
			</WithTestRegistry>
		);
	} )
;
<|endoftext|>


<commit_before>const Plugin = require('../Plugin')
const Client = require('./Client')
const StatusSocket = require('./Socket')

/**
 * Upload files to Transloadit using Tus.
 */
module.exports = class Transloadit extends Plugin {
  constructor (core, opts) {
    super(core, opts)
    this.type = 'uploader'
    this.id = 'Transloadit'
    this.title = 'Transloadit'

    const defaultLocale = {
      strings: {
        creatingAssembly: 'Preparing upload...',
        creatingAssemblyFailed: 'Transloadit: Could not create assembly',
        encoding: 'Encoding...'
      }
    }

    const defaultOptions = {
      waitForEncoding: false,
      waitForMetadata: false,
      signature: null,
      params: null,
      fields: {},
      locale: defaultLocale
    }

    this.opts = Object.assign({}, defaultOptions, opts)

    this.locale = Object.assign({}, defaultLocale, this.opts.locale)
    this.locale.strings = Object.assign({}, defaultLocale.strings, this.opts.locale.strings)

    this.prepareUpload = this.prepareUpload.bind(this)
    this.afterUpload = this.afterUpload.bind(this)

    if (!this.opts.params) {
      throw new Error('Transloadit: The `params` option is required.')
    }

    let params = this.opts.params
    if (typeof params === 'string') {
      try {
        params = JSON.parse(params)
      } catch (err) {
        // Tell the user that this is not an Uppy bug!
        err.message = 'Transloadit: The `params` option is a malformed JSON string: ' +
          err.message
        throw err
      }
    }

    if (!params.auth || !params.auth.key) {
      throw new Error('Transloadit: The `params.auth.key` option is required. ' +
        'You can find your Transloadit API key at https://transloadit.com/accounts/credentials.')
    }

    this.client = new Client()
    this.sockets = {}
  }

  createAssembly (filesToUpload) {
    this.core.log('Transloadit: create assembly')

    return this.client.createAssembly({
      params: this.opts.params,
      fields: this.opts.fields,
      expectedFiles: Object.keys(filesToUpload).length,
      signature: this.opts.signature
    }).then((assembly) => {
      this.updateState({
        assemblies: Object.assign(this.state.assemblies, {
          [assembly.assembly_id]: assembly
        })
      })

      function attachAssemblyMetadata (file, assembly) {
        // Attach meta parameters for the Tus plugin. See:
        // https://github.com/tus/tusd/wiki/Uploading-to-Transloadit-using-tus#uploading-using-tus
        // TODO Should this `meta` be moved to a `tus.meta` property instead?
        // If the MetaData plugin can add eg. resize parameters, it doesn't
        // make much sense to set those as upload-metadata for tus.
        const meta = Object.assign({}, file.meta, {
          assembly_url: assembly.assembly_url,
          filename: file.name,
          fieldname: 'file'
        })
        // Add assembly-specific Tus endpoint.
        const tus = Object.assign({}, file.tus, {
          endpoint: assembly.tus_url
        })
        const transloadit = {
          assembly: assembly.assembly_id
        }
        return Object.assign(
          {},
          file,
          { meta, tus, transloadit }
        )
      }

      const files = Object.assign({}, this.core.state.files)
      Object.keys(filesToUpload).forEach((id) => {
        files[id] = attachAssemblyMetadata(files[id], assembly)
      })

      this.core.setState({ files })

      this.core.emit('transloadit:assembly', assembly, Object.keys(filesToUpload))

      return this.connectSocket(assembly)
    }).then(() => {
      this.core.log('Transloadit: Created assembly')
    }).catch((err) => {
      this.core.emit('informer', this.opts.locale.strings.creatingAssemblyFailed, 'error', 0)

      // Reject the promise.
      throw err
    })
  }

  shouldWait () {
    return this.opts.waitForEncoding || this.opts.waitForMetadata
  }

  findFile (uploadedFile) {
    const files = this.core.state.files
    for (const id in files) {
      if (!files.hasOwnProperty(id)) {
        continue
      }
      if (files[id].uploadURL === uploadedFile.tus_upload_url) {
        return files[id]
      }
    }
  }

  onFileUploadComplete (assemblyId, uploadedFile) {
    const file = this.findFile(uploadedFile)
    this.updateState({
      files: Object.assign({}, this.state.files, {
        [uploadedFile.id]: {
          id: file.id,
          uploadedFile
        }
      })
    })
    this.core.bus.emit('transloadit:upload', uploadedFile, this.getAssembly(assemblyId))
  }

  onResult (assemblyId, stepName, result) {
    const file = this.state.files[result.original_id]
    // The `file` may not exist if an import robot was used instead of a file upload.
    result.localId = file ? file.id : null

    this.updateState({
      results: this.state.results.concat(result)
    })
    this.core.bus.emit('transloadit:result', stepName, result, this.getAssembly(assemblyId))
  }

  connectSocket (assembly) {
    const socket = new StatusSocket(
      assembly.websocket_url,
      assembly
    )
    this.sockets[assembly.assembly_id] = socket

    socket.on('upload', this.onFileUploadComplete.bind(this, assembly.assembly_id))
    socket.on('error', (error) => {
      this.core.emit('transloadit:assembly-error', assembly, error)
    })

    if (this.opts.waitForEncoding) {
      socket.on('result', this.onResult.bind(this, assembly.assembly_id))
    }

    if (this.opts.waitForEncoding) {
      socket.on('finished', () => {
        this.core.emit('transloadit:complete', assembly)
      })
    } else if (this.opts.waitForMetadata) {
      socket.on('metadata', () => {
        this.core.emit('transloadit:complete', assembly)
      })
    }

    return new Promise((resolve, reject) => {
      socket.on('connect', resolve)
      socket.on('error', reject)
    }).then(() => {
      this.core.log('Transloadit: Socket is ready')
    })
  }

  prepareUpload (fileIDs) {
    const filesToUpload = fileIDs.map(getFile, this).reduce(intoFileMap, {})
    function getFile (fileID) {
      return this.core.state.files[fileID]
    }
    function intoFileMap (map, file) {
      map[file.id] = file
      return map
    }

    fileIDs.forEach((fileID) => {
      this.core.emit('core:preprocess-progress', fileID, {
        mode: 'indeterminate',
        message: this.opts.locale.strings.creatingAssembly
      })
    })
    return this.createAssembly(filesToUpload).then(() => {
      fileIDs.forEach((fileID) => {
        this.core.emit('core:preprocess-complete', fileID)
      })
    })
  }

  afterUpload (fileIDs) {
    // A file ID that is part of this assembly...
    const fileID = fileIDs[0]

    // If we don't have to wait for encoding metadata or results, we can close
    // the socket immediately and finish the upload.
    if (!this.shouldWait()) {
      const file = this.core.getState().files[fileID]
      const socket = this.socket[file.assembly]
      socket.close()
      return
    }

    return new Promise((resolve, reject) => {
      fileIDs.forEach((fileID) => {
        this.core.emit('core:postprocess-progress', fileID, {
          mode: 'indeterminate',
          message: this.opts.locale.strings.encoding
        })
      })

      const onAssemblyFinished = (assembly) => {
        const file = this.core.state.files[fileID]
        // An assembly for a different upload just finished. We can ignore it.
        if (assembly.assembly_id !== file.transloadit.assembly) {
          return
        }
        // Remove this handler once we find the assembly we needed.
        this.core.emitter.off('transloadit:complete', onAssemblyFinished)

        this.client.getAssemblyStatus(assembly.assembly_ssl_url).then((assembly) => {
          this.updateState({
            assemblies: Object.assign({}, this.state.assemblies, {
              [assembly.assembly_id]: assembly
            })
          })

          // TODO set the `file.uploadURL` to a result?
          // We will probably need an option here so the plugin user can tell us
          // which result to pick…?

          fileIDs.forEach((fileID) => {
            this.core.emit('core:postprocess-complete', fileID)
          })
        }).then(resolve, reject)
      }

      const onAssemblyError = (assembly, error) => {
        const file = this.core.state.files[fileID]
        // An assembly for a different upload just errored. We can ignore it.
        if (assembly.assembly_id !== file.transloadit.assembly) {
          return
        }
        // Remove this handler once we find the assembly we needed.
        this.core.emitter.off('transloadit:assembly-error', onAssemblyError)

        // Clear postprocessing state for all our files.
        fileIDs.forEach((fileID) => {
          this.core.emit('core:postprocess-complete', fileID)
        })

        // Reject the `afterUpload()` promise.
        reject(error)
      }

      this.core.on('transloadit:complete', onAssemblyFinished)
      this.core.on('transloadit:assembly-error', onAssemblyError)
    })
  }

  install () {
    this.core.addPreProcessor(this.prepareUpload)
    this.core.addPostProcessor(this.afterUpload)

    this.updateState({
      assemblies: {},
      files: {},
      results: []
    })
  }

  uninstall () {
    this.core.removePreProcessor(this.prepareUpload)
    this.core.removePostProcessor(this.afterUpload)
  }

  getAssembly (id) {
    return this.state.assemblies[id]
  }

  get state () {
    return this.core.state.transloadit || {}
  }

  updateState (newState) {
    const transloadit = Object.assign({}, this.state, newState)

    this.core.setState({ transloadit })
  }
}
<commit_msg>transloadit: Retrieve completed assembly for `complete` event<commit_after>const Plugin = require('../Plugin')
const Client = require('./Client')
const StatusSocket = require('./Socket')

/**
 * Upload files to Transloadit using Tus.
 */
module.exports = class Transloadit extends Plugin {
  constructor (core, opts) {
    super(core, opts)
    this.type = 'uploader'
    this.id = 'Transloadit'
    this.title = 'Transloadit'

    const defaultLocale = {
      strings: {
        creatingAssembly: 'Preparing upload...',
        creatingAssemblyFailed: 'Transloadit: Could not create assembly',
        encoding: 'Encoding...'
      }
    }

    const defaultOptions = {
      waitForEncoding: false,
      waitForMetadata: false,
      signature: null,
      params: null,
      fields: {},
      locale: defaultLocale
    }

    this.opts = Object.assign({}, defaultOptions, opts)

    this.locale = Object.assign({}, defaultLocale, this.opts.locale)
    this.locale.strings = Object.assign({}, defaultLocale.strings, this.opts.locale.strings)

    this.prepareUpload = this.prepareUpload.bind(this)
    this.afterUpload = this.afterUpload.bind(this)

    if (!this.opts.params) {
      throw new Error('Transloadit: The `params` option is required.')
    }

    let params = this.opts.params
    if (typeof params === 'string') {
      try {
        params = JSON.parse(params)
      } catch (err) {
        // Tell the user that this is not an Uppy bug!
        err.message = 'Transloadit: The `params` option is a malformed JSON string: ' +
          err.message
        throw err
      }
    }

    if (!params.auth || !params.auth.key) {
      throw new Error('Transloadit: The `params.auth.key` option is required. ' +
        'You can find your Transloadit API key at https://transloadit.com/accounts/credentials.')
    }

    this.client = new Client()
    this.sockets = {}
  }

  createAssembly (filesToUpload) {
    this.core.log('Transloadit: create assembly')

    return this.client.createAssembly({
      params: this.opts.params,
      fields: this.opts.fields,
      expectedFiles: Object.keys(filesToUpload).length,
      signature: this.opts.signature
    }).then((assembly) => {
      this.updateState({
        assemblies: Object.assign(this.state.assemblies, {
          [assembly.assembly_id]: assembly
        })
      })

      function attachAssemblyMetadata (file, assembly) {
        // Attach meta parameters for the Tus plugin. See:
        // https://github.com/tus/tusd/wiki/Uploading-to-Transloadit-using-tus#uploading-using-tus
        // TODO Should this `meta` be moved to a `tus.meta` property instead?
        // If the MetaData plugin can add eg. resize parameters, it doesn't
        // make much sense to set those as upload-metadata for tus.
        const meta = Object.assign({}, file.meta, {
          assembly_url: assembly.assembly_url,
          filename: file.name,
          fieldname: 'file'
        })
        // Add assembly-specific Tus endpoint.
        const tus = Object.assign({}, file.tus, {
          endpoint: assembly.tus_url
        })
        const transloadit = {
          assembly: assembly.assembly_id
        }
        return Object.assign(
          {},
          file,
          { meta, tus, transloadit }
        )
      }

      const files = Object.assign({}, this.core.state.files)
      Object.keys(filesToUpload).forEach((id) => {
        files[id] = attachAssemblyMetadata(files[id], assembly)
      })

      this.core.setState({ files })

      this.core.emit('transloadit:assembly', assembly, Object.keys(filesToUpload))

      return this.connectSocket(assembly)
    }).then(() => {
      this.core.log('Transloadit: Created assembly')
    }).catch((err) => {
      this.core.emit('informer', this.opts.locale.strings.creatingAssemblyFailed, 'error', 0)

      // Reject the promise.
      throw err
    })
  }

  shouldWait () {
    return this.opts.waitForEncoding || this.opts.waitForMetadata
  }

  findFile (uploadedFile) {
    const files = this.core.state.files
    for (const id in files) {
      if (!files.hasOwnProperty(id)) {
        continue
      }
      if (files[id].uploadURL === uploadedFile.tus_upload_url) {
        return files[id]
      }
    }
  }

  onFileUploadComplete (assemblyId, uploadedFile) {
    const file = this.findFile(uploadedFile)
    this.updateState({
      files: Object.assign({}, this.state.files, {
        [uploadedFile.id]: {
          id: file.id,
          uploadedFile
        }
      })
    })
    this.core.bus.emit('transloadit:upload', uploadedFile, this.getAssembly(assemblyId))
  }

  onResult (assemblyId, stepName, result) {
    const file = this.state.files[result.original_id]
    // The `file` may not exist if an import robot was used instead of a file upload.
    result.localId = file ? file.id : null

    this.updateState({
      results: this.state.results.concat(result)
    })
    this.core.bus.emit('transloadit:result', stepName, result, this.getAssembly(assemblyId))
  }

  onAssemblyFinished (url) {
    this.client.getAssemblyStatus(url).then((assembly) => {
      this.updateState({
        assemblies: Object.assign({}, this.state.assemblies, {
          [assembly.assembly_id]: assembly
        })
      })
      this.core.emit('transloadit:complete', assembly)
    })
  }

  connectSocket (assembly) {
    const socket = new StatusSocket(
      assembly.websocket_url,
      assembly
    )
    this.sockets[assembly.assembly_id] = socket

    socket.on('upload', this.onFileUploadComplete.bind(this, assembly.assembly_id))
    socket.on('error', (error) => {
      this.core.emit('transloadit:assembly-error', assembly, error)
    })

    if (this.opts.waitForEncoding) {
      socket.on('result', this.onResult.bind(this, assembly.assembly_id))
    }

    if (this.opts.waitForEncoding) {
      socket.on('finished', () => {
        this.onAssemblyFinished(assembly.assembly_ssl_url)
      })
    } else if (this.opts.waitForMetadata) {
      socket.on('metadata', () => {
        this.onAssemblyFinished(assembly.assembly_ssl_url)
        this.core.emit('transloadit:complete', assembly)
      })
    }

    return new Promise((resolve, reject) => {
      socket.on('connect', resolve)
      socket.on('error', reject)
    }).then(() => {
      this.core.log('Transloadit: Socket is ready')
    })
  }

  prepareUpload (fileIDs) {
    const filesToUpload = fileIDs.map(getFile, this).reduce(intoFileMap, {})
    function getFile (fileID) {
      return this.core.state.files[fileID]
    }
    function intoFileMap (map, file) {
      map[file.id] = file
      return map
    }

    fileIDs.forEach((fileID) => {
      this.core.emit('core:preprocess-progress', fileID, {
        mode: 'indeterminate',
        message: this.opts.locale.strings.creatingAssembly
      })
    })
    return this.createAssembly(filesToUpload).then(() => {
      fileIDs.forEach((fileID) => {
        this.core.emit('core:preprocess-complete', fileID)
      })
    })
  }

  afterUpload (fileIDs) {
    // A file ID that is part of this assembly...
    const fileID = fileIDs[0]

    // If we don't have to wait for encoding metadata or results, we can close
    // the socket immediately and finish the upload.
    if (!this.shouldWait()) {
      const file = this.core.getState().files[fileID]
      const socket = this.socket[file.assembly]
      socket.close()
      return
    }

    return new Promise((resolve, reject) => {
      fileIDs.forEach((fileID) => {
        this.core.emit('core:postprocess-progress', fileID, {
          mode: 'indeterminate',
          message: this.opts.locale.strings.encoding
        })
      })

      const onAssemblyFinished = (assembly) => {
        const file = this.core.state.files[fileID]
        // An assembly for a different upload just finished. We can ignore it.
        if (assembly.assembly_id !== file.transloadit.assembly) {
          return
        }
        // Remove this handler once we find the assembly we needed.
        this.core.emitter.off('transloadit:complete', onAssemblyFinished)

        // TODO set the `file.uploadURL` to a result?
        // We will probably need an option here so the plugin user can tell us
        // which result to pick…?

        fileIDs.forEach((fileID) => {
          this.core.emit('core:postprocess-complete', fileID)
        })

        resolve()
      }

      const onAssemblyError = (assembly, error) => {
        const file = this.core.state.files[fileID]
        // An assembly for a different upload just errored. We can ignore it.
        if (assembly.assembly_id !== file.transloadit.assembly) {
          return
        }
        // Remove this handler once we find the assembly we needed.
        this.core.emitter.off('transloadit:assembly-error', onAssemblyError)

        // Clear postprocessing state for all our files.
        fileIDs.forEach((fileID) => {
          this.core.emit('core:postprocess-complete', fileID)
        })

        // Reject the `afterUpload()` promise.
        reject(error)
      }

      this.core.on('transloadit:complete', onAssemblyFinished)
      this.core.on('transloadit:assembly-error', onAssemblyError)
    })
  }

  install () {
    this.core.addPreProcessor(this.prepareUpload)
    this.core.addPostProcessor(this.afterUpload)

    this.updateState({
      assemblies: {},
      files: {},
      results: []
    })
  }

  uninstall () {
    this.core.removePreProcessor(this.prepareUpload)
    this.core.removePostProcessor(this.afterUpload)
  }

  getAssembly (id) {
    return this.state.assemblies[id]
  }

  get state () {
    return this.core.state.transloadit || {}
  }

  updateState (newState) {
    const transloadit = Object.assign({}, this.state, newState)

    this.core.setState({ transloadit })
  }
}
<|endoftext|>


<commit_before>// Copyright 2016 The Oppia Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/**
 * @fileoverview Directive for the Search Bar.
 *
 * @author sean@seanlip.org (Sean Lip)
 */

oppia.directive('searchBar', [function() {
  return {
    restrict: 'E',
    templateUrl: 'components/searchBar',
    controller: [
      '$scope', '$rootScope', '$timeout', '$window', 'searchService',
      'oppiaDebouncer', 'ExplorationCreationButtonService', 'urlService',
      'CATEGORY_LIST', 'siteAnalyticsService',
      function(
          $scope, $rootScope, $timeout, $window, searchService,
          oppiaDebouncer, ExplorationCreationButtonService, urlService,
          CATEGORY_LIST, siteAnalyticsService) {
        $scope.searchIsLoading = false;
        $scope.ALL_CATEGORIES = CATEGORY_LIST.map(function(categoryName) {
          return {
            id: categoryName,
            text: categoryName
          };
        });
        $scope.ALL_LANGUAGE_CODES = GLOBALS.LANGUAGE_CODES_AND_NAMES.map(
          function(languageItem) {
            return {
              id: languageItem.code,
              text: languageItem.name
            };
          });

        $scope.searchQuery = '';
        $scope.selectionDetails = {
          categories: {
            description: '',
            itemsName: 'categories',
            masterList: $scope.ALL_CATEGORIES,
            numSelections: 0,
            selections: {},
            summary: ''
          },
          languageCodes: {
            description: '',
            itemsName: 'languages',
            masterList: $scope.ALL_LANGUAGE_CODES,
            numSelections: 0,
            selections: {},
            summary: ''
          }
        };

        // Update the description, numSelections and summary fields of the relevant
        // entry of $scope.selectionDetails.
        var _updateSelectionDetails = function(itemsType) {
          var itemsName = $scope.selectionDetails[itemsType].itemsName;
          var masterList = $scope.selectionDetails[itemsType].masterList;

          var selectedItems = [];
          for (var i = 0; i < masterList.length; i++) {
            if ($scope.selectionDetails[itemsType].selections[masterList[i].id]) {
              selectedItems.push(masterList[i].text);
            }
          }

          var totalCount = selectedItems.length;
          $scope.selectionDetails[itemsType].numSelections = totalCount;

          $scope.selectionDetails[itemsType].summary = (
            totalCount === 0 ? (
              'All ' + itemsName.charAt(0).toUpperCase() + itemsName.substr(1)) :
            totalCount === 1 ? selectedItems[0] :
            totalCount + ' ' + itemsName);

          $scope.selectionDetails[itemsType].description = (
            selectedItems.length > 0 ? selectedItems.join(', ') :
            'All ' + itemsName + ' selected');
        };

        $scope.toggleSelection = function(itemsType, optionName) {
          var selections = $scope.selectionDetails[itemsType].selections;
          if (!selections.hasOwnProperty(optionName)) {
            selections[optionName] = true;
          } else {
            selections[optionName] = !selections[optionName];
          }

          _updateSelectionDetails(itemsType);
          _onSearchQueryChangeExec();
        };

        var _searchBarFullyLoaded = false;

        var _hasChangedSearchQuery = Boolean(urlService.getUrlParams().q);
        var _onSearchQueryChangeExec = function() {
          $scope.searchIsLoading = true;
          searchService.executeSearchQuery(
              $scope.searchQuery, $scope.selectionDetails.categories.selections,
              $scope.selectionDetails.languageCodes.selections, function() {
            $scope.searchIsLoading = false;
            if (!_hasChangedSearchQuery && _searchBarFullyLoaded) {
              _hasChangedSearchQuery = true;
              $rootScope.$broadcast('hasChangedSearchQuery');
            }
          });
        };

        // Initialize the selection descriptions and summaries.
        for (var itemsType in $scope.selectionDetails) {
          _updateSelectionDetails(itemsType);
        }

        $scope.onSearchQueryChange = function(evt) {
          // Query immediately when the enter or space key is pressed.
          if (evt.keyCode == 13 || evt.keyCode == 32) {
            _onSearchQueryChangeExec();
          } else {
            oppiaDebouncer.debounce(_onSearchQueryChangeExec, 400)();
          }
        };

        $scope.$on(
          'preferredLanguageCodesLoaded',
          function(evt, preferredLanguageCodesList) {
            for (var i = 0; i < preferredLanguageCodesList.length; i++) {
              var selections = $scope.selectionDetails.languageCodes.selections;
              var languageCode = preferredLanguageCodesList[i];
              if (!selections.hasOwnProperty(languageCode)) {
                selections[languageCode] = true;
              } else {
                selections[languageCode] = !selections[languageCode];
              }
            }

            _updateSelectionDetails('languageCodes');
            _onSearchQueryChangeExec();

            _searchBarFullyLoaded = true;
          }
        );

        $scope.showCreateExplorationModal = function() {
          ExplorationCreationButtonService.showCreateExplorationModal(
            CATEGORY_LIST);
        };
        $scope.showUploadExplorationModal = function() {
          ExplorationCreationButtonService.showUploadExplorationModal(
            CATEGORY_LIST);
        };

        $scope.onRedirectToLogin = function(destinationUrl) {
          siteAnalyticsService.registerStartLoginEvent('createExplorationButton');
          $timeout(function() {
            $window.location = destinationUrl;
          }, 150);
          return false;
        };
      }
    ]
  };
}]);
<commit_msg>fix lint issues<commit_after>// Copyright 2016 The Oppia Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/**
 * @fileoverview Directive for the Search Bar.
 *
 * @author sean@seanlip.org (Sean Lip)
 */

oppia.directive('searchBar', [function() {
  return {
    restrict: 'E',
    templateUrl: 'components/searchBar',
    controller: [
      '$scope', '$rootScope', '$timeout', '$window', 'searchService',
      'oppiaDebouncer', 'ExplorationCreationButtonService', 'urlService',
      'CATEGORY_LIST', 'siteAnalyticsService',
      function(
          $scope, $rootScope, $timeout, $window, searchService,
          oppiaDebouncer, ExplorationCreationButtonService, urlService,
          CATEGORY_LIST, siteAnalyticsService) {
        $scope.searchIsLoading = false;
        $scope.ALL_CATEGORIES = CATEGORY_LIST.map(function(categoryName) {
          return {
            id: categoryName,
            text: categoryName
          };
        });
        $scope.ALL_LANGUAGE_CODES = GLOBALS.LANGUAGE_CODES_AND_NAMES.map(
          function(languageItem) {
            return {
              id: languageItem.code,
              text: languageItem.name
            };
          });

        $scope.searchQuery = '';
        $scope.selectionDetails = {
          categories: {
            description: '',
            itemsName: 'categories',
            masterList: $scope.ALL_CATEGORIES,
            numSelections: 0,
            selections: {},
            summary: ''
          },
          languageCodes: {
            description: '',
            itemsName: 'languages',
            masterList: $scope.ALL_LANGUAGE_CODES,
            numSelections: 0,
            selections: {},
            summary: ''
          }
        };

        // Update the description, numSelections and summary fields of the
        // relevant entry of $scope.selectionDetails.
        var _updateSelectionDetails = function(itemsType) {
          var itemsName = $scope.selectionDetails[itemsType].itemsName;
          var masterList = $scope.selectionDetails[itemsType].masterList;

          var selectedItems = [];
          for (var i = 0; i < masterList.length; i++) {
            if ($scope.selectionDetails[itemsType]
                      .selections[masterList[i].id]) {
              selectedItems.push(masterList[i].text);
            }
          }

          var totalCount = selectedItems.length;
          $scope.selectionDetails[itemsType].numSelections = totalCount;

          $scope.selectionDetails[itemsType].summary = (
            totalCount === 0 ? (
              'All ' + itemsName.charAt(0).toUpperCase() +
                       itemsName.substr(1)) :
            totalCount === 1 ? selectedItems[0] :
            totalCount + ' ' + itemsName);

          $scope.selectionDetails[itemsType].description = (
            selectedItems.length > 0 ? selectedItems.join(', ') :
            'All ' + itemsName + ' selected');
        };

        $scope.toggleSelection = function(itemsType, optionName) {
          var selections = $scope.selectionDetails[itemsType].selections;
          if (!selections.hasOwnProperty(optionName)) {
            selections[optionName] = true;
          } else {
            selections[optionName] = !selections[optionName];
          }

          _updateSelectionDetails(itemsType);
          _onSearchQueryChangeExec();
        };

        var _searchBarFullyLoaded = false;

        var _hasChangedSearchQuery = Boolean(urlService.getUrlParams().q);
        var _onSearchQueryChangeExec = function() {
          $scope.searchIsLoading = true;
          searchService.executeSearchQuery(
              $scope.searchQuery, $scope.selectionDetails.categories.selections,
              $scope.selectionDetails.languageCodes.selections, function() {
            $scope.searchIsLoading = false;
            if (!_hasChangedSearchQuery && _searchBarFullyLoaded) {
              _hasChangedSearchQuery = true;
              $rootScope.$broadcast('hasChangedSearchQuery');
            }
          });
        };

        // Initialize the selection descriptions and summaries.
        for (var itemsType in $scope.selectionDetails) {
          _updateSelectionDetails(itemsType);
        }

        $scope.onSearchQueryChange = function(evt) {
          // Query immediately when the enter or space key is pressed.
          if (evt.keyCode == 13 || evt.keyCode == 32) {
            _onSearchQueryChangeExec();
          } else {
            oppiaDebouncer.debounce(_onSearchQueryChangeExec, 400)();
          }
        };

        $scope.$on(
          'preferredLanguageCodesLoaded',
          function(evt, preferredLanguageCodesList) {
            for (var i = 0; i < preferredLanguageCodesList.length; i++) {
              var selections = $scope.selectionDetails.languageCodes.selections;
              var languageCode = preferredLanguageCodesList[i];
              if (!selections.hasOwnProperty(languageCode)) {
                selections[languageCode] = true;
              } else {
                selections[languageCode] = !selections[languageCode];
              }
            }

            _updateSelectionDetails('languageCodes');
            _onSearchQueryChangeExec();

            _searchBarFullyLoaded = true;
          }
        );

        $scope.showCreateExplorationModal = function() {
          ExplorationCreationButtonService.showCreateExplorationModal(
            CATEGORY_LIST);
        };
        $scope.showUploadExplorationModal = function() {
          ExplorationCreationButtonService.showUploadExplorationModal(
            CATEGORY_LIST);
        };

        $scope.onRedirectToLogin = function(destinationUrl) {
          siteAnalyticsService.registerStartLoginEvent(
            'createExplorationButton');
          $timeout(function() {
            $window.location = destinationUrl;
          }, 150);
          return false;
        };
      }
    ]
  };
}]);
<|endoftext|>


<commit_before>import idbKeyval from 'idb-keyval';
import { CACHE_NAME, notificationTypes, urls } from './constants';
import { refreshIndex } from './search';

let isLoggedIn = true;

export async function testLogin(request, shouldLoadCache = true) {
  const response = await fetch(request);

  const wasLoggedIn = isLoggedIn;
  isLoggedIn = response.status !== 403;

  if (!isLoggedIn) {
    if (Notification.permission === 'granted') {
      self.registration.showNotification('Login expired\nPlease log in again', {
        icon: '/tweek.png',
        requireInteraction: true,
        tag: notificationTypes.LOGIN,
      });
    }
  } else {
    const loginNotifications = await self.registration.getNotifications({
      tag: notificationTypes.LOGIN,
    });
    loginNotifications.forEach(notification => notification.close());
    if (!wasLoggedIn && shouldLoadCache) {
      refresh();
    }
  }

  return response;
}

export async function refresh() {
  console.log('refreshing cache...');

  try {
    const cache = await caches.open(CACHE_NAME);
    await cache.addAll(urls.CACHE);

    await refreshIndex();

    const manifests = await fetch(urls.MANIFESTS, {credentials: 'include'});
    if (manifests.ok) {
      const data = await manifests.json();
      idbKeyval.clear();
      await Promise.all(data.map(manifest => idbKeyval.set(manifest.key_path, manifest)));
    }
    console.log('cache refreshed');
  } catch (error) {
    console.warn('failed to refresh cache', error);
  }
}
<commit_msg>Create data-actions.js<commit_after>import idbKeyval from 'idb-keyval';
import { CACHE_NAME, notificationTypes, urls } from './constants';
import { refreshIndex } from './search';

let isLoggedIn = true;

export async function testLogin(request, shouldLoadCache = true) {
  const response = await fetch(request);

  const wasLoggedIn = isLoggedIn;
  isLoggedIn = response.status !== 403;

  if (!isLoggedIn) {
    if (Notification.permission === 'granted') {
      self.registration.showNotification('Login expired\nPlease log in again', {
        icon: '/tweek.png',
        requireInteraction: true,
        tag: notificationTypes.LOGIN,
      });
    }
  } else {
    const loginNotifications = await self.registration.getNotifications({
      tag: notificationTypes.LOGIN,
    });
    loginNotifications.forEach(notification => notification.close());
    if (!wasLoggedIn && shouldLoadCache) {
      refresh();
    }
  }

  return response;
}

async function clearCache(){
  const cache = await caches.open(CACHE_NAME);
  const cachedKeys = await cache.keys();
  const urlsToCacheSet = new Set(urls.CACHE);
  await Promise.all(
    urlsToCacheSet.map(key => (console.log('deleting from cache', key.url), cache.delete(key))),
  );
}

export async function refresh() {
  console.log('refreshing cache...');

  try {
    await clearCache();

    await refreshIndex();

    const manifests = await fetch(urls.MANIFESTS, {credentials: 'include'});
    if (manifests.ok) {
      const data = await manifests.json();
      idbKeyval.clear();
      await Promise.all(data.map(manifest => idbKeyval.set(manifest.key_path, manifest)));
    }
    console.log('cache refreshed');
  } catch (error) {
    console.warn('failed to refresh cache', error);
  }
}
<|endoftext|>


<commit_before>var toArray = function(value) {
    return Array.isArray(value) ? value : [value]
}

var concatenateBuffers = function(buffer1, buffer2) {
    var size = buffer1.length + buffer2.length
    var buffer = new Buffer(size)
    buffer1.copy(buffer)
    buffer2.copy(buffer, buffer1.length)
    return buffer
}

exports.UserError = (function() {
    var UserError = function(message) {
        this.message = message // Error.call(this, message) doesn't work
        this.name = "UserError"
    }
    UserError.prototype = new Error
    return UserError
})()

exports.Response = function(response) {
    return {
        response: function() {
            return response
        },
        handleUserError: function(error) {
            this.handleError(new exports.UserError(error.message))
        },
        handleError: function(error) {
            var httpCode
            var message
            if (error instanceof exports.UserError) {
                httpCode = 400
                message = error.message
            } else {
                httpCode = 500
                message = "Internal error"
                console.error(error.stack)
            }
            response.send(message, { 'Content-Type': 'text/plain' }, httpCode)
        }
    }
}

/**
 * Returns a function that handles callback response and throwed errors
 *
 * - Returned values (wich will be casted to an array) are converted to a
 *   call to handledCallback(null[, ...]). Callback isn't called when undefined
 *   is returned.
 * - Throwed errors are converted to a call to handledCallback(error)
 */
exports.handledCallback = function(handledCallback, callback) {
    return function() {
        try {
            var response = callback.apply(null, arguments)
            if (response !== undefined) {
                response = toArray(response)
                response.unshift(null) // Add error ("no error") parameter
                handledCallback.apply(null, response)
            }
        } catch (error) {
            handledCallback.call(null, error)
        }
    }
}

exports.request = function(args, callback) {
    if (! args.url) throw new exports.UserError("No URL specified")
    exports.handledCallback(callback, function() {
        require('request')(args, exports.handledCallback(callback, function(error, response, body) {
            if (error) throw error
            if (response.statusCode >= 400) throw new exports.UserError("Error " + response.statusCode)
            return [response, body]
        }))
    })()
}

exports.download = function(url, callback) {
   this.request({ url: url, encoding: 'binary' }, exports.handledCallback(callback, function(error, response, body) {
       if (error) throw error
       require('temp').open('download', exports.handledCallback(callback, function(error, file) {
           if (error) throw error
           require('fs').write(file.fd, body, null, 'binary', exports.handledCallback(callback, function(error) {
               if (error) throw error
               return [file.path, response]
           }))
       }))
   }))
}

/**
 * A child_process.exec() that accepts args
 */
exports.exec = function(command, args, callback) {
    args = toArray(args)
    var stdout = new Buffer(0)
    var stderr = new Buffer(0)
    var spawned = require('child_process').spawn(command, args)
    spawned.stdout.on('data', function(data) {
        stdout = concatenateBuffers(stdout, data)
    })
    spawned.stderr.on('data', function(data) {
        stderr = concatenateBuffers(stderr, data)
    })
    spawned.on('exit', exports.handledCallback(callback, function(code, signal) {
        if (code) throw new Error(stderr)
        return stdout
    }))
}
<commit_msg>Proper initialization of UserError prototype<commit_after>var toArray = function(value) {
    return Array.isArray(value) ? value : [value]
}

var concatenateBuffers = function(buffer1, buffer2) {
    var size = buffer1.length + buffer2.length
    var buffer = new Buffer(size)
    buffer1.copy(buffer)
    buffer2.copy(buffer, buffer1.length)
    return buffer
}

exports.UserError = (function() {
    var UserError = function(message) {
        this.message = message // Error.call(this, message) doesn't work
        this.name = "UserError"
    }
    UserError.prototype = Error.prototype
    return UserError
})()

exports.Response = function(response) {
    return {
        response: function() {
            return response
        },
        handleUserError: function(error) {
            this.handleError(new exports.UserError(error.message))
        },
        handleError: function(error) {
            var httpCode
            var message
            if (error instanceof exports.UserError) {
                httpCode = 400
                message = error.message
            } else {
                httpCode = 500
                message = "Internal error"
                console.error(error.stack)
            }
            response.send(message, { 'Content-Type': 'text/plain' }, httpCode)
        }
    }
}

/**
 * Returns a function that handles callback response and throwed errors
 *
 * - Returned values (wich will be casted to an array) are converted to a
 *   call to handledCallback(null[, ...]). Callback isn't called when undefined
 *   is returned.
 * - Throwed errors are converted to a call to handledCallback(error)
 */
exports.handledCallback = function(handledCallback, callback) {
    return function() {
        try {
            var response = callback.apply(null, arguments)
            if (response !== undefined) {
                response = toArray(response)
                response.unshift(null) // Add error ("no error") parameter
                handledCallback.apply(null, response)
            }
        } catch (error) {
            handledCallback.call(null, error)
        }
    }
}

exports.request = function(args, callback) {
    if (! args.url) throw new exports.UserError("No URL specified")
    exports.handledCallback(callback, function() {
        require('request')(args, exports.handledCallback(callback, function(error, response, body) {
            if (error) throw error
            if (response.statusCode >= 400) throw new exports.UserError("Error " + response.statusCode)
            return [response, body]
        }))
    })()
}

exports.download = function(url, callback) {
   this.request({ url: url, encoding: 'binary' }, exports.handledCallback(callback, function(error, response, body) {
       if (error) throw error
       require('temp').open('download', exports.handledCallback(callback, function(error, file) {
           if (error) throw error
           require('fs').write(file.fd, body, null, 'binary', exports.handledCallback(callback, function(error) {
               if (error) throw error
               return [file.path, response]
           }))
       }))
   }))
}

/**
 * A child_process.exec() that accepts args
 */
exports.exec = function(command, args, callback) {
    args = toArray(args)
    var stdout = new Buffer(0)
    var stderr = new Buffer(0)
    var spawned = require('child_process').spawn(command, args)
    spawned.stdout.on('data', function(data) {
        stdout = concatenateBuffers(stdout, data)
    })
    spawned.stderr.on('data', function(data) {
        stderr = concatenateBuffers(stderr, data)
    })
    spawned.on('exit', exports.handledCallback(callback, function(code, signal) {
        if (code) throw new Error(stderr)
        return stdout
    }))
}
<|endoftext|>