<commit_before>import Vue from 'vue'

import QAvatar from '../components/avatar/QAvatar.js'
import QIcon from '../components/icon/QIcon.js'
import QBtn from '../components/btn/QBtn.js'
import QBadge from '../components/badge/QBadge.js'

import clone from '../utils/clone.js'
import { noop } from '../utils/event.js'
import { isSSR } from './Platform.js'

let uid = 0
let defaults = {}

const positionList = [
  'top-left', 'top-right',
  'bottom-left', 'bottom-right',
  'top', 'bottom', 'left', 'right', 'center'
]

const groups = {}

const Notifications = {
  name: 'QNotifications',

  data: {
    notifs: {
      center: [],
      left: [],
      right: [],
      top: [],
      'top-left': [],
      'top-right': [],
      bottom: [],
      'bottom-left': [],
      'bottom-right': []
    }
  },

  methods: {
    add (config) {
      if (!config) {
        console.error('Notify: parameter required')
        return false
      }

      let notif = { textColor: 'white' }

      if (typeof config === 'string' || config.ignoreDefaults !== true) {
        Object.assign(notif, defaults)
      }

      Object.assign(
        notif,
        typeof config === 'string'
          ? { message: config }
          : clone(config)
      )

      if (notif.position) {
        if (!positionList.includes(notif.position)) {
          console.error(`Notify: wrong position: ${notif.position}`)
          return false
        }
      }
      else {
        notif.position = 'bottom'
      }

      if (notif.timeout === void 0) {
        notif.timeout = 5000
      }
      else {
        const t = parseInt(notif.timeout, 10)
        if (isNaN(t) || t < 0) {
          console.error(`Notify: wrong timeout: ${notif.timeout}`)
          return false
        }
        notif.timeout = t
      }

      if (notif.timeout === 0) {
        notif.progress = false
      }
      else if (notif.progress === true) {
        notif.__progressStyle = {
          animationDuration: `${notif.timeout + 1000}ms`
        }
      }

      const actions = (config.actions || [])
        .concat(config.ignoreDefaults !== true && Array.isArray(defaults.actions) === true ? defaults.actions : [])

      notif.actions = actions.map(item => {
        const
          handler = item.handler,
          action = clone(item)

        action.handler = typeof handler === 'function'
          ? () => {
            handler()
            !item.noDismiss && notif.__close()
          }
          : () => { notif.__close() }

        return action
      })

      if (typeof config.onDismiss === 'function') {
        notif.onDismiss = config.onDismiss
      }

      if (typeof notif.closeBtn === 'string') {
        notif.actions.push({ label: notif.closeBtn, handler: () => { notif.__close() } })
      }

      if (notif.multiLine === void 0) {
        notif.multiLine = notif.actions.length > 1
      }

      notif.staticClass = [
        `q-notification row items-stretch`,
        notif.color && `bg-${notif.color}`,
        notif.textColor && `text-${notif.textColor}`,
        `q-notification--${notif.multiLine === true ? 'multi-line' : 'standard'}`,
        notif.classes
      ].filter(n => n).join(' ')

      if (notif.group === false) {
        notif.group = void 0
      }
      else if (notif.group !== void 0) {
        if (notif.group === true) {
          // do not replace notifications with different buttons
          notif.group = [
            notif.message,
            notif.caption,
            notif.multiline
          ].concat(notif.actions.map(a => a.label)).join('|')
        }

        notif.group += '|' + notif.position
      }

      const groupNotif = groups[notif.group]

      if (notif.actions.length === 0) {
        notif.actions = void 0
      }

      // wohoo, new notification
      if (groupNotif === void 0) {
        notif.__uid = uid++
        notif.__badge = 1

        if (['left', 'right', 'center'].indexOf(notif.position) !== -1) {
          this.notifs[notif.position].splice(
            Math.floor(this.notifs[notif.position].length / 2),
            0,
            notif
          )
        }
        else {
          const action = notif.position.indexOf('top') > -1 ? 'unshift' : 'push'
          this.notifs[notif.position][action](notif)
        }
      }
      // ok, so it's NOT a new one
      else {
        // reset timeout if any
        if (groupNotif.__timeout !== void 0) {
          clearTimeout(groupNotif.__timeout)
        }

        notif = Object.assign(groups[notif.group], notif)
        groups[notif.group].__badge++
      }

      notif.__close = () => {
        this.remove(notif)
      }

      if (notif.timeout > 0) {
        notif.__timeout = setTimeout(() => {
          notif.__close()
        }, notif.timeout + /* show duration */ 1000)
      }

      return notif.__close
    },

    remove (notif) {
      if (notif.__timeout) { clearTimeout(notif.__timeout) }

      const index = this.notifs[notif.position].indexOf(notif)
      if (index !== -1) {
        groups[notif.group] = void 0

        const el = this.$refs[`notif_${notif.__uid}`]

        if (el) {
          const { width, height } = getComputedStyle(el)

          el.style.left = `${el.offsetLeft}px`
          el.style.width = width
          el.style.height = height
        }

        this.notifs[notif.position].splice(index, 1)

        if (typeof notif.onDismiss === 'function') {
          notif.onDismiss()
        }
      }
    }
  },

  render (h) {
    return h('div', { staticClass: 'q-notifications' }, positionList.map(pos => {
      const
        vert = ['left', 'center', 'right'].includes(pos) ? 'center' : (pos.indexOf('top') > -1 ? 'top' : 'bottom'),
        align = pos.indexOf('left') > -1 ? 'start' : (pos.indexOf('right') > -1 ? 'end' : 'center'),
        classes = ['left', 'right'].includes(pos) ? `items-${pos === 'left' ? 'start' : 'end'} justify-center` : (pos === 'center' ? 'flex-center' : `items-${align}`)

      return h('transition-group', {
        key: pos,
        staticClass: `q-notifications__list q-notifications__list--${vert} fixed column no-wrap ${classes}`,
        tag: 'div',
        props: {
          name: `q-notification--${pos}`,
          mode: 'out-in'
        }
      }, this.notifs[pos].map(notif => {
        let msgChild
        const msgData = { staticClass: 'q-notification__message col' }

        if (notif.html === true) {
          msgData.domProps = {
            innerHTML: notif.caption
              ? `<div>${notif.message}</div><div class="q-notification__caption">${notif.caption}</div>`
              : notif.message
          }
        }
        else {
          const msgNode = [ notif.message ]
          msgChild = notif.caption
            ? [
              h('div', msgNode),
              h('div', { staticClass: 'q-notification__caption' }, [ notif.caption ])
            ]
            : msgNode
        }

        const mainChild = []

        if (notif.icon) {
          mainChild.push(
            h(QIcon, {
              staticClass: 'q-notification__icon col-auto',
              props: { name: notif.icon }
            })
          )
        }
        else if (notif.avatar) {
          mainChild.push(
            h(QAvatar, { staticClass: 'q-notification__avatar col-auto' }, [
              h('img', { attrs: { src: notif.avatar } })
            ])
          )
        }

        mainChild.push(
          h('div', msgData, msgChild)
        )

        const child = [
          h('div', {
            staticClass: 'row items-center' +
              (notif.multiLine === true ? '' : ' col')
          }, mainChild)
        ]

        notif.progress === true && child.push(
          h('div', {
            key: `${notif.__uid}|p|${notif.__badge}`,
            staticClass: 'q-notification__progress',
            style: notif.__progressStyle,
            class: notif.progressClass
          })
        )

        notif.actions !== void 0 && child.push(
          h('div', {
            staticClass: 'q-notification__actions row items-center ' +
              (notif.multiLine === true ? 'justify-end' : 'col-auto')
          }, notif.actions.map(action => h(QBtn, {
            props: { flat: true, ...action },
            on: { click: action.handler }
          })))
        )

        notif.__badge > 1 && child.push(
          h(QBadge, {
            key: `${notif.__uid}|${notif.__badge}`,
            staticClass: 'q-notification__badge',
            props: { color: notif.badgeColor, textColor: notif.badgeTextColor },
            style: notif.badgeStyle,
            class: notif.badgeClass
          }, [ notif.__badge ])
        )

        return h('div', {
          ref: `notif_${notif.__uid}`,
          key: notif.__uid,
          staticClass: notif.staticClass
        }, [
          h('div', {
            staticClass: 'col relative-position border-radius-inherit ' +
              (notif.multiLine === true ? 'column no-wrap justify-center' : 'row items-center')
          }, child)
        ])
      }))
    }))
  }
}

export default {
  create (opts) {
    if (isSSR === true) { return noop }
    return this.__vm.add(opts)
  },
  setDefaults (opts) {
    opts === Object(opts) && Object.assign(defaults, opts)
  },

  install ({ cfg, $q }) {
    if (isSSR === true) {
      $q.notify = noop
      $q.notify.setDefaults = noop
      return
    }

    this.setDefaults(cfg.notify)

    $q.notify = this.create.bind(this)
    $q.notify.setDefaults = this.setDefaults

    const node = document.createElement('div')
    document.body.appendChild(node)

    this.__vm = new Vue(Notifications)
    this.__vm.$mount(node)
  }
}
<commit_msg>fix(Notify): more small fixes<commit_after>import Vue from 'vue'

import QAvatar from '../components/avatar/QAvatar.js'
import QIcon from '../components/icon/QIcon.js'
import QBtn from '../components/btn/QBtn.js'
import QBadge from '../components/badge/QBadge.js'

import clone from '../utils/clone.js'
import { noop } from '../utils/event.js'
import { isSSR } from './Platform.js'

let uid = 0
let defaults = {}

const positionList = [
  'top-left', 'top-right',
  'bottom-left', 'bottom-right',
  'top', 'bottom', 'left', 'right', 'center'
]

const groups = {}

const Notifications = {
  name: 'QNotifications',

  data: {
    notifs: {
      center: [],
      left: [],
      right: [],
      top: [],
      'top-left': [],
      'top-right': [],
      bottom: [],
      'bottom-left': [],
      'bottom-right': []
    }
  },

  methods: {
    add (config) {
      if (!config) {
        console.error('Notify: parameter required')
        return false
      }

      let notif = { textColor: 'white' }

      if (typeof config === 'string' || config.ignoreDefaults !== true) {
        Object.assign(notif, defaults)
      }

      Object.assign(
        notif,
        typeof config === 'string'
          ? { message: config }
          : clone(config)
      )

      if (notif.position) {
        if (!positionList.includes(notif.position)) {
          console.error(`Notify: wrong position: ${notif.position}`)
          return false
        }
      }
      else {
        notif.position = 'bottom'
      }

      if (notif.timeout === void 0) {
        notif.timeout = 5000
      }
      else {
        const t = parseInt(notif.timeout, 10)
        if (isNaN(t) || t < 0) {
          console.error(`Notify: wrong timeout: ${notif.timeout}`)
          return false
        }
        notif.timeout = t
      }

      if (notif.timeout === 0) {
        notif.progress = false
      }
      else if (notif.progress === true) {
        notif.__progressStyle = {
          animationDuration: `${notif.timeout + 1000}ms`
        }
      }

      const actions = (config.actions || [])
        .concat(config.ignoreDefaults !== true && Array.isArray(defaults.actions) === true ? defaults.actions : [])

      notif.actions = actions.map(item => {
        const
          handler = item.handler,
          action = clone(item)

        action.handler = typeof handler === 'function'
          ? () => {
            handler()
            !item.noDismiss && notif.__close()
          }
          : () => { notif.__close() }

        return action
      })

      if (typeof config.onDismiss === 'function') {
        notif.onDismiss = config.onDismiss
      }

      if (typeof notif.closeBtn === 'string') {
        notif.actions.push({ label: notif.closeBtn, handler: () => { notif.__close() } })
      }

      if (notif.multiLine === void 0) {
        notif.multiLine = notif.actions.length > 1
      }

      notif.staticClass = [
        `q-notification row items-stretch`,
        notif.color && `bg-${notif.color}`,
        notif.textColor && `text-${notif.textColor}`,
        `q-notification--${notif.multiLine === true ? 'multi-line' : 'standard'}`,
        notif.classes
      ].filter(n => n).join(' ')

      if (notif.group === false) {
        notif.group = void 0
      }
      else {
        if (notif.group === void 0 || notif.group === true) {
          // do not replace notifications with different buttons
          notif.group = [
            notif.message,
            notif.caption,
            notif.multiline
          ].concat(notif.actions.map(a => a.label)).join('|')
        }

        notif.group += '|' + notif.position
      }

      console.log(notif.group)

      if (notif.actions.length === 0) {
        notif.actions = void 0
      }

      const groupNotif = groups[notif.group]

      // wohoo, new notification
      if (groupNotif === void 0) {
        notif.__uid = uid++
        notif.__badge = 1

        if (['left', 'right', 'center'].indexOf(notif.position) !== -1) {
          this.notifs[notif.position].splice(
            Math.floor(this.notifs[notif.position].length / 2),
            0,
            notif
          )
        }
        else {
          const action = notif.position.indexOf('top') > -1 ? 'unshift' : 'push'
          this.notifs[notif.position][action](notif)
        }

        if (notif.group !== void 0) {
          groups[notif.group] = notif
        }
      }
      // ok, so it's NOT a new one
      else {
        // reset timeout if any
        if (groupNotif.__timeout !== void 0) {
          clearTimeout(groupNotif.__timeout)
        }

        notif = Object.assign(groups[notif.group], notif)
        notif.__badge++
      }

      notif.__close = () => {
        this.remove(notif)
      }

      if (notif.timeout > 0) {
        notif.__timeout = setTimeout(() => {
          notif.__close()
        }, notif.timeout + /* show duration */ 1000)
      }

      return notif.__close
    },

    remove (notif) {
      if (notif.__timeout) { clearTimeout(notif.__timeout) }

      const index = this.notifs[notif.position].indexOf(notif)
      if (index !== -1) {
        if (notif.group !== void 0) {
          delete groups[notif.group]
        }

        const el = this.$refs[`notif_${notif.__uid}`]

        if (el) {
          const { width, height } = getComputedStyle(el)

          el.style.left = `${el.offsetLeft}px`
          el.style.width = width
          el.style.height = height
        }

        this.notifs[notif.position].splice(index, 1)

        if (typeof notif.onDismiss === 'function') {
          notif.onDismiss()
        }
      }
    }
  },

  render (h) {
    return h('div', { staticClass: 'q-notifications' }, positionList.map(pos => {
      const
        vert = ['left', 'center', 'right'].includes(pos) ? 'center' : (pos.indexOf('top') > -1 ? 'top' : 'bottom'),
        align = pos.indexOf('left') > -1 ? 'start' : (pos.indexOf('right') > -1 ? 'end' : 'center'),
        classes = ['left', 'right'].includes(pos) ? `items-${pos === 'left' ? 'start' : 'end'} justify-center` : (pos === 'center' ? 'flex-center' : `items-${align}`)

      return h('transition-group', {
        key: pos,
        staticClass: `q-notifications__list q-notifications__list--${vert} fixed column no-wrap ${classes}`,
        tag: 'div',
        props: {
          name: `q-notification--${pos}`,
          mode: 'out-in'
        }
      }, this.notifs[pos].map(notif => {
        let msgChild
        const msgData = { staticClass: 'q-notification__message col' }

        if (notif.html === true) {
          msgData.domProps = {
            innerHTML: notif.caption
              ? `<div>${notif.message}</div><div class="q-notification__caption">${notif.caption}</div>`
              : notif.message
          }
        }
        else {
          const msgNode = [ notif.message ]
          msgChild = notif.caption
            ? [
              h('div', msgNode),
              h('div', { staticClass: 'q-notification__caption' }, [ notif.caption ])
            ]
            : msgNode
        }

        const mainChild = []

        if (notif.icon) {
          mainChild.push(
            h(QIcon, {
              staticClass: 'q-notification__icon col-auto',
              props: { name: notif.icon }
            })
          )
        }
        else if (notif.avatar) {
          mainChild.push(
            h(QAvatar, { staticClass: 'q-notification__avatar col-auto' }, [
              h('img', { attrs: { src: notif.avatar } })
            ])
          )
        }

        mainChild.push(
          h('div', msgData, msgChild)
        )

        const child = [
          h('div', {
            staticClass: 'row items-center' +
              (notif.multiLine === true ? '' : ' col')
          }, mainChild)
        ]

        notif.progress === true && child.push(
          h('div', {
            key: `${notif.__uid}|p|${notif.__badge}`,
            staticClass: 'q-notification__progress',
            style: notif.__progressStyle,
            class: notif.progressClass
          })
        )

        notif.actions !== void 0 && child.push(
          h('div', {
            staticClass: 'q-notification__actions row items-center ' +
              (notif.multiLine === true ? 'justify-end' : 'col-auto')
          }, notif.actions.map(action => h(QBtn, {
            props: { flat: true, ...action },
            on: { click: action.handler }
          })))
        )

        notif.__badge > 1 && child.push(
          h(QBadge, {
            key: `${notif.__uid}|${notif.__badge}`,
            staticClass: 'q-notification__badge',
            props: { color: notif.badgeColor, textColor: notif.badgeTextColor },
            style: notif.badgeStyle,
            class: notif.badgeClass
          }, [ notif.__badge ])
        )

        return h('div', {
          ref: `notif_${notif.__uid}`,
          key: notif.__uid,
          staticClass: notif.staticClass
        }, [
          h('div', {
            staticClass: 'col relative-position border-radius-inherit ' +
              (notif.multiLine === true ? 'column no-wrap justify-center' : 'row items-center')
          }, child)
        ])
      }))
    }))
  }
}

export default {
  create (opts) {
    if (isSSR === true) { return noop }
    return this.__vm.add(opts)
  },
  setDefaults (opts) {
    opts === Object(opts) && Object.assign(defaults, opts)
  },

  install ({ cfg, $q }) {
    if (isSSR === true) {
      $q.notify = noop
      $q.notify.setDefaults = noop
      return
    }

    this.setDefaults(cfg.notify)

    $q.notify = this.create.bind(this)
    $q.notify.setDefaults = this.setDefaults

    const node = document.createElement('div')
    document.body.appendChild(node)

    this.__vm = new Vue(Notifications)
    this.__vm.$mount(node)
  }
}
<|endoftext|>


<commit_before>'use strict';

// require('../../tools/colorize');

var

lang = require('./util/lang'),
fs = require('fs'),
path = require('path'),
neuron = require('../../lib/neuron'),

UglifyJS = require('uglify-js'),

TRANSFORMER_ROOT = './transformer/',

PRESET_TRANSFORMERS = ['nr', '$'];

var

converter = {

    // parse a piece of code into AST
    parse: function(content){
        return UglifyJS.parse(content);
    },

    convert: function(ast, transformers){
        transformers = transformers || PRESET_TRANSFORMERS;

        converter._prepareToplevelAst(ast);

        ast = converter._applyTransformers(ast, transformers);

        converter._removeGlobalVarDef(ast);

        return ast;
    },

    printCode: function(ast){
        var stream = UglifyJS.OutputStream({
                indent_start: 0,
                indent_level: 4,
                space_colon: true,
                ascii_only: false,
                bracketize: true,
                beautify: true,
                comments: true
            });

        ast.print(stream);

        return stream.toString();
    },

    _prepareToplevelAst: function(ast){

        if(ast instanceof UglifyJS.AST_Toplevel){

            // converter._addGlobalVarDef(ast);

            // figure out scope so that we will never do harmfull replacement
            ast.figure_out_scope();

            // converter._globalScope();
        }
    },

    _addGlobalVarDef: function(ast){
        var global_varname = ['DP', '$'];

        // add the definition of global variables of neuron 1.0 to global scope
        //      global DP;
        //      global $;
        // so Toplevel::figure_out_scope() method will get the right determination of global variables
        ast.body.unshift(
            new UglifyJS.AST_Var({
                definitions: global_varname.map(function(key){
                    var symbol_var = new UglifyJS.AST_SymbolVar({ name: key });

                    if(!converter.global_var){
                        converter.global_var = symbol_var;
                    }

                    return new UglifyJS.AST_VarDef({
                        name: symbol_var
                    });
                })
            })
        );

        return ast;
    },

    // generate the global scope of current ast
    _globalScope: function(){
        converter.global_scope = converter.global_var.scope;
    },

    _removeGlobalVarDef: function(ast){
        // ast.body.shift();

        return ast;
    },

    /**
     * @param {Array.<string|UglifyJS.TreeTransformer>} array
     */
    _applyTransformers: function(ast, array){

        // tree transformers
        array.forEach(function(handler){
            var tt;

            var env = {
                // global_scope: converter.global_scope
            };

            if(neuron.isString(handler)){
                handler = require(TRANSFORMER_ROOT + handler);
            }

            if(neuron.isObject(handler)){
                handler.env = env;
                handler.setup && handler.setup();

                tt = new UglifyJS.TreeTransformer(handler.before || null, handler.after || null);
                ast = ast.transform(tt);
            }
        });

        return ast;
    }
};

module.exports = converter;

<commit_msg>converter: fix a bug on common env<commit_after>'use strict';

// require('../../tools/colorize');

var

lang = require('./util/lang'),
fs = require('fs'),
path = require('path'),
neuron = require('../../lib/neuron'),

UglifyJS = require('uglify-js'),

TRANSFORMER_ROOT = './transformer/',

PRESET_TRANSFORMERS = [ 'nr', 'exports' ];

var

converter = {

    // parse a piece of code into AST
    parse: function(content){
        return UglifyJS.parse(content);
    },

    convert: function(ast, transformers){
        transformers = transformers || PRESET_TRANSFORMERS;

        converter._prepareToplevelAst(ast);

        ast = converter._applyTransformers(ast, transformers);

        converter._removeGlobalVarDef(ast);

        return ast;
    },

    printCode: function(ast){
        var stream = UglifyJS.OutputStream({
                indent_start: 0,
                indent_level: 4,
                space_colon: true,
                ascii_only: false,
                bracketize: true,
                beautify: true,
                comments: true
            });

        ast.print(stream);

        return stream.toString();
    },

    _prepareToplevelAst: function(ast){

        if(ast instanceof UglifyJS.AST_Toplevel){

            // converter._addGlobalVarDef(ast);

            // figure out scope so that we will never do harmfull replacement
            ast.figure_out_scope();

            // converter._globalScope();
        }
    },

    _addGlobalVarDef: function(ast){
        var global_varname = ['DP', '$'];

        // add the definition of global variables of neuron 1.0 to global scope
        //      global DP;
        //      global $;
        // so Toplevel::figure_out_scope() method will get the right determination of global variables
        ast.body.unshift(
            new UglifyJS.AST_Var({
                definitions: global_varname.map(function(key){
                    var symbol_var = new UglifyJS.AST_SymbolVar({ name: key });

                    if(!converter.global_var){
                        converter.global_var = symbol_var;
                    }

                    return new UglifyJS.AST_VarDef({
                        name: symbol_var
                    });
                })
            })
        );

        return ast;
    },

    // generate the global scope of current ast
    _globalScope: function(){
        converter.global_scope = converter.global_var.scope;
    },

    _removeGlobalVarDef: function(ast){
        // ast.body.shift();

        return ast;
    },

    /**
     * @param {Array.<string|UglifyJS.TreeTransformer>} array
     */
    _applyTransformers: function(ast, array){

        var env = {};

        // tree transformers
        array.forEach(function(handler){
            var tt;

            if(neuron.isString(handler)){
                handler = require(TRANSFORMER_ROOT + handler);
            }

            if(neuron.isObject(handler)){
                handler.env = env;
                handler.setup && handler.setup();

                tt = new UglifyJS.TreeTransformer(handler.before || null, handler.after || null);
                ast = ast.transform(tt);
            }
        });

        return ast;
    }
};

module.exports = converter;

<|endoftext|>


<commit_before>var Discord = require('discord.js');

var ytdl = require('ytdl-core');
var request = require('request');
var url = require('url');

// Output version information in console
var git = require('git-rev');

git.short(commit => git.branch(branch => {
  console.log(`Lethe#${branch}@${commit}`);
}));

var shouldDisallowQueue = require('./lib/permission-checks.js');
var VideoFormat = require('./lib/video-format.js');
var YoutubeStream = require('./lib/youtube-stream.js');
var Saved = require('./lib/saved.js');
Saved.read();

var Config = require('./lib/config.js');

var client = new Discord.Client();

// Handle discord.js warnings
client.on('warn', (m) => console.log('[warn]', m));
client.on('debug', (m) => console.log('[debug]', m));

var playQueue = [];
var boundChannel = false;
var currentStream = false;
var currentVideo = false;

var botMention = false;

// Handling api key
if (process.argv[4]) {
  var apiKey = process.argv[4];
} else {
  var apiKey = false;
}

client.on('ready', () => {
  botMention = `<@${client.user.id}>`;
  console.log(`Bot mention: ${botMention}`);
});

client.on('message', m => {
  if (!botMention) return;

  if (checkCommand(m, 'info') && m.content.startsWith(`${botMention} info`)) {
    git.short(commit => git.branch(branch => {
      client.reply(m, `Version: \`Lethe#${branch}@${commit}\`. Info about Lethe can be found at https://github.com/meew0/Lethe.`);
    }));
    return;
  }

  if (checkCommand(m, 'init') && m.content.startsWith(`${botMention} i`)) { // init
    if (boundChannel) return;
    var channelToJoin = spliceArguments(m.content)[1];
    for (var channel of m.channel.server.channels) {
      if (channel instanceof Discord.VoiceChannel) {
        if (!channelToJoin || channel.name === channelToJoin) {
          boundChannel = m.channel;
          client.reply(m, `Binding to text channel <#${boundChannel.id}> and voice channel **${channel.name}** \`(${channel.id})\``);
          client.joinVoiceChannel(channel).catch(error);
          break;
        }
      }
    }
  }

  if (checkCommand(m, 'destroy') && m.content.startsWith(`${botMention} d`)) { // destroy
    if (!boundChannel) return;
    client.reply(m, `Unbinding from <#${boundChannel.id}> and destroying voice connection`);
    playQueue = [];
    client.internal.leaveVoiceChannel();
    boundChannel = false;
    currentStream = false;
    currentVideo = false;
    return;
  }

  // Only respond to other messages inside the bound channel
  if (!m.channel.equals(boundChannel)) return;

  if (checkCommand(m, 'next') && m.content.startsWith(`${botMention} n`)) { // next
    playStopped();
  }

  if (checkCommand(m, 'yq') && m.content.startsWith(`${botMention} yq`) // youtube query
    || m.content.startsWith(`${botMention} qq`) // queue query
    || m.content.startsWith(`${botMention} pq`) // play query
    || m.content.startsWith(`${botMention} ytq`)) {

    if (apiKey == false) {
      client.reply(m, 'Search is disabled (no API KEY found).');
      return;
    }

    var args = spliceArguments(m.content)[1];

    if (!args) {
      client.reply(m, 'You need to specify a search parameter.');
      return;
    }

    var requestUrl = 'https://www.googleapis.com/youtube/v3/search' +
      `?part=snippet&q=${escape(args)}&key=${apiKey}`;

    request(requestUrl, (error, response, body) => {
      if (!error && response.statusCode == 200) {
        body = JSON.parse(body);
        if (body.items.length == 0) {
          client.reply(m, 'Your query gave 0 results.');
          return;
        }

        var vid = body.items[0].id.videoId;
        getInfoAndQueue(vid, m);
      } else {
        client.reply(m, 'There was an error searching.');
        return;
      }
    });

    return; // have to stop propagation
  }

  if (checkCommand(m, 'pl') && m.content.startsWith(`${botMention} pl`)) { // playlist
    if (apiKey == false) {
      client.reply(m, 'Playlist adding is disabled (no API KEY found).');
      return;
    }

    var pid = spliceArguments(m.content)[1];

    if (!pid) {
      client.reply(m, 'You need to specify a playlist ID!');
      return;
    }

    var requestUrl = 'https://www.googleapis.com/youtube/v3/playlistItems' +
      `?part=contentDetails&maxResults=50&playlistId=${pid}&key=${apiKey}`;

    request(requestUrl, (error, response, body) => {
      if (!error && response.statusCode == 200) {
        body = JSON.parse(body);
        if (body.items.length == 0) {
          client.reply(m, 'That playlist has no videos.');
          return;
        }

        client.reply(m, `Loading ${body.items.length} videos...`);
        var suppress = 0;
        body.items.forEach((elem, idx) => {
          var vid = elem.contentDetails.videoId;
          if (idx == 1) suppress = body.items.length - 2;
          if (idx == 2) suppress = -1;
          getInfoAndQueue(vid, m, suppress);
        });
      } else {
        client.reply(m, 'There was an error finding playlist with that id.');
        return;
      }
    });

    return;
  }

  if (checkCommand(m, 'yt') && m.content.startsWith(`${botMention} y`) // youtube
    || m.content.startsWith(`${botMention} q`) // queue
    || m.content.startsWith(`${botMention} p`)) { // play

    var vidList = spliceArguments(m.content)[1];

    var vids = vidList.split(',');
    var suppress = 0;
    vids.forEach((vid, idx) => {
      if (idx == 1) suppress = vids.length - 2;
      if (idx == 2) suppress = -1;
      parseVidAndQueue(vid, m, suppress);
    });
  }

  if (checkCommand(m, 'replay') && m.content.startsWith(`${botMention} r`)) { // replay
    playQueue.push(currentVideo);
    client.reply(m, `Queued ${VideoFormat.prettyPrint(currentVideo)}`);
  }

  if (checkCommand(m, 'shuffle') && m.content.startsWith(`${botMention} sh`)) { // shuffle
    if (playQueue.length < 2) {
      client.reply(m, 'Not enough songs in the queue.');
      return;
    } else {
      shuffle(playQueue);
      client.reply(m, 'Songs in the queue have been shuffled.');
    }

    return;
  }

  if (checkCommand(m, 'link') && m.content.startsWith(`${botMention} link`)) {
    if (currentVideo) client.reply(m, `<${currentVideo.loaderUrl}>`);
    return; // stop propagation
  }

  if (checkCommand(m, 'list saved') && m.content.startsWith(`${botMention} list s`)) { // list saved
    var formattedList = 'Here are the videos currently saved: \n';
    for (var key in Saved.saved.videos) {
      if (Saved.saved.videos.hasOwnProperty(key)) {
        formattedList += `*${key}*: ${VideoFormat.prettyPrint(Saved.saved.videos[key])}\n`;
      }
    }

    client.reply(m, formattedList);
    return; // so list doesn't get triggered
  }

  if (checkCommand(m, 'list') && m.content.startsWith(`${botMention} l`)) { // list
    var formattedList = 'Here are the videos currently in the play queue, from first added to last added: \n';
    formattedList += `Currently playing: ${VideoFormat.prettyPrintWithUser(currentVideo)}\n`;

    var shouldBreak = false;

    playQueue.forEach((video, idx) => {
      if (shouldBreak) return;

      var formattedVideo = `${idx + 1}. ${VideoFormat.prettyPrintWithUser(video)}\n`;

      if ((formattedList.length + formattedVideo.length) > 1950) {
        formattedList += `... and ${playQueue.length - idx} more`;
        shouldBreak = true;
      } else {
        formattedList += formattedVideo;
      }
    });
    client.reply(m, formattedList);
  }

  if (checkCommand(m, 'save') && m.content.startsWith(`${botMention} s`)) { // save
    var argument = spliceArguments(m.content)[1];
    if (!argument) {
      client.reply(m, 'You need to specify a video and a keyword!');
      return;
    }

    var splitArgs = spliceArguments(argument, 1);

    var vid = splitArgs[0];

    if (vid === 'current') {
      if (currentVideo) vid = currentVideo.vid;
      else {
        client.reply(m, `Couldn't retrieve video information of current video!`);
      }
    }

    var requestUrl = 'http://www.youtube.com/watch?v=' + vid;
    ytdl.getInfo(requestUrl, (err, info) => {
      if (err) handleYTError(err);
      else saveVideo(info, vid, splitArgs[1], m);
    });
  }

  if (checkCommand(m, 'time') && m.content.startsWith(`${botMention} t`)) { // time
    var streamTime = client.internal.voiceConnection.streamTime; // in ms
    var videoTime = currentVideo.length_seconds;
    client.reply(m, `${VideoFormat.prettyTime(streamTime)} / ${VideoFormat.prettyTime(videoTime * 1000)} (${(streamTime / (videoTime * 10)).toFixed(2)} %)`);
  }
});

function parseVidAndQueue(vid, m, suppress) {
  if (/^http/.test(vid)) {
    if (url.parse(vid, true).query.v) {
      vid = url.parse(vid, true).query.v;
    }
  }

  vid = Saved.possiblyRetrieveVideo(vid);
  if (!vid) {
    client.reply(m, 'You need to specify a video!');
    return;
  }

  getInfoAndQueue(vid, m, suppress);
}

function getInfoAndQueue(vid, m, suppress) {
  requestUrl = 'http://www.youtube.com/watch?v=' + vid;
  ytdl.getInfo(requestUrl, (err, info) => {
    if (err) handleYTError(err);
    else {
      info.vid = vid;
      possiblyQueue(info, m.author.id, m, suppress);
    }
  });
}

function spliceArguments(message, after) {
  after = after || 2;
  var rest = message.split(' ');
  var removed = rest.splice(0, after);
  return [removed.join(' '), rest.join(' ')];
}

function saveVideo(video, vid, keywords, m) {
  simplified = VideoFormat.simplify(video, vid);
  if (Saved.saved.videos.hasOwnProperty(keywords)) client.reply(m, `Warning: ${VideoFormat.simplePrint(Saved.saved.videos[keywords])} is already saved as *${keywords}*! Overwriting.`);

  var key;
  if (key = Saved.isVideoSaved(vid)) client.reply(m, `Warning: This video is already saved as *${key}*! Adding it anyway as *${keywords}*.`);

  Saved.saved.videos[keywords] = simplified;
  client.reply(m, `Saved video ${VideoFormat.prettyPrint(video)} as *${keywords}*`);
  Saved.write();
}

function possiblyQueue(video, userId, m, suppress) {
  video.userId = userId;
  suppress = (suppress === undefined) ? false : suppress;
  reason = shouldDisallowQueue(playQueue, video, Config);
  if (!userIsAdmin(userId) && reason) {
    client.reply(m, `You can't queue ${VideoFormat.simplePrint(video)} right now! Reason: ${reason}`);
  } else {
    playQueue.push(video);
    if (suppress == 0) client.reply(m, `Queued ${VideoFormat.prettyPrint(video)}`);
    else if (suppress > -1) client.reply(m, `Queued ${VideoFormat.prettyPrint(video)} and ${suppress} other videos`);

    // Start playing if not playing yet
    if (!currentVideo) nextInQueue();
  }
}

function handleYTError(err) {
  if (err.toString().indexOf('Code 150') > -1) {
    // Video unavailable in country
    boundChannel.sendMessage('This video is unavailable in the country the bot is running in! Please try a different video.');
  } else {
    boundChannel.sendMessage('An error occurred while getting video information! Please try a different video.');
  }

  console.log(err.toString());
}

function playStopped() {
  if (client.internal.voiceConnection) client.internal.voiceConnection.stopPlaying();

  boundChannel.sendMessage(`Finished playing ${VideoFormat.simplePrint(currentVideo)}`);
  currentVideo = false;
  nextInQueue();
}

function play(video) {
  currentVideo = video;
  if (client.internal.voiceConnection) {
    var connection = client.internal.voiceConnection;
    currentStream = YoutubeStream.getStream(video.loaderUrl);

    currentStream.on('error', (err) => {
      boundChannel.sendMessage(`There was an error during playback! **${err}**`);
    });

    currentStream.on('end', () => setTimeout(playStopped, Config.timeOffset || 8000)); // 8 second leeway for bad timing
    connection.playRawStream(currentStream).then(intent => {
      boundChannel.sendMessage(`Playing ${VideoFormat.prettyPrint(video)}`);
    });
  }
}

function userIsAdmin(user) {
  return Config.adminIds.indexOf(user.id) > -1;
}

function checkCommand(m, command) {
  if (Config.commandsRestrictedToAdmins[command]) {
    if (!userIsAdmin(m.author.id)) {
      client.reply(m, `You don't have permission to execute that command!`);
      return false;
    }
  }

  return true;
}

function nextInQueue() {
  if (playQueue.length > 0) {
    next = playQueue.shift();
    play(next);
  }
}

function shuffle(array) {
  var counter = array.length;
  var temp;
  var index;

  // While there are elements in the array
  while (counter > 0) {
    index = Math.floor(Math.random() * counter);

    counter--;

    temp = array[counter];
    array[counter] = array[index];
    array[index] = temp;
  }

  return array;
}

function error(argument) {
  console.log(argument.stack);
}

// Email and password over command line
client.login(process.argv[2], process.argv[3]).catch((e) => console.log(e));
<commit_msg>Don't parse messages from the bot itself<commit_after>var Discord = require('discord.js');

var ytdl = require('ytdl-core');
var request = require('request');
var url = require('url');

// Output version information in console
var git = require('git-rev');

git.short(commit => git.branch(branch => {
  console.log(`Lethe#${branch}@${commit}`);
}));

var shouldDisallowQueue = require('./lib/permission-checks.js');
var VideoFormat = require('./lib/video-format.js');
var YoutubeStream = require('./lib/youtube-stream.js');
var Saved = require('./lib/saved.js');
Saved.read();

var Config = require('./lib/config.js');

var client = new Discord.Client();

// Handle discord.js warnings
client.on('warn', (m) => console.log('[warn]', m));
client.on('debug', (m) => console.log('[debug]', m));

var playQueue = [];
var boundChannel = false;
var currentStream = false;
var currentVideo = false;

var botMention = false;

// Handling api key
if (process.argv[4]) {
  var apiKey = process.argv[4];
} else {
  var apiKey = false;
}

client.on('ready', () => {
  botMention = `<@${client.user.id}>`;
  console.log(`Bot mention: ${botMention}`);
});

client.on('message', m => {
  if (!botMention) return;
  if (client.user.id == m.user.id) return;

  if (checkCommand(m, 'info') && m.content.startsWith(`${botMention} info`)) {
    git.short(commit => git.branch(branch => {
      client.reply(m, `Version: \`Lethe#${branch}@${commit}\`. Info about Lethe can be found at https://github.com/meew0/Lethe.`);
    }));
    return;
  }

  if (checkCommand(m, 'init') && m.content.startsWith(`${botMention} i`)) { // init
    if (boundChannel) return;
    var channelToJoin = spliceArguments(m.content)[1];
    for (var channel of m.channel.server.channels) {
      if (channel instanceof Discord.VoiceChannel) {
        if (!channelToJoin || channel.name === channelToJoin) {
          boundChannel = m.channel;
          client.reply(m, `Binding to text channel <#${boundChannel.id}> and voice channel **${channel.name}** \`(${channel.id})\``);
          client.joinVoiceChannel(channel).catch(error);
          break;
        }
      }
    }
  }

  if (checkCommand(m, 'destroy') && m.content.startsWith(`${botMention} d`)) { // destroy
    if (!boundChannel) return;
    client.reply(m, `Unbinding from <#${boundChannel.id}> and destroying voice connection`);
    playQueue = [];
    client.internal.leaveVoiceChannel();
    boundChannel = false;
    currentStream = false;
    currentVideo = false;
    return;
  }

  // Only respond to other messages inside the bound channel
  if (!m.channel.equals(boundChannel)) return;

  if (checkCommand(m, 'next') && m.content.startsWith(`${botMention} n`)) { // next
    playStopped();
  }

  if (checkCommand(m, 'yq') && m.content.startsWith(`${botMention} yq`) // youtube query
    || m.content.startsWith(`${botMention} qq`) // queue query
    || m.content.startsWith(`${botMention} pq`) // play query
    || m.content.startsWith(`${botMention} ytq`)) {

    if (apiKey == false) {
      client.reply(m, 'Search is disabled (no API KEY found).');
      return;
    }

    var args = spliceArguments(m.content)[1];

    if (!args) {
      client.reply(m, 'You need to specify a search parameter.');
      return;
    }

    var requestUrl = 'https://www.googleapis.com/youtube/v3/search' +
      `?part=snippet&q=${escape(args)}&key=${apiKey}`;

    request(requestUrl, (error, response, body) => {
      if (!error && response.statusCode == 200) {
        body = JSON.parse(body);
        if (body.items.length == 0) {
          client.reply(m, 'Your query gave 0 results.');
          return;
        }

        var vid = body.items[0].id.videoId;
        getInfoAndQueue(vid, m);
      } else {
        client.reply(m, 'There was an error searching.');
        return;
      }
    });

    return; // have to stop propagation
  }

  if (checkCommand(m, 'pl') && m.content.startsWith(`${botMention} pl`)) { // playlist
    if (apiKey == false) {
      client.reply(m, 'Playlist adding is disabled (no API KEY found).');
      return;
    }

    var pid = spliceArguments(m.content)[1];

    if (!pid) {
      client.reply(m, 'You need to specify a playlist ID!');
      return;
    }

    var requestUrl = 'https://www.googleapis.com/youtube/v3/playlistItems' +
      `?part=contentDetails&maxResults=50&playlistId=${pid}&key=${apiKey}`;

    request(requestUrl, (error, response, body) => {
      if (!error && response.statusCode == 200) {
        body = JSON.parse(body);
        if (body.items.length == 0) {
          client.reply(m, 'That playlist has no videos.');
          return;
        }

        client.reply(m, `Loading ${body.items.length} videos...`);
        var suppress = 0;
        body.items.forEach((elem, idx) => {
          var vid = elem.contentDetails.videoId;
          if (idx == 1) suppress = body.items.length - 2;
          if (idx == 2) suppress = -1;
          getInfoAndQueue(vid, m, suppress);
        });
      } else {
        client.reply(m, 'There was an error finding playlist with that id.');
        return;
      }
    });

    return;
  }

  if (checkCommand(m, 'yt') && m.content.startsWith(`${botMention} y`) // youtube
    || m.content.startsWith(`${botMention} q`) // queue
    || m.content.startsWith(`${botMention} p`)) { // play

    var vidList = spliceArguments(m.content)[1];

    var vids = vidList.split(',');
    var suppress = 0;
    vids.forEach((vid, idx) => {
      if (idx == 1) suppress = vids.length - 2;
      if (idx == 2) suppress = -1;
      parseVidAndQueue(vid, m, suppress);
    });
  }

  if (checkCommand(m, 'replay') && m.content.startsWith(`${botMention} r`)) { // replay
    playQueue.push(currentVideo);
    client.reply(m, `Queued ${VideoFormat.prettyPrint(currentVideo)}`);
  }

  if (checkCommand(m, 'shuffle') && m.content.startsWith(`${botMention} sh`)) { // shuffle
    if (playQueue.length < 2) {
      client.reply(m, 'Not enough songs in the queue.');
      return;
    } else {
      shuffle(playQueue);
      client.reply(m, 'Songs in the queue have been shuffled.');
    }

    return;
  }

  if (checkCommand(m, 'link') && m.content.startsWith(`${botMention} link`)) {
    if (currentVideo) client.reply(m, `<${currentVideo.loaderUrl}>`);
    return; // stop propagation
  }

  if (checkCommand(m, 'list saved') && m.content.startsWith(`${botMention} list s`)) { // list saved
    var formattedList = 'Here are the videos currently saved: \n';
    for (var key in Saved.saved.videos) {
      if (Saved.saved.videos.hasOwnProperty(key)) {
        formattedList += `*${key}*: ${VideoFormat.prettyPrint(Saved.saved.videos[key])}\n`;
      }
    }

    client.reply(m, formattedList);
    return; // so list doesn't get triggered
  }

  if (checkCommand(m, 'list') && m.content.startsWith(`${botMention} l`)) { // list
    var formattedList = 'Here are the videos currently in the play queue, from first added to last added: \n';
    formattedList += `Currently playing: ${VideoFormat.prettyPrintWithUser(currentVideo)}\n`;

    var shouldBreak = false;

    playQueue.forEach((video, idx) => {
      if (shouldBreak) return;

      var formattedVideo = `${idx + 1}. ${VideoFormat.prettyPrintWithUser(video)}\n`;

      if ((formattedList.length + formattedVideo.length) > 1950) {
        formattedList += `... and ${playQueue.length - idx} more`;
        shouldBreak = true;
      } else {
        formattedList += formattedVideo;
      }
    });
    client.reply(m, formattedList);
  }

  if (checkCommand(m, 'save') && m.content.startsWith(`${botMention} s`)) { // save
    var argument = spliceArguments(m.content)[1];
    if (!argument) {
      client.reply(m, 'You need to specify a video and a keyword!');
      return;
    }

    var splitArgs = spliceArguments(argument, 1);

    var vid = splitArgs[0];

    if (vid === 'current') {
      if (currentVideo) vid = currentVideo.vid;
      else {
        client.reply(m, `Couldn't retrieve video information of current video!`);
      }
    }

    var requestUrl = 'http://www.youtube.com/watch?v=' + vid;
    ytdl.getInfo(requestUrl, (err, info) => {
      if (err) handleYTError(err);
      else saveVideo(info, vid, splitArgs[1], m);
    });
  }

  if (checkCommand(m, 'time') && m.content.startsWith(`${botMention} t`)) { // time
    var streamTime = client.internal.voiceConnection.streamTime; // in ms
    var videoTime = currentVideo.length_seconds;
    client.reply(m, `${VideoFormat.prettyTime(streamTime)} / ${VideoFormat.prettyTime(videoTime * 1000)} (${(streamTime / (videoTime * 10)).toFixed(2)} %)`);
  }
});

function parseVidAndQueue(vid, m, suppress) {
  if (/^http/.test(vid)) {
    if (url.parse(vid, true).query.v) {
      vid = url.parse(vid, true).query.v;
    }
  }

  vid = Saved.possiblyRetrieveVideo(vid);
  if (!vid) {
    client.reply(m, 'You need to specify a video!');
    return;
  }

  getInfoAndQueue(vid, m, suppress);
}

function getInfoAndQueue(vid, m, suppress) {
  requestUrl = 'http://www.youtube.com/watch?v=' + vid;
  ytdl.getInfo(requestUrl, (err, info) => {
    if (err) handleYTError(err);
    else {
      info.vid = vid;
      possiblyQueue(info, m.author.id, m, suppress);
    }
  });
}

function spliceArguments(message, after) {
  after = after || 2;
  var rest = message.split(' ');
  var removed = rest.splice(0, after);
  return [removed.join(' '), rest.join(' ')];
}

function saveVideo(video, vid, keywords, m) {
  simplified = VideoFormat.simplify(video, vid);
  if (Saved.saved.videos.hasOwnProperty(keywords)) client.reply(m, `Warning: ${VideoFormat.simplePrint(Saved.saved.videos[keywords])} is already saved as *${keywords}*! Overwriting.`);

  var key;
  if (key = Saved.isVideoSaved(vid)) client.reply(m, `Warning: This video is already saved as *${key}*! Adding it anyway as *${keywords}*.`);

  Saved.saved.videos[keywords] = simplified;
  client.reply(m, `Saved video ${VideoFormat.prettyPrint(video)} as *${keywords}*`);
  Saved.write();
}

function possiblyQueue(video, userId, m, suppress) {
  video.userId = userId;
  suppress = (suppress === undefined) ? false : suppress;
  reason = shouldDisallowQueue(playQueue, video, Config);
  if (!userIsAdmin(userId) && reason) {
    client.reply(m, `You can't queue ${VideoFormat.simplePrint(video)} right now! Reason: ${reason}`);
  } else {
    playQueue.push(video);
    if (suppress == 0) client.reply(m, `Queued ${VideoFormat.prettyPrint(video)}`);
    else if (suppress > -1) client.reply(m, `Queued ${VideoFormat.prettyPrint(video)} and ${suppress} other videos`);

    // Start playing if not playing yet
    if (!currentVideo) nextInQueue();
  }
}

function handleYTError(err) {
  if (err.toString().indexOf('Code 150') > -1) {
    // Video unavailable in country
    boundChannel.sendMessage('This video is unavailable in the country the bot is running in! Please try a different video.');
  } else {
    boundChannel.sendMessage('An error occurred while getting video information! Please try a different video.');
  }

  console.log(err.toString());
}

function playStopped() {
  if (client.internal.voiceConnection) client.internal.voiceConnection.stopPlaying();

  boundChannel.sendMessage(`Finished playing ${VideoFormat.simplePrint(currentVideo)}`);
  currentVideo = false;
  nextInQueue();
}

function play(video) {
  currentVideo = video;
  if (client.internal.voiceConnection) {
    var connection = client.internal.voiceConnection;
    currentStream = YoutubeStream.getStream(video.loaderUrl);

    currentStream.on('error', (err) => {
      boundChannel.sendMessage(`There was an error during playback! **${err}**`);
    });

    currentStream.on('end', () => setTimeout(playStopped, Config.timeOffset || 8000)); // 8 second leeway for bad timing
    connection.playRawStream(currentStream).then(intent => {
      boundChannel.sendMessage(`Playing ${VideoFormat.prettyPrint(video)}`);
    });
  }
}

function userIsAdmin(user) {
  return Config.adminIds.indexOf(user.id) > -1;
}

function checkCommand(m, command) {
  if (Config.commandsRestrictedToAdmins[command]) {
    if (!userIsAdmin(m.author.id)) {
      client.reply(m, `You don't have permission to execute that command!`);
      return false;
    }
  }

  return true;
}

function nextInQueue() {
  if (playQueue.length > 0) {
    next = playQueue.shift();
    play(next);
  }
}

function shuffle(array) {
  var counter = array.length;
  var temp;
  var index;

  // While there are elements in the array
  while (counter > 0) {
    index = Math.floor(Math.random() * counter);

    counter--;

    temp = array[counter];
    array[counter] = array[index];
    array[index] = temp;
  }

  return array;
}

function error(argument) {
  console.log(argument.stack);
}

// Email and password over command line
client.login(process.argv[2], process.argv[3]).catch((e) => console.log(e));
<|endoftext|>


<commit_before>var queryStr = require('querystring');
var url = require('url');

var dispatcher = {};

dispatcher.dispatch = function(req, resp) {
    var uri = url.parse(req.url).pathname;
    var query = queryStr.parse(url.parse(req.url).query);

    console.log('uri:' + uri + '?query:');
    console.log(query);
    resp.end('uri:' + uri + '?query:' + query);
}

module.exports = dispatcher;<commit_msg>解决中文参数回传乱码的问题<commit_after>var queryStr = require('querystring');
var url = require('url');

var dispatcher = {};

dispatcher.dispatch = function(req, resp) {
    var uri = url.parse(req.url).pathname;
    var query = queryStr.parse(url.parse(req.url).query);

    console.log('uri:' + uri + '?query:');
    console.log(query);
    resp.writeHead(200, {'Content-Type': 'text/plain; charset=utf-8'});
    resp.write('uri:' + uri + '?query:' + JSON.stringify(query));
    resp.end();
}

module.exports = dispatcher;<|endoftext|>


<commit_before>'use strict';
/**
 * S expression wrapper. Create S expression object represented by pair.
 */

var Util = require('./util'),
    parse = require('./parse');

/**
 * defined as a pair of two elements `fst` and `snd`
 */
function S(fst, snd) {
  this.fst = fst;
  this.snd = snd;
}

var symbolChars = ['!','#','$','%','&','|','*','+','-','/',':','<','=','>','?','@','^','_','~'],
    symbolPat = symbolChars.join(''),
    letterPat = 'a-zA-Z',
    digitPat = '0-9',
    basePat = '^[%LETTER%SYMBOL][%LETTER%DIGIT%SYMBOL]*$',
    atomRegexStr = Util.replaceAll(
      Util.replaceAll(
	Util.replaceAll(basePat,
			'%LETTER', letterPat),
	'%SYMBOL', symbolPat),
      '%DIGIT', digitPat),
    atomRegex = new RegExp(atomRegexStr);

function validateAtom(str) {
  return !!atomRegex.exec(str);
}

function createList(tokens) {
  var i, len, message, head, s, prevS;

  len = tokens.length;
  if (len === 0) return null;

  for(i=0; i<len; i+=1) {
    // handle dotted-list
    if (tokens[i] === '.') {
      if (i !== len-2 || i==0) {
	// (a . b c) or (. a)
	message = 'Ill-formed dotted list: (%s)'.replace('%s', tokens.join(' '));
	return Util.error(message);
      }
      prevS.snd = create(tokens[i+1]);
      break;
    }

    s = new S(create(tokens[i]), null);
    if (i===0) {
      head = s;
    } else {
      prevS.snd = s;
    }
    prevS = s;
  }
  return head;
}

function create(token) {
  var i, len, rest, message, head, s;
  /************** list *************/
  if (Array.isArray(token)) {
    return createList(token);
  }
  /************** atom *************/
  var num = parseFloat(token);
  // number
  if (!isNaN(num)) return num;
  // boolean: true
  if (token === '#t') return true;
  // boolean: false
  if (token === '#f') return false;
  // string ("xxxx")
  if (S.isString(token)) return token;
  // atom
  if (validateAtom(token)) return token;

  message = 'Invalid token: %s'.replace('%s', token);
  return Util.error(message);
}

S.create = function(source) {
  return create(parse(source));
};

S.isNull = function(s) {
  return s === null;
};

S.isPair = function(s) {
  return s instanceof S;
};

S.isString = function(s) {
  return s.match('^"(.*)"$');
};

// TODO: generate programably
S.car = function(s) {
  return s.fst;
};
S.cdr = function(s) {
  return s.snd;
};
S.caar = function(s) {
  return S.car(S.car(s));
};
S.cadr = function(s) {
  return S.car(S.cdr(s));
};
S.cdar = function(s) {
  return S.cdr(S.car(s));
};
S.cddr = function(s) {
  return S.cdr(S.cdr(s));
};
S.caadr = function(s) {
  return S.car(S.car(S.cdr(s)));
};
S.caddr = function(s) {
  return S.car(S.cdr(S.cdr(s)));
};
S.cdddr = function(s) {
  return S.cdr(S.cdr(S.cdr(s)));
};
S.cadadr = function(s) {
  return S.car(S.cdr(S.car(S.cdr(s))));
};
S.cddadr = function(s) {
  return S.cdr(S.cdr(S.car(S.cdr(s))));
};
S.cadddr = function(s) {
  return S.car(S.cdr(S.cdr(S.cdr(s))));
};
S.cddddr = function(s) {
  return S.cdr(S.cdr(S.cdr(S.cdr(s))));
};
S.caaddr = function(s) {
  return S.car(S.car(S.cdr(S.cdr(s))));
};
S.cdddadr = function(s) {
  return S.cdr(S.cdr(S.cdr(S.car(S.cdr(s)))));
};

module.exports = S;
<commit_msg>clean up by recursive call<commit_after>'use strict';
/**
 * S expression wrapper. Create S expression object represented by pair.
 */

var Util = require('./util'),
    parse = require('./parse');

/**
 * defined as a pair of two elements `fst` and `snd`
 */
function S(fst, snd) {
  this.fst = fst;
  this.snd = snd;
}

var symbolChars = ['!','#','$','%','&','|','*','+','-','/',':','<','=','>','?','@','^','_','~'],
    symbolPat = symbolChars.join(''),
    letterPat = 'a-zA-Z',
    digitPat = '0-9',
    basePat = '^[%LETTER%SYMBOL][%LETTER%DIGIT%SYMBOL]*$',
    atomRegexStr = Util.replaceAll(
      Util.replaceAll(
	Util.replaceAll(basePat,
			'%LETTER', letterPat),
	'%SYMBOL', symbolPat),
      '%DIGIT', digitPat),
    atomRegex = new RegExp(atomRegexStr);

function validateAtom(str) {
  return !!atomRegex.exec(str);
}

function create(token, listIdx) {
  var head, rest, message;
  /************** list *************/
  if (Array.isArray(token)) {

    if (token.length === 0) return null;
    head = token[0];

    // handle dotted-list
    if (head === '.') {
      if (token.length !== 2 || listIdx===undefined) {
	// (a . b c) or (. a)
	message = 'Ill-formed dotted list: %s'.replace('%s', token.join(' ')+ ')');
	return Util.error(message);
      }
      return create(token[1]);
    }
    rest = token.slice(1);
    listIdx = listIdx || 0;
    return new S(create(head), create(rest, listIdx+1));
  }
  /************** atom *************/
  var num = parseFloat(token);
  // number
  if (!isNaN(num)) return num;
  // boolean: true
  if (token === '#t') return true;
  // boolean: false
  if (token === '#f') return false;
  // string ("xxxx")
  if (S.isString(token)) return token;
  // atom
  if (validateAtom(token)) return token;

  message = 'Invalid token: %s'.replace('%s', token);
  return Util.error(message);
}

S.create = function(source) {
  return create(parse(source));
};

S.isNull = function(s) {
  return s === null;
};

S.isPair = function(s) {
  return s instanceof S;
};

S.isString = function(s) {
  return s.match('^"(.*)"$');
};

// TODO: generate programably
S.car = function(s) {
  return s.fst;
};
S.cdr = function(s) {
  return s.snd;
};
S.caar = function(s) {
  return S.car(S.car(s));
};
S.cadr = function(s) {
  return S.car(S.cdr(s));
};
S.cdar = function(s) {
  return S.cdr(S.car(s));
};
S.cddr = function(s) {
  return S.cdr(S.cdr(s));
};
S.caadr = function(s) {
  return S.car(S.car(S.cdr(s)));
};
S.caddr = function(s) {
  return S.car(S.cdr(S.cdr(s)));
};
S.cdddr = function(s) {
  return S.cdr(S.cdr(S.cdr(s)));
};
S.cadadr = function(s) {
  return S.car(S.cdr(S.car(S.cdr(s))));
};
S.cddadr = function(s) {
  return S.cdr(S.cdr(S.car(S.cdr(s))));
};
S.cadddr = function(s) {
  return S.car(S.cdr(S.cdr(S.cdr(s))));
};
S.cddddr = function(s) {
  return S.cdr(S.cdr(S.cdr(S.cdr(s))));
};
S.caaddr = function(s) {
  return S.car(S.car(S.cdr(S.cdr(s))));
};
S.cdddadr = function(s) {
  return S.cdr(S.cdr(S.cdr(S.car(S.cdr(s)))));
};

module.exports = S;
<|endoftext|>


<commit_before>
angular.module("mm.foundation.topbar", [])
    .factory('closest', [function(){
        return function(el, selector) {
            var matchesSelector = function (node, selector) {
                var nodes = (node.parentNode || node.document).querySelectorAll(selector);
                var i = -1;
                while (nodes[++i] && nodes[i] != node){}
                return !!nodes[i];
            };

            var element = el[0];
            while (element) {
                if (matchesSelector(element, selector)) {
                    return angular.element(element);
                } else {
                    element = element.parentElement;
                }
            }
            return false;
        };
    }])
    .directive('topBar', ['$timeout','$compile', '$window', '$document',
        function ($timeout, $compile, $window, $document) {

        var win = angular.element($window);
        var head = angular.element($document[0].querySelector('head'));

        head.append('<meta class="foundation-mq-topbar" />');
        head.append('<meta class="foundation-mq-small" />');
        head.append('<meta class="foundation-mq-medium" />');
        head.append('<meta class="foundation-mq-large" />');

        // MatchMedia for IE < 9
        var matchMedia = $window.matchMedia || (function( doc, undefined ) {
            var bool,
                docElem = doc.documentElement,
                refNode = docElem.firstElementChild || docElem.firstChild,
                // fakeBody required for <FF4 when executed in
                fakeBody = doc.createElement( "body" ),
                div = doc.createElement( "div" );

            div.id = "mq-test-1";
            div.style.cssText = "position:absolute;top:-100em";
            fakeBody.style.background = "none";
            fakeBody.appendChild(div);

            return function (q) {
                div.innerHTML = "";

                docElem.insertBefore( fakeBody, refNode );
                bool = div.offsetWidth === 42;
                docElem.removeChild( fakeBody );

                return {
                    matches: bool,
                    media: q
                };

            };

        }( $document[0] ));

        return {
            scope: {
                stickyClass : '@',
                backText: '@',
                stickyOn : '=',
                customBackText: '=',
                isHover: '=',
                mobileShowParentLink: '=',
                scrolltop : '=',
            },
            restrict: 'EA',
            replace: true,
            templateUrl: 'template/topbar/top-bar.html',
            transclude: true,
            link: function ($scope, element, attrs) {
                var topbar = $scope.topbar = element;
                var topbarContainer = topbar.parent();
                var regex = /^[\/\\'"]+|(;\s?})+|[\/\\'"]+$/g;
                var mediaQueries = $scope.mediaQueries = {
                    topbar: getComputedStyle(head[0].querySelector('meta.foundation-mq-topbar')).fontFamily.replace(regex, ''),
                    small : getComputedStyle(head[0].querySelector('meta.foundation-mq-small')).fontFamily.replace(regex, ''),
                    medium : getComputedStyle(head[0].querySelector('meta.foundation-mq-medium')).fontFamily.replace(regex, ''),
                    large : getComputedStyle(head[0].querySelector('meta.foundation-mq-large')).fontFamily.replace(regex, '')
                };
                var body = angular.element($document[0].querySelector('body'));

                var isSticky = $scope.isSticky = function () {
                    var sticky = topbarContainer.hasClass($scope.settings.stickyClass);
                    if (sticky && $scope.settings.stickyOn === 'all') {
                        return true;
                    } else if (sticky && small() && $scope.settings.stickyOn === 'small') {
                        return true;
                    } else if (sticky && medium() && $scope.settings.stickyOn === 'medium') {
                        return true;
                    } else if (sticky && large() && $scope.settings.stickyOn === 'large') {
                        return true;
                    }
                    return false;
                };

                var updateStickyPositioning = function(){
                    if (!$scope.stickyTopbar || !$scope.isSticky()) {
                        return;
                    }

                    var $class = angular.element($document[0].querySelector('.' + $scope.settings.stickyClass));

                    var distance = stickyoffset;
                    if (true) {
                        if ($window.scrollY > distance && !$class.hasClass('fixed')) {
                            $class.addClass('fixed');
                            body.css('padding-top', $scope.originalHeight + 'px');
                        } else if ($window.scrollY <= distance && $class.hasClass('fixed')) {
                            $class.removeClass('fixed');
                            body.css('padding-top', '');
                        }
                    }
                };

                $scope.toggle = function(on) {
                    if(!$scope.breakpoint()){
                        return false;
                    }

                    var expand = (on === undefined) ? !topbar.hasClass('expanded') : on;

                    if (expand){
                        topbar.addClass('expanded');
                    }
                    else {
                        topbar.removeClass('expanded');
                    }

                    if ($scope.settings.scrolltop) {
                        if (!expand && topbar.hasClass('fixed')) {
                            topbar.parent().addClass('fixed');
                            topbar.removeClass('fixed');
                            body.css('padding-top', $scope.originalHeight + 'px');
                        } else if (expand && topbar.parent().hasClass('fixed')) {
                            topbar.parent().removeClass('fixed');
                            topbar.addClass('fixed');
                            body.css('padding-top', '');
                            $window.scrollTo(0,0);
                        }
                    } else {
                        if(isSticky()) {
                            topbar.parent().addClass('fixed');
                        }

                        if(topbar.parent().hasClass('fixed')) {
                            if (!expand) {
                                topbar.removeClass('fixed');
                                topbar.parent().removeClass('expanded');
                                updateStickyPositioning();
                            } else {
                                topbar.addClass('fixed');
                                topbar.parent().addClass('expanded');
                                body.css('padding-top', $scope.originalHeight + 'px');
                            }
                        }
                    }
                };

                if(topbarContainer.hasClass('fixed') || isSticky() ) {
                    $scope.stickyTopbar = true;
                    $scope.height = topbarContainer[0].offsetHeight;
                    var stickyoffset = topbarContainer[0].getBoundingClientRect().top;
                } else {
                    $scope.height = topbar[0].offsetHeight;
                }

                $scope.originalHeight = $scope.height;

                $scope.$watch('height', function(h){
                    if(h){
                        topbar.css('height', h + 'px');
                    } else {
                        topbar.css('height', '');
                    }
                });

                var lastBreakpoint;
                $timeout(function(){
                    lastBreakpoint = $scope.breakpoint();
                });

                win.bind('resize', function(){
                    var currentBreakpoint = $scope.breakpoint();
                    if(lastBreakpoint === currentBreakpoint){
                        return;
                    }
                    lastBreakpoint = $scope.breakpoint();

                    topbar.removeClass('expanded');
                    topbar.parent().removeClass('expanded');
                    $scope.height = '';

                    var sections = angular.element(topbar[0].querySelectorAll('section'));
                    angular.forEach(sections, function(section){
                        angular.element(section.querySelectorAll('li.moved')).removeClass('moved');
                    });

                    $scope.$apply();
                 });

                // update sticky positioning
                win.bind("scroll", function() {
                    updateStickyPositioning();
                    $scope.$apply();
                });

                $scope.$on('$destroy', function(){
                    win.unbind("scroll");
                    win.unbind("resize");
                });

                if (topbarContainer.hasClass('fixed')) {
                    body.css('padding-top', $scope.originalHeight + 'px');
                }

            },
            controller: ['$window', '$scope', 'closest', function($window, $scope, closest) {

                $scope.settings = {};
                $scope.settings.stickyClass = $scope.stickyClass || 'sticky';
                $scope.settings.backText = $scope.backText || 'Back';
                $scope.settings.stickyOn = $scope.stickyOn || 'all';

                $scope.settings.customBackText = $scope.customBackText === undefined ? true : $scope.customBackText;
                $scope.settings.isHover = $scope.isHover === undefined ? true : $scope.isHover;
                $scope.settings.mobileShowParentLink = $scope.mobileShowParentLink === undefined ? true : $scope.mobileShowParentLink;
                $scope.settings.scrolltop = $scope.scrolltop === undefined ? true : $scope.scrolltop; // jump to top when sticky nav menu toggle is clicked

                this.settings = $scope.settings;

                $scope.index = 0;

                var outerHeight = function(el){
                    var height = el.offsetHeight;
                    var style = el.currentStyle || getComputedStyle(el);

                    height += parseInt(style.marginTop, 10) + parseInt(style.marginBottom, 10);
                    return height;
                };

                var breakpoint = $scope.breakpoint = this.breakpoint = function () {
                    return !matchMedia($scope.mediaQueries.topbar).matches;
                };

                var small = function () {
                    return $matchMedia($scope.mediaQueries.small).matches;
                };

                var medium = function () {
                    return $matchMedia($scope.mediaQueries.medium).matches;
                };

                var large = function () {
                    return $matchMedia($scope.mediaQueries.large).matches;
                };

                var sections = [];

                this.addSection = function(section){
                    sections.push(section);
                };

                this.removeSection = function(section){
                    var index = sections.indexOf(section);
                    if (index > -1) {
                        sections.splice(index, 1);
                    }
                };

                var dir = /rtl/i.test($document.find('html').attr('dir')) ? 'right' : 'left';

                $scope.$watch('index', function(index){
                    for(var i = 0; i < sections.length; i++){
                        sections[i].move(dir, index);
                    }
                });

                this.toggle = function(on){
                    $scope.toggle(on);
                    for(var i = 0; i < sections.length; i++){
                        sections[i].reset();
                    }
                    $scope.index = 0;
                    $scope.height = '';
                    $scope.$apply();
                };

                this.back = function(event) {
                    if($scope.index < 1 || !breakpoint()){
                        return;
                    }

                    var $link = angular.element(event.currentTarget);
                    var $movedLi = closest($link, 'li.moved');
                    var $previousLevelUl = $movedLi.parent();
                    $scope.index = $scope.index -1;

                    if($scope.index === 0){
                        $scope.height = '';
                    } else {
                        $scope.height = $scope.originalHeight + outerHeight($previousLevelUl[0]);
                    }

                    $timeout(function () {
                        $movedLi.removeClass('moved');
                    }, 300);
                };

                this.forward = function(event) {
                    if(!breakpoint()){
                        return false;
                    }

                    var $link = angular.element(event.currentTarget);
                    var $selectedLi = closest($link, 'li');
                    $selectedLi.addClass('moved');
                    $scope.height = $scope.originalHeight + outerHeight($link.parent()[0].querySelector('ul'));
                    $scope.index = $scope.index + 1;
                    $scope.$apply();
                };

            }]
        };
    }])
    .directive('toggleTopBar', ['closest', function (closest) {
        return {
            scope: {},
            require: '^topBar',
            restrict: 'A',
            replace: true,
            templateUrl: 'template/topbar/toggle-top-bar.html',
            transclude: true,
            link: function ($scope, element, attrs, topBar) {
                element.bind('click', function(event) {
                    var li = closest(angular.element(event.currentTarget), 'li');
                    if(!li.hasClass('back') && !li.hasClass('has-dropdown')) {
                        topBar.toggle();
                    }
                });

                $scope.$on('$destroy', function(){
                    element.unbind('click');
                });
            }
        };
    }])
    .directive('topBarSection', ['$compile', 'closest', function ($compile, closest) {
        return {
            scope: {},
            require: '^topBar',
            restrict: 'EA',
            replace: true,
            templateUrl: 'template/topbar/top-bar-section.html',
            transclude: true,
            link: function ($scope, element, attrs, topBar) {
                var section = element;

                $scope.reset = function(){
                    angular.element(section[0].querySelectorAll('li.moved')).removeClass('moved');
                };

                $scope.move = function(dir, index){
                    if(dir === 'left'){
                        section.css({"left": index * -100 + '%'});
                    }
                    else {
                        section.css({"right": index * -100 + '%'});
                    }
                };

                topBar.addSection($scope);

                $scope.$on("$destroy", function(){
                    topBar.removeSection($scope);
                });

                // Top level links close menu on click
                var links = section[0].querySelectorAll('li>a');
                angular.forEach(links, function(link){
                    var $link = angular.element(link);
                    var li = closest($link, 'li');
                    if(li.hasClass('has-dropdown') || li.hasClass('back') || li.hasClass('title')){
                        return;
                    }

                    $link.bind('click', function(){
                        topBar.toggle(false);
                    });

                    $scope.$on('$destroy', function(){
                        $link.bind('click');
                    });
                });

            }
        };
    }])
    .directive('hasDropdown', [function () {
        return {
            scope: {},
            require: ['^topBar'],
            restrict: 'A',
            templateUrl: 'template/topbar/has-dropdown.html',
            replace: true,
            transclude: true,
            link: function ($scope, element, attrs, ctrls) {
                var topBar = ctrls[0];
                var topBarSection = ctrls[1];

                $scope.triggerLink = element.children('a')[0];

                var $link = angular.element($scope.triggerLink);

                $link.bind('click', function(event){
                    topBar.forward(event);
                });
                $scope.$on('$destroy', function(){
                    $link.unbind('click');
                });

                element.bind('mouseenter', function() {
                    if(topBar.settings.isHover && !topBar.breakpoint()){
                        element.addClass('not-click');
                    }
                });
                element.bind('click', function(event) {
                    if(!topBar.settings.isHover && !topBar.breakpoint()){
                        element.toggleClass('not-click');
                    }
                });

                element.bind('mouseleave', function() {
                    element.removeClass('not-click');
                });

                $scope.$on('$destroy', function(){
                    element.unbind('click');
                    element.unbind('mouseenter');
                    element.unbind('mouseleave');
                });
            },
            controller: ['$window', '$scope', function($window, $scope) {
                this.triggerLink = $scope.triggerLink;
            }]
        };
    }])
    .directive('topBarDropdown', ['$compile', function ($compile) {
        return {
            scope: {},
            require: ['^topBar', '^hasDropdown'],
            restrict: 'A',
            replace: true,
            templateUrl: 'template/topbar/top-bar-dropdown.html',
            transclude: true,
            link: function ($scope, element, attrs, ctrls) {

                var topBar = ctrls[0];
                var hasDropdown = ctrls[1];

                var $link = angular.element(hasDropdown.triggerLink);
                var url = $link.attr('href');

                $scope.linkText = $link.text();

                $scope.back = function(event){
                    topBar.back(event);
                };

                // Add back link
                if (topBar.settings.customBackText) {
                    $scope.backText = topBar.settings.backText;
                } else {
                    $scope.backText = '« ' + $link.html();
                }

                var $titleLi;
                if (topBar.settings.mobileShowParentLink && url && url.length > 1) {
                    $titleLi = angular.element('
' +
                        '
{{backText}}

' +
                        '
{{linkText}}
');
                } else {
                    $titleLi = angular.element('
' +
                        '
{{backText}}

');
                }

                $compile($titleLi)($scope);
                element.prepend($titleLi);
            }
        };
    }]);
<commit_msg>fix(topbar): Removed always true if statement<commit_after>
angular.module("mm.foundation.topbar", [])
    .factory('closest', [function(){
        return function(el, selector) {
            var matchesSelector = function (node, selector) {
                var nodes = (node.parentNode || node.document).querySelectorAll(selector);
                var i = -1;
                while (nodes[++i] && nodes[i] != node){}
                return !!nodes[i];
            };

            var element = el[0];
            while (element) {
                if (matchesSelector(element, selector)) {
                    return angular.element(element);
                } else {
                    element = element.parentElement;
                }
            }
            return false;
        };
    }])
    .directive('topBar', ['$timeout','$compile', '$window', '$document',
        function ($timeout, $compile, $window, $document) {

        var win = angular.element($window);
        var head = angular.element($document[0].querySelector('head'));

        head.append('<meta class="foundation-mq-topbar" />');
        head.append('<meta class="foundation-mq-small" />');
        head.append('<meta class="foundation-mq-medium" />');
        head.append('<meta class="foundation-mq-large" />');

        // MatchMedia for IE < 9
        var matchMedia = $window.matchMedia || (function( doc, undefined ) {
            var bool,
                docElem = doc.documentElement,
                refNode = docElem.firstElementChild || docElem.firstChild,
                // fakeBody required for <FF4 when executed in
                fakeBody = doc.createElement( "body" ),
                div = doc.createElement( "div" );

            div.id = "mq-test-1";
            div.style.cssText = "position:absolute;top:-100em";
            fakeBody.style.background = "none";
            fakeBody.appendChild(div);

            return function (q) {
                div.innerHTML = "";

                docElem.insertBefore( fakeBody, refNode );
                bool = div.offsetWidth === 42;
                docElem.removeChild( fakeBody );

                return {
                    matches: bool,
                    media: q
                };

            };

        }( $document[0] ));

        return {
            scope: {
                stickyClass : '@',
                backText: '@',
                stickyOn : '=',
                customBackText: '=',
                isHover: '=',
                mobileShowParentLink: '=',
                scrolltop : '=',
            },
            restrict: 'EA',
            replace: true,
            templateUrl: 'template/topbar/top-bar.html',
            transclude: true,
            link: function ($scope, element, attrs) {
                var topbar = $scope.topbar = element;
                var topbarContainer = topbar.parent();
                var regex = /^[\/\\'"]+|(;\s?})+|[\/\\'"]+$/g;
                var mediaQueries = $scope.mediaQueries = {
                    topbar: getComputedStyle(head[0].querySelector('meta.foundation-mq-topbar')).fontFamily.replace(regex, ''),
                    small : getComputedStyle(head[0].querySelector('meta.foundation-mq-small')).fontFamily.replace(regex, ''),
                    medium : getComputedStyle(head[0].querySelector('meta.foundation-mq-medium')).fontFamily.replace(regex, ''),
                    large : getComputedStyle(head[0].querySelector('meta.foundation-mq-large')).fontFamily.replace(regex, '')
                };
                var body = angular.element($document[0].querySelector('body'));

                var isSticky = $scope.isSticky = function () {
                    var sticky = topbarContainer.hasClass($scope.settings.stickyClass);
                    if (sticky && $scope.settings.stickyOn === 'all') {
                        return true;
                    } else if (sticky && small() && $scope.settings.stickyOn === 'small') {
                        return true;
                    } else if (sticky && medium() && $scope.settings.stickyOn === 'medium') {
                        return true;
                    } else if (sticky && large() && $scope.settings.stickyOn === 'large') {
                        return true;
                    }
                    return false;
                };

                var updateStickyPositioning = function(){
                    if (!$scope.stickyTopbar || !$scope.isSticky()) {
                        return;
                    }

                    var $class = angular.element($document[0].querySelector('.' + $scope.settings.stickyClass));

                    var distance = stickyoffset;

                    if ($window.scrollY > distance && !$class.hasClass('fixed')) {
                        $class.addClass('fixed');
                        body.css('padding-top', $scope.originalHeight + 'px');
                    } else if ($window.scrollY <= distance && $class.hasClass('fixed')) {
                        $class.removeClass('fixed');
                        body.css('padding-top', '');
                    }

                };

                $scope.toggle = function(on) {
                    if(!$scope.breakpoint()){
                        return false;
                    }

                    var expand = (on === undefined) ? !topbar.hasClass('expanded') : on;

                    if (expand){
                        topbar.addClass('expanded');
                    }
                    else {
                        topbar.removeClass('expanded');
                    }

                    if ($scope.settings.scrolltop) {
                        if (!expand && topbar.hasClass('fixed')) {
                            topbar.parent().addClass('fixed');
                            topbar.removeClass('fixed');
                            body.css('padding-top', $scope.originalHeight + 'px');
                        } else if (expand && topbar.parent().hasClass('fixed')) {
                            topbar.parent().removeClass('fixed');
                            topbar.addClass('fixed');
                            body.css('padding-top', '');
                            $window.scrollTo(0,0);
                        }
                    } else {
                        if(isSticky()) {
                            topbar.parent().addClass('fixed');
                        }

                        if(topbar.parent().hasClass('fixed')) {
                            if (!expand) {
                                topbar.removeClass('fixed');
                                topbar.parent().removeClass('expanded');
                                updateStickyPositioning();
                            } else {
                                topbar.addClass('fixed');
                                topbar.parent().addClass('expanded');
                                body.css('padding-top', $scope.originalHeight + 'px');
                            }
                        }
                    }
                };

                if(topbarContainer.hasClass('fixed') || isSticky() ) {
                    $scope.stickyTopbar = true;
                    $scope.height = topbarContainer[0].offsetHeight;
                    var stickyoffset = topbarContainer[0].getBoundingClientRect().top;
                } else {
                    $scope.height = topbar[0].offsetHeight;
                }

                $scope.originalHeight = $scope.height;

                $scope.$watch('height', function(h){
                    if(h){
                        topbar.css('height', h + 'px');
                    } else {
                        topbar.css('height', '');
                    }
                });

                var lastBreakpoint;
                $timeout(function(){
                    lastBreakpoint = $scope.breakpoint();
                });

                win.bind('resize', function(){
                    var currentBreakpoint = $scope.breakpoint();
                    if(lastBreakpoint === currentBreakpoint){
                        return;
                    }
                    lastBreakpoint = $scope.breakpoint();

                    topbar.removeClass('expanded');
                    topbar.parent().removeClass('expanded');
                    $scope.height = '';

                    var sections = angular.element(topbar[0].querySelectorAll('section'));
                    angular.forEach(sections, function(section){
                        angular.element(section.querySelectorAll('li.moved')).removeClass('moved');
                    });

                    $scope.$apply();
                 });

                // update sticky positioning
                win.bind("scroll", function() {
                    updateStickyPositioning();
                    $scope.$apply();
                });

                $scope.$on('$destroy', function(){
                    win.unbind("scroll");
                    win.unbind("resize");
                });

                if (topbarContainer.hasClass('fixed')) {
                    body.css('padding-top', $scope.originalHeight + 'px');
                }

            },
            controller: ['$window', '$scope', 'closest', function($window, $scope, closest) {

                $scope.settings = {};
                $scope.settings.stickyClass = $scope.stickyClass || 'sticky';
                $scope.settings.backText = $scope.backText || 'Back';
                $scope.settings.stickyOn = $scope.stickyOn || 'all';

                $scope.settings.customBackText = $scope.customBackText === undefined ? true : $scope.customBackText;
                $scope.settings.isHover = $scope.isHover === undefined ? true : $scope.isHover;
                $scope.settings.mobileShowParentLink = $scope.mobileShowParentLink === undefined ? true : $scope.mobileShowParentLink;
                $scope.settings.scrolltop = $scope.scrolltop === undefined ? true : $scope.scrolltop; // jump to top when sticky nav menu toggle is clicked

                this.settings = $scope.settings;

                $scope.index = 0;

                var outerHeight = function(el){
                    var height = el.offsetHeight;
                    var style = el.currentStyle || getComputedStyle(el);

                    height += parseInt(style.marginTop, 10) + parseInt(style.marginBottom, 10);
                    return height;
                };

                var breakpoint = $scope.breakpoint = this.breakpoint = function () {
                    return !matchMedia($scope.mediaQueries.topbar).matches;
                };

                var small = function () {
                    return $matchMedia($scope.mediaQueries.small).matches;
                };

                var medium = function () {
                    return $matchMedia($scope.mediaQueries.medium).matches;
                };

                var large = function () {
                    return $matchMedia($scope.mediaQueries.large).matches;
                };

                var sections = [];

                this.addSection = function(section){
                    sections.push(section);
                };

                this.removeSection = function(section){
                    var index = sections.indexOf(section);
                    if (index > -1) {
                        sections.splice(index, 1);
                    }
                };

                var dir = /rtl/i.test($document.find('html').attr('dir')) ? 'right' : 'left';

                $scope.$watch('index', function(index){
                    for(var i = 0; i < sections.length; i++){
                        sections[i].move(dir, index);
                    }
                });

                this.toggle = function(on){
                    $scope.toggle(on);
                    for(var i = 0; i < sections.length; i++){
                        sections[i].reset();
                    }
                    $scope.index = 0;
                    $scope.height = '';
                    $scope.$apply();
                };

                this.back = function(event) {
                    if($scope.index < 1 || !breakpoint()){
                        return;
                    }

                    var $link = angular.element(event.currentTarget);
                    var $movedLi = closest($link, 'li.moved');
                    var $previousLevelUl = $movedLi.parent();
                    $scope.index = $scope.index -1;

                    if($scope.index === 0){
                        $scope.height = '';
                    } else {
                        $scope.height = $scope.originalHeight + outerHeight($previousLevelUl[0]);
                    }

                    $timeout(function () {
                        $movedLi.removeClass('moved');
                    }, 300);
                };

                this.forward = function(event) {
                    if(!breakpoint()){
                        return false;
                    }

                    var $link = angular.element(event.currentTarget);
                    var $selectedLi = closest($link, 'li');
                    $selectedLi.addClass('moved');
                    $scope.height = $scope.originalHeight + outerHeight($link.parent()[0].querySelector('ul'));
                    $scope.index = $scope.index + 1;
                    $scope.$apply();
                };

            }]
        };
    }])
    .directive('toggleTopBar', ['closest', function (closest) {
        return {
            scope: {},
            require: '^topBar',
            restrict: 'A',
            replace: true,
            templateUrl: 'template/topbar/toggle-top-bar.html',
            transclude: true,
            link: function ($scope, element, attrs, topBar) {
                element.bind('click', function(event) {
                    var li = closest(angular.element(event.currentTarget), 'li');
                    if(!li.hasClass('back') && !li.hasClass('has-dropdown')) {
                        topBar.toggle();
                    }
                });

                $scope.$on('$destroy', function(){
                    element.unbind('click');
                });
            }
        };
    }])
    .directive('topBarSection', ['$compile', 'closest', function ($compile, closest) {
        return {
            scope: {},
            require: '^topBar',
            restrict: 'EA',
            replace: true,
            templateUrl: 'template/topbar/top-bar-section.html',
            transclude: true,
            link: function ($scope, element, attrs, topBar) {
                var section = element;

                $scope.reset = function(){
                    angular.element(section[0].querySelectorAll('li.moved')).removeClass('moved');
                };

                $scope.move = function(dir, index){
                    if(dir === 'left'){
                        section.css({"left": index * -100 + '%'});
                    }
                    else {
                        section.css({"right": index * -100 + '%'});
                    }
                };

                topBar.addSection($scope);

                $scope.$on("$destroy", function(){
                    topBar.removeSection($scope);
                });

                // Top level links close menu on click
                var links = section[0].querySelectorAll('li>a');
                angular.forEach(links, function(link){
                    var $link = angular.element(link);
                    var li = closest($link, 'li');
                    if(li.hasClass('has-dropdown') || li.hasClass('back') || li.hasClass('title')){
                        return;
                    }

                    $link.bind('click', function(){
                        topBar.toggle(false);
                    });

                    $scope.$on('$destroy', function(){
                        $link.bind('click');
                    });
                });

            }
        };
    }])
    .directive('hasDropdown', [function () {
        return {
            scope: {},
            require: ['^topBar'],
            restrict: 'A',
            templateUrl: 'template/topbar/has-dropdown.html',
            replace: true,
            transclude: true,
            link: function ($scope, element, attrs, ctrls) {
                var topBar = ctrls[0];
                var topBarSection = ctrls[1];

                $scope.triggerLink = element.children('a')[0];

                var $link = angular.element($scope.triggerLink);

                $link.bind('click', function(event){
                    topBar.forward(event);
                });
                $scope.$on('$destroy', function(){
                    $link.unbind('click');
                });

                element.bind('mouseenter', function() {
                    if(topBar.settings.isHover && !topBar.breakpoint()){
                        element.addClass('not-click');
                    }
                });
                element.bind('click', function(event) {
                    if(!topBar.settings.isHover && !topBar.breakpoint()){
                        element.toggleClass('not-click');
                    }
                });

                element.bind('mouseleave', function() {
                    element.removeClass('not-click');
                });

                $scope.$on('$destroy', function(){
                    element.unbind('click');
                    element.unbind('mouseenter');
                    element.unbind('mouseleave');
                });
            },
            controller: ['$window', '$scope', function($window, $scope) {
                this.triggerLink = $scope.triggerLink;
            }]
        };
    }])
    .directive('topBarDropdown', ['$compile', function ($compile) {
        return {
            scope: {},
            require: ['^topBar', '^hasDropdown'],
            restrict: 'A',
            replace: true,
            templateUrl: 'template/topbar/top-bar-dropdown.html',
            transclude: true,
            link: function ($scope, element, attrs, ctrls) {

                var topBar = ctrls[0];
                var hasDropdown = ctrls[1];

                var $link = angular.element(hasDropdown.triggerLink);
                var url = $link.attr('href');

                $scope.linkText = $link.text();

                $scope.back = function(event){
                    topBar.back(event);
                };

                // Add back link
                if (topBar.settings.customBackText) {
                    $scope.backText = topBar.settings.backText;
                } else {
                    $scope.backText = '« ' + $link.html();
                }

                var $titleLi;
                if (topBar.settings.mobileShowParentLink && url && url.length > 1) {
                    $titleLi = angular.element('
' +
                        '
{{backText}}

' +
                        '
{{linkText}}
');
                } else {
                    $titleLi = angular.element('
' +
                        '
{{backText}}

');
                }

                $compile($titleLi)($scope);
                element.prepend($titleLi);
            }
        };
    }]);
<|endoftext|>