keep keep keep keep keep replace keep keep keep keep

require('../../stylus/components/_text-fields.styl')
require('../../stylus/components/_input-groups.styl')
require('../../stylus/components/_select.styl')

// Components
import VCard from '../VCard'
import VCheckbox from '../VCheckbox'
import {
  VList,
  VListTile,
  VListTileAction,
  VListTileContent,
  VListTileTitle
} from '../VList'
import VMenu from '../VMenu'
import VBtn from '../VBtn'

// Mixins
import Colorable from '../../mixins/colorable'
import Dependent from '../../mixins/dependent'
import Filterable from '../../mixins/filterable'
import Input from '../../mixins/input'
import Maskable from '../../mixins/maskable'

// Component level mixins
import Autocomplete from './mixins/select-autocomplete'
import Generators from './mixins/select-generators'

// Directives
import ClickOutside from '../../directives/click-outside'

// Helpers
import { getObjectValueByPath } from '../../util/helpers'

export default {
  name: 'v-select',

  inheritAttrs: false,

  components: {
    VCard,
    VCheckbox,
    VList,
    VListTile,
    VListTileAction,
    VListTileContent,
    VListTileTitle,
    VMenu,
    VBtn
  },

  directives: {
    ClickOutside
  },

  mixins: [Autocomplete, Colorable, Dependent, Filterable, Generators, Input, Maskable],

  data () {
    return {
      cachedItems: [],
      content: {},
      defaultColor: 'primary',
      inputValue: (this.multiple || this.tags) && !this.value ? [] : this.value,
      isBooted: false,
      lastItem: 20,
      lazySearch: null,
      isActive: false,
      menuIsActive: false,
      searchTimeout: null,
      selectedIndex: -1,
      selectedItems: [],
      shouldBreak: false
    }
  },

  props: {
    appendIcon: {
      type: String,
      default: 'arrow_drop_down'
    },
    appendIconCb: Function,
    auto: Boolean,
    autocomplete: Boolean,
    browserAutocomplete: {
      type: String,
      default: 'on'
    },
    cacheItems: Boolean,
    chips: Boolean,
    clearable: Boolean,
    combobox: Boolean,
    debounceSearch: {
      type: [Number, String],
      default: 200
    },
    editable: Boolean,
    hideSelected: Boolean,
    items: {
      type: Array,
      default: () => []
    },
    itemAvatar: {
      type: String,
      default: 'avatar'
    },
    itemDisabled: {
      type: String,
      default: 'disabled'
    },
    itemText: {
      type: String,
      default: 'text'
    },
    itemValue: {
      type: String,
      default: 'value'
    },
    maxHeight: {
      type: [Number, String],
      default: 300
    },
    minWidth: {
      type: [Boolean, Number, String],
      default: false
    },
    multiple: Boolean,
    multiLine: Boolean,
    overflow: Boolean,
    returnObject: Boolean,
    searchInput: {
      default: null
    },
    segmented: Boolean,
    singleLine: Boolean,
    solo: Boolean,
    tags: Boolean
  },

  computed: {
    classes () {
      const classes = {
        'input-group--text-field input-group--select': true,
        'input-group--auto': this.auto,
        'input-group--overflow': this.overflow,
        'input-group--segmented': this.segmented,
        'input-group--editable': this.editable,
        'input-group--autocomplete': this.isAutocomplete,
        'input-group--single-line': this.singleLine || this.isDropdown,
        'input-group--multi-line': this.multiLine,
        'input-group--chips': this.chips,
        'input-group--solo': this.solo,
        'input-group--multiple': this.multiple
      }

      if (this.hasError) {
        classes['error--text'] = true
      } else {
        return this.addTextColorClassChecks(classes)
      }

      return classes
    },
    computedContentClass () {
      const children = [
        'menu__content--select',
        this.auto ? 'menu__content--auto' : '',
        this.isDropdown ? 'menu__content--dropdown' : '',
        this.isAutocomplete ? 'menu__content--autocomplete' : ''
      ]

      return children.join(' ')
    },
    computedItems () {
      return this.filterDuplicates(this.cachedItems.concat(this.items))
    },
    /**
     * The range of the current input text
     *
     * @return {Number}
     */
    currentRange () {
      return this.getText(this.selectedItem || '').length
    },
    filteredItems () {
      // If we are not actively filtering
      // Show all available items
      const items = this.isNotFiltering
        ? this.computedItems
        : this.filterSearch()

      return !this.auto ? items.slice(0, this.lastItem) : items
    },
    hideSelections () {
      return this.isAutocomplete &&
        !this.isMultiple &&
        this.isFocused &&
        !this.chips
    },
    isNotFiltering () {
      return this.isAutocomplete &&
        this.isDirty &&
        this.searchValue === this.getText(this.selectedItem)
    },
    isHidingSelected () {
      return this.hideSelected && this.isAutocomplete && this.isMultiple
    },
    isAutocomplete () {
      return this.autocomplete || this.editable || this.tags || this.combobox
    },
    isDirty () {
      return this.selectedItems.length > 0
    },
    isDropdown () {
      return this.segmented || this.overflow || this.editable || this.solo
    },
    isMultiple () {
      return this.multiple || this.tags
    },
    isAnyValueAllowed () {
      return this.tags || this.combobox
    },
    searchValue: {
      get () { return this.lazySearch },
      set (val) {
        if (!this.isAutocomplete ||
          this.selectedIndex > -1
        ) return

        this.lazySearch = val

        clearTimeout(this.searchTimeout)

        this.searchTimeout = setTimeout(() => {
          this.$emit('update:searchInput', val)
        }, this.debounceSearch)
      }
    },
    selectedItem () {
      if (this.isMultiple) return null

      return this.selectedItems.find(i => (
        this.getValue(i) === this.getValue(this.inputValue)
      )) || null
    }
  },

  watch: {
    inputValue (val) {
      // Populate selected items
      this.genSelectedItems(val)

      this.$emit('input', val)

      // When inputValue is changed
      // and combobox is true set
      // menu property to false
      if (this.combobox) this.menuIsActive = false
    },
    isActive (val) {
      if (val) {
        this.searchValue = this.getText(this.selectedItem)
        return
      }

      if (this.tags && this.searchValue) {
        this.updateTags(this.searchValue)
      }

      this.searchValue = null
      this.menuIsActive = false
      this.isFocused = false
      this.selectedIndex = -1

      // this.lastItem += !val ? 20 : 0
    },
    isBooted () {
      this.$nextTick(() => {
        if (this.content && this.content.addEventListener) {
          this.content.addEventListener('scroll', this.onScroll, false)
        }
      })
    },
    items (val) {
      if (this.cacheItems) {
        this.cachedItems = this.filterDuplicates(this.cachedItems.concat(val))
      }

      this.resetMenuIndex()

      this.searchValue && this.$nextTick(() => this.setMenuIndex(0))

      this.genSelectedItems()
    },
    menuIsActive (val) {
      if (!val) return

      this.isBooted = true
      this.isActive = true
    },
    isMultiple (val) {
      this.inputValue = val ? [] : null
    },
    searchInput (val) {
      this.searchValue = val
    },
    searchValue (val) {
      // Wrap input to next line if overflowing
      if (this.$refs.input.scrollWidth > this.$refs.input.clientWidth) {
        this.shouldBreak = true
        this.$nextTick(this.$refs.menu.updateDimensions)
      } else if (val === null) {
        this.shouldBreak = false
      }

      // Activate menu if inactive and searching
      if (this.isActive &&
        !this.menuIsActive &&
        val !== this.getValue(this.selectedItem)
      ) {
        this.menuIsActive = true
      }

      // Only reset list index
      // if typing in search
      val && this.resetMenuIndex()

      this.$nextTick(() => {
        val ? this.setMenuIndex(0) : this.resetMenuIndex()
      })
    },
    selectedItems () {
      clearTimeout(this.searchTimeout)

      if (this.isAutocomplete) {
        this.$nextTick(this.$refs.menu.updateDimensions)
      }
    },
    value (val) {
      this.inputValue = val
      this.validate()
    }
  },

  mounted () {
    // If instance is being destroyed
    // do not run mounted functions
    if (this._isDestroyed) return

    // Evaluate the selected items immediately
    // to avoid a unnecessary label transition
    this.genSelectedItems()

    this.content = this.$refs.menu.$refs.content
  },

  beforeDestroy () {
    if (this.isBooted) {
      if (this.content) {
        this.content.removeEventListener('scroll', this.onScroll, false)
      }
    }
  },

  methods: {
    blur () {
      this.$emit('blur')
      if (this.isAutocomplete && this.$refs.input) this.$refs.input.blur()
      this.$nextTick(() => (this.isActive = false))
    },
    changeSelectedIndex (keyCode) {
      // backspace, left, right, delete
      if (![8, 37, 39, 46].includes(keyCode)) return

      const indexes = this.selectedItems.length - 1

      if (keyCode === 37) { // Left arrow
        this.selectedIndex = this.selectedIndex === -1
          ? indexes
          : this.selectedIndex - 1
      } else if (keyCode === 39) { // Right arrow
        this.selectedIndex = this.selectedIndex >= indexes
          ? -1
          : this.selectedIndex + 1
      } else if (this.selectedIndex === -1) {
        this.selectedIndex = indexes
        return
      }

      // backspace/delete
      if ([8, 46].includes(keyCode)) {
        const newIndex = this.selectedIndex === indexes
          ? this.selectedIndex - 1
          : this.selectedItems[this.selectedIndex + 1]
            ? this.selectedIndex
            : -1

        this.selectItem(this.selectedItems[this.selectedIndex])
        this.selectedIndex = newIndex
      }
    },
    compareObjects (a, b) {
      const aProps = Object.keys(a)
      const bProps = Object.keys(b)

      if (aProps.length !== bProps.length) return false

      for (let i = 0, length = aProps.length; i < length; i++) {
        const propName = aProps[i]

        if (a[propName] !== b[propName]) return false
      }

      return true
    },
    filterDuplicates (arr) {
      const values = arr.map(this.getValue)
      return arr.filter((el, i) => i === values.indexOf(values[i]))
    },
    focus () {
      this.isActive = true
      this.isFocused = true

      if (this.$refs.input && this.isAutocomplete) {
        this.$refs.input.focus()
        this.$nextTick(() => {
          this.$refs.input.select()
          this.shouldBreak && (
            this.$refs.input.scrollLeft = this.$refs.input.scrollWidth
          )
        })
      } else {
        this.$el.focus()
      }

      this.$emit('focus')
    },
    genDirectives () {
      return [{
        name: 'click-outside',
        value: e => {
          return (
            this.$refs.menu &&
            !this.$refs.menu.$refs.content.contains(e.target)
          )
        }
      }]
    },
    genListeners () {
      const listeners = Object.assign({}, this.$listeners)
      delete listeners.input

      return {
        ...listeners,
        click: () => {
          if (this.disabled || this.readonly) return
          this.showMenuItems()
          this.selectedIndex = -1
        },
        focus: () => {
          if (this.disabled || this.readonly) return

          !this.isFocused && this.focus()
        },
        keydown: this.onKeyDown // Located in mixins/select-autocomplete.js
      }
    },
    genLabel () {
      const singleLine = this.singleLine || this.isDropdown
      if (singleLine && this.isDirty ||
        singleLine && this.isFocused && this.searchValue
      ) return null

      const data = {}

      if (this.id) data.attrs = { for: this.id }

      return this.$createElement('label', data, this.$slots.label || this.label)
    },
    getPropertyFromItem (item, field) {
      if (item !== Object(item)) return item

      const value = getObjectValueByPath(item, field)

      return typeof value === 'undefined' ? item : value
    },
    genSelectedItems (val = this.inputValue) {
      // If we are using tags, don't filter results
      if (this.tags) return (this.selectedItems = val)

      // Combobox is the single version
      // of a taggable select element
      if (this.combobox) return (this.selectedItems = val ? [val] : [])

      let selectedItems = this.computedItems.filter(i => {
        if (!this.isMultiple) {
          return this.getValue(i) === this.getValue(val)
        } else {
          // Always return Boolean
          return val.find((j) => {
            const a = this.getValue(j)
            const b = this.getValue(i)

            if (a !== Object(a)) return a === b

            return this.compareObjects(a, b)
          }) !== undefined
        }
      })

      if (!selectedItems.length &&
        val != null &&
        this.tags
      ) {
        selectedItems = Array.isArray(val) ? val : [val]
      }

      this.selectedItems = selectedItems
    },
    getText (item) {
      return this.getPropertyFromItem(item, this.itemText)
    },
    getValue (item) {
      return this.getPropertyFromItem(item, this.itemValue)
    },
    clearableCallback () {
      const inputValue = this.isMultiple ? [] : null

      this.inputValue = inputValue
      this.searchValue = null
      this.$emit('change', inputValue)
      this.genSelectedItems()
      setTimeout(this.showMenu, 0)
    },
    showMenu () {
      this.showMenuItems()
      this.isAutocomplete && this.focus()
    },
    onScroll () {
      if (!this.isActive) {
        requestAnimationFrame(() => (this.content.scrollTop = 0))
      } else {
        if (this.lastItem >= this.computedItems.length) return

        const showMoreItems = (
          this.content.scrollHeight -
          (this.content.scrollTop +
          this.content.clientHeight)
        ) < 200

        if (showMoreItems) {
          this.lastItem += 20
        }
      }
    },
    selectItem (item) {
      if (!this.isMultiple) {
        this.inputValue = this.returnObject ? item : this.getValue(item)
        this.selectedItems = [item]
      } else {
        const selectedItems = []
        const inputValue = this.inputValue.slice()
        const i = this.inputValue.findIndex((i) => {
          const a = this.getValue(i)
          const b = this.getValue(item)

          if (a !== Object(a)) return a === b

          return this.compareObjects(a, b)
        })

        i !== -1 && inputValue.splice(i, 1) || inputValue.push(item)
        this.inputValue = inputValue.map((i) => {
          selectedItems.push(i)
          return this.returnObject ? i : this.getValue(i)
        })

        this.selectedItems = selectedItems
      }

      this.searchValue = !this.isMultiple || this.chips
        ? this.getText(this.selectedItem)
        : ''

      this.$emit('change', this.inputValue)

      // List tile will re-render, reset index to
      // maintain highlighting
      const savedIndex = this.getMenuIndex()
      this.resetMenuIndex()

      // After selecting an item
      // refocus the input and
      // reset the caret pos
      this.$nextTick(() => {
        this.focus()
        this.setCaretPosition(this.currentRange)

        requestAnimationFrame(() => {
          (!this.isAutocomplete || this.searchValue) ? this.setMenuIndex(savedIndex) : this.resetMenuIndex()
        })
      })
    },
    showMenuItems () {
      this.isActive = true
      this.menuIsActive = true
      this.chips && this.resetMenuIndex()
    }
  },

  render (h) {
    const data = {
      attrs: {
        tabindex: this.isAutocomplete || this.disabled ? -1 : this.tabindex,
        ...(this.isAutocomplete ? null : this.$attrs),
        role: this.isAutocomplete ? null : 'combobox'
      }
    }

    if (!this.isAutocomplete) {
      data.on = this.genListeners()
      data.directives = this.genDirectives()
    } else {
      data.on = {
        click: () => {
          if (this.disabled || this.readonly) return

          // Workaround for clicking select
          // when using autocomplete
          // and click doesn't target the input
          setTimeout(() => {
            if (this.menuIsActive) return

            this.focus()
            this.menuIsActive = true
          }, 100)
        }
      }
    }

    return this.genInputGroup([
      this.genSelectionsAndSearch(),
      this.genMenu()
    ], data, () => this.showMenu())
  }
}


/**
 * Delayable
 *
 * @mixin
 *
 * Changes the open or close
 * delay time for elements
 */
export default {
  data: () => ({
    openTimeout: null,
    closeTimeout: null
  }),

  props: {
    openDelay: {
      type: [Number, String],
      default: 0
    },
    closeDelay: {
      type: [Number, String],
      default: 200
    }
  },

  methods: {
    /**
     * Clear any pending delay
     * timers from executing
     *
     * @return {void}
     */
    clearDelay () {
      clearTimeout(this.openTimeout)
      clearTimeout(this.closeTimeout)
    },
    /**
     * Runs callback after
     * a specified delay
     *
     * @param  {String}   type
     * @param  {Function} cb
     *
     * @return {void}
     */
    runDelay (type, cb) {
      this.clearDelay()

      const delay = parseInt(this[`${type}Delay`], 10)

      this[`${type}Timeout`] = setTimeout(cb, delay)
    }
  }
}


@import '../bootstrap'
@import '../theme'

/* Themes */
list($material)
  background: $material.cards

  .list__tile:not(.list__tile--active)
    color: $material.text.primary

  .list__tile__sub-title
    color: $material.text.secondary

  .subheader
    color: $material.text.secondary

  .divider
    background-color: $material.dividers

  .list__tile__mask
    color: $material.text.disabled

  &--group
    .list__tile--active
      .list__tile__title
        color: $material.text.link

theme(list, "list")

.list
  list-style-type: none
  padding: $list-top-padding 0 $list-bottom-padding
  transition: height .4s $transition.fast-out-slow-in

  .input-group
    margin: 0

  > .list__tile ~ .list__tile
    margin-top: 0

  &__tile
    font-size: $list-tile-font-size
    font-weight: $list-tile-font-weight
    display: flex
    height: $list-item-single-height
    text-decoration: none
    align-items: center
    padding: 0 16px
    margin: 0
    transition: $primary-transition
    position: relative
    user-select: none

    &:after
      content: ''
      position: absolute
      left: 0
      top: 0
      height: 1px
      opacity: 0
      width: 100%
      transition: $primary-transition
      background-color: $material-twelve-percent-dark

    &--link
      cursor: pointer

    &--link:hover, &--highlighted
      background: $material-twelve-percent-dark

    &__content,
    &__action,
    &__avatar
      height: 100%

    &__title, &__sub-title
      white-space: nowrap
      overflow: hidden
      text-overflow: ellipsis
      width: 100%

    &__title
      transition: $primary-transition
      height: 24px
      line-height: 24px
      position: relative
      text-align: left

    &__sub-title
      font-size: $list-sub-title-font-size

    .avatar
      justify-content: flex-start
      min-width: 56px

    &__action
      display: flex
      justify-content: flex-start
      min-width: 56px
      align-items: center

      .input-group--selection-controls
        align-items: center
        padding: 0

      .input-group__details
        display: none

      .icon
        transition: $primary-transition

      .btn //fix button padding if used
        padding 0
        margin 0
        &--icon
          margin -8px

      &-text
        color: $list-action-text-font-color
        font-size: $list-action-text-font-size

      &--stack
        align-items: flex-end
        justify-content: space-between
        padding-top: $list-top-padding
        padding-bottom: $list-bottom-padding
        white-space: nowrap
        flex-direction: column

    &__content
      text-align: left
      flex: 1 1 auto
      overflow: hidden
      display: flex
      align-items: flex-start
      justify-content: center
      flex-direction: column

      ~ .avatar
        justify-content: flex-end

      ~ .list__tile__action:not(.list__tile__action--stack)
        justify-content: flex-end

    &--active
      color: $list-tile-active-color

      .list__tile__action:first-of-type
        .icon
          color: inherit

    &--disabled
      opacity: 0.4 !important
      pointer-events: none

    &--avatar
      height: $list-item-avatar-single-height

      .avatar
        img,
        .icon
          height: 40px
          width: 40px

  &--dense
    padding-top: $list-item-dense-top-padding
    padding-bottom: $list-item-dense-top-padding

    .subheader
      font-size: $subheader-dense-font-size
      height: $list-dense-subheader-height
    .list--group
      .subheader
        height $list-dense-subheader-group-height
    .list__tile
      font-size: $list-item-dense-font-size

      &--avatar
        height: $list-item-dense-avatar-single-height

      &:not(.list__tile--avatar)
        height: $list-item-dense-single-height

      .icon
        font-size: $list-item-dense-icon-font-size

    .list__tile__sub-title
      font-size: $list-item-dense-font-size

  &--two-line
    .list__tile
      height: $list-item-double-height

    &.list--dense
      .list__tile
        height: $list-item-dense-double-height

  &--three-line
    .list__tile
      height: $list-item-triple-height

    .list__tile__sub-title
      white-space: initial
      -webkit-line-clamp: 2
      -webkit-box-orient: vertical
      display: -webkit-box

    &.list--dense
      .list__tile
        height: $list-item-dense-triple-height

  &--group
    position: relative
    padding: 0

    &:after
      content: ''
      position: absolute
      left: 0
      bottom: 0
      height: 1px
      opacity: 0
      width: 100%
      background-color: $material-twelve-percent-dark

    .list__tile
      padding-left: $list-item-left-padding

    &__header
      > li:first-child
        cursor: pointer

        &:hover
          background: $material-twelve-percent-dark

      + .list--group
        &:after
          opacity: 1

      &--active
        .list__tile
          background: $material-twelve-percent-dark

          &:after
            opacity: 1

          .list__tile__title
            color: inherit

          .list__tile__action:last-of-type
            .icon
              transform: rotate(-180deg)

      &--no-action + .list--group
        .list__tile
          padding-left: $list-left-padding

  &--subheader
    padding-top: 0

  &.list--two-line,
  &.list--three-line
    .list__tile__avatar
      align-items: flex-start


this.searchValue = !this.isMultiple || this.chips

this.searchValue = !this.isMultiple && !this.combobox


@import '../settings/_elevations'

// GENERATE HELPER CLASSES
for z in (0..24)
  .elevation-{z}
    elevation(z, true)


vuetify

cross-env TARGET=development webpack --config build/config.js --progress --hide-modules --watch

cross-env NODE_ENV=development webpack-dev-server --config build/webpack.dev.config.js --open --hot

cross-env NODE_ENV=production node build/webpack.dev.config.js

rimraf dist && cross-env NODE_ENV=production webpack --config build/config.js --progress --hide-modules

rimraf es5 && cross-env NODE_ENV=es5 babel src --out-dir es5

node --inspect --debug-brk build/config.js

./node_modules/.bin/cross-env NODE_ENV=test node --inspect --inspect-brk ./node_modules/jest/bin/jest.js --no-cache --runInBand --verbose

cross-env NODE_ENV=test jest -i --coverage

Vue.js 2 Semantic Component Framework


import Ripple from '../directives/ripple'

export default {
  directives: {
    Ripple
  },

  props: {
    activeClass: String,
    append: Boolean,
    disabled: Boolean,
    exact: Boolean,
    exactActiveClass: String,
    href: [String, Object],
    to: [String, Object],
    nuxt: Boolean,
    replace: Boolean,
    ripple: Boolean,
    tag: String,
    target: String
  },

  methods: {
    click () {},
    generateRouteLink () {
      let exact = this.exact
      let tag

      const data = {
        attrs: { disabled: this.disabled },
        class: this.classes,
        props: {},
        directives: [{
          name: 'ripple',
          value: this.ripple || false
        }],
        on: {
          ...(this.$listeners || {}),
          click: this.click
        }
      }

      if (typeof this.exact === 'undefined') {
        exact = this.to === '/' ||
          (this.to === Object(this.to) && this.to.path === '/')
      }

      if (this.to) {
        // Add a special activeClass hook
        // for component level styles
        let activeClass = this.activeClass
        let exactActiveClass = this.exactActiveClass || activeClass

        if (this.proxyClass) {
          activeClass += ' ' + this.proxyClass
          exactActiveClass += ' ' + this.proxyClass
        }

        tag = this.nuxt ? 'nuxt-link' : 'router-link'
        data.props.to = this.to
        data.props.exact = exact
        data.props.activeClass = activeClass
        data.props.exactActiveClass = exactActiveClass
        data.props.append = this.append
        data.props.replace = this.replace
      } else {
        tag = this.href && 'a' || this.tag || 'a'

        if (tag === 'a') {
          data.attrs.href = this.href || 'javascript:;'
          if (this.target) data.attrs.target = this.target
        }
      }

      return { tag, data }
    }
  }
}


// List tile will re-render, reset index to


require('../../stylus/components/_tabs.styl')

import Resize from '../../directives/resize'

export default {
  name: 'v-tabs',

  directives: {
    Resize
  },

  provide () {
    return {
      registerContent: this.registerContent,
      unregisterContent: this.unregisterContent,
      registerTabItem: this.registerTabItem,
      unregisterTabItem: this.unregisterTabItem,
      next: this.next,
      prev: this.prev,
      slider: this.slider,
      tabClick: this.tabClick,
      isScrollable: () => this.scrollable,
      isMobile: () => this.isMobile
    }
  },

  data () {
    return {
      activeIndex: null,
      content: [],
      isBooted: false,
      isMobile: false,
      resizeTimeout: null,
      reverse: false,
      tabItems: [],
      tabsContainer: null,
      tabsSlider: null,
      target: null,
      targetEl: null,
      transitionTime: 300
    }
  },

  props: {
    centered: Boolean,
    fixed: Boolean,
    grow: Boolean,
    icons: Boolean,
    mobileBreakPoint: {
      type: [Number, String],
      default: 1280
    },
    value: String,
    scrollable: {
      type: Boolean,
      default: true
    }
  },

  computed: {
    classes () {
      return {
        'tabs': true,
        'tabs--centered': this.centered,
        'tabs--fixed': this.fixed,
        'tabs--grow': this.grow,
        'tabs--icons': this.icons,
        'tabs--mobile': this.isMobile,
        'tabs--scroll-bars': this.scrollable
      }
    }
  },

  watch: {
    value () {
      this.tabClick(this.value)
    },
    activeIndex () {
      this.updateTabs()
      this.$nextTick(() => (this.isBooted = true))
    },
    tabItems (newItems, oldItems) {
      // Tab item got removed
      if (oldItems.length > newItems.length) {
        if (!newItems.find(o => o.id === this.target)) {
          const i = oldItems.findIndex(o => o.id === this.target)

          this.$nextTick(() => {
            this.activeIndex = this.tabItems[i > 0 ? i - 1 : 0].id
            this.target = this.activeIndex
          })
        }
      }
      this.slider()
    },
    '$vuetify.application.left' () {
      this.onContainerResize()
    },
    '$vuetify.application.right' () {
      this.onContainerResize()
    }
  },

  mounted () {
    this.$vuetify.load(() => {
      // // This is a workaround to detect if link is active
      // // when being used as a router or nuxt link
      const i = this.tabItems.findIndex(({ el }) => {
        return el.firstChild.classList.contains('tabs__item--active')
      })

      const tab = this.value || (this.tabItems[i !== -1 ? i : 0] || {}).id

      tab && this.tabClick(tab) && this.onResize()
    })
  },

  methods: {
    registerContent (id, toggle) {
      this.content.push({ id, toggle })
    },
    registerTabItem (id, toggle, el) {
      this.tabItems.push({ id, toggle, el })
    },
    unregisterContent (id) {
      this.content = this.content.filter(o => o.id !== id)
    },
    unregisterTabItem (id) {
      this.tabItems = this.tabItems.filter(o => o.id !== id)
    },
    next (cycle) {
      let nextIndex = this.activeIndex + 1

      if (!this.content[nextIndex]) {
        if (!cycle) return
        nextIndex = 0
      }

      this.tabClick(this.tabItems[nextIndex].id)
    },
    prev (cycle) {
      let prevIndex = this.activeIndex - 1

      if (!this.content[prevIndex]) {
        if (!cycle) return
        prevIndex = this.content.length - 1
      }

      this.tabClick(this.tabItems[prevIndex].id)
    },
    onResize () {
      this.isMobile = window.innerWidth < this.mobileBreakPoint
      this.slider()
    },
    /**
     * When v-navigation-drawer changes the
     * width of the container, call resize
     * after the transition is complete
     *
     * @return {Void}
     */
    onContainerResize () {
      clearTimeout(this.resizeTimeout)
      this.resizeTimeout = setTimeout(this.onResize, this.transitionTime)
    },
    slider (el) {
      this.tabsSlider = this.tabsSlider ||
        !!this.$el && this.$el.querySelector('.tabs__slider')

      this.tabsContainer = this.tabsContainer ||
        !!this.$el && this.$el.querySelector('.tabs__container')

      if (!this.tabsSlider || !this.tabsContainer) return

      this.targetEl = el || this.targetEl

      if (!this.targetEl) return

      // Gives DOM time to paint when
      // processing slider for
      // dynamic tabs
      this.$nextTick(() => {
        // #684 Calculate width as %
        const width = (
          this.targetEl.scrollWidth /
          this.tabsContainer.clientWidth *
          100
        )

        this.tabsSlider.style.width = `${width}%`
        this.tabsSlider.style.left = `${this.targetEl.offsetLeft}px`
      })
    },
    tabClick (target) {
      const setActiveIndex = index => {
        if (this.activeIndex === index) {
          // #762 update tabs display
          // In case tabs count got changed but activeIndex didn't
          this.updateTabs()
        } else {
          this.activeIndex = index
        }
      }

      this.target = target

      this.$nextTick(() => {
        const nextIndex = this.content.findIndex(o => o.id === target)
        this.reverse = nextIndex < this.activeIndex
        setActiveIndex(nextIndex)

        this.$emit('input', this.target)
      })
    },
    updateTabs () {
      this.content.forEach(({ toggle }) => {
        toggle(this.target, this.reverse, this.isBooted)
      })

      this.tabItems.forEach(({ toggle }) => {
        toggle(this.target)
      })
    }
  },

  render (h) {
    return h('div', {
      'class': this.classes,
      directives: [{
        name: 'resize',
        value: this.onResize
      }]
    }, this.$slots.default)
  }
}


import {
  createSimpleFunctional
} from '../../util/helpers'

import VToolbar from './VToolbar'
import VToolbarSideIcon from './VToolbarSideIcon'

export { VToolbar, VToolbarSideIcon }
export const VToolbarTitle = createSimpleFunctional('toolbar__title')
export const VToolbarItems = createSimpleFunctional('toolbar__items')

/* istanbul ignore next */
VToolbar.install = function install (Vue) {
  Vue.component('v-toolbar', VToolbar)
  Vue.component('v-toolbar-items', VToolbarItems)
  Vue.component('v-toolbar-title', VToolbarTitle)
  Vue.component('v-toolbar-side-icon', VToolbarSideIcon)
}

export default VToolbar


/**
 * Menu generators
 *
 * @mixin
 *
 * Used for creating the DOM elements for VMenu
 */
export default {
  methods: {
    genActivator () {
      if (!this.$slots.activator) return null

      const options = {
        staticClass: 'menu__activator',
        'class': {
          'menu__activator--active': this.hasJustFocused || this.isActive
        },
        ref: 'activator',
        on: {}
      }

      if (this.openOnHover) {
        options.on['mouseenter'] = this.mouseEnterHandler
        options.on['mouseleave'] = this.mouseLeaveHandler
      } else if (this.openOnClick) {
        options.on['click'] = this.activatorClickHandler
      }

      return this.$createElement('div', options, this.$slots.activator)
    },

    genTransition () {
      if (!this.transition) return this.genContent()

      return this.$createElement('transition', {
        props: {
          name: this.transition
        }
      }, [this.genContent()])
    },

    genDirectives () {
      // Do not add click outside for hover menu
      const directives = !this.openOnHover ? [{
        name: 'click-outside',
        value: {
          callback: () => this.closeOnClick,
          include: () => [this.$el, ...this.getOpenDependentElements()]
        }
      }] : []

      directives.push({
        name: 'show',
        value: this.isContentActive
      })

      return directives
    },

    genContent () {
      const options = {
        'class': [
          (`menu__content ${this.contentClass}`).trim(),
          { 'menuable__content__active': this.isActive }
        ],
        style: this.styles,
        directives: this.genDirectives(),
        ref: 'content',
        on: {
          click: e => {
            e.stopPropagation()
            if (e.target.getAttribute('disabled')) return
            if (this.closeOnContentClick) this.isActive = false
          }
        }
      }

      !this.disabled && this.openOnHover && (options.on.mouseenter = this.mouseEnterHandler)
      this.openOnHover && (options.on.mouseleave = this.mouseLeaveHandler)

      return this.$createElement('div', options, this.showLazyContent(this.$slots.default))
    }
  }
}


(!this.isAutocomplete || this.searchValue) ? this.setMenuIndex(savedIndex) : this.resetMenuIndex()

keep keep keep keep keep replace keep keep keep keep keep

import { test } from '~util/testing'
import { mount } from 'avoriaz'
import VSelect from '~components/VSelect'
import VMenu from '~components/VMenu'

test('VSelect - tags', () => {
  const backspace = new Event('keydown')
  backspace.keyCode = 8

  it('should create new values when tagging', async () => {
    const wrapper = mount(VSelect, {
      attachToDocument: true,
      propsData: {
        tags: true,
        value: []
      }
    })

    const input = wrapper.find('input')[0]

    const change = jest.fn()
    wrapper.vm.$on('change', change)

    wrapper.vm.focus()
    await wrapper.vm.$nextTick()

    input.element.value = 'foo'
    input.trigger('input')
    await wrapper.vm.$nextTick()
    await wrapper.vm.$nextTick()

    input.trigger('keydown.enter')
    await wrapper.vm.$nextTick()

    expect(change).toHaveBeenCalledWith(['foo'])
    expect('Application is missing <v-app> component.').toHaveBeenTipped()
  })

  it('should change selectedIndex with keyboard', async () => {
    const wrapper = mount(VSelect, {
      attachToDocument: true,
      propsData: {
        tags: true,
        value: ['foo', 'bar']
      }
    })

    const input = wrapper.find('input')[0]

    wrapper.vm.focus()
    await wrapper.vm.$nextTick()

    for (const index of [1, 0, -1]) {
      input.trigger('keydown.left')
      await wrapper.vm.$nextTick()
      expect(wrapper.vm.selectedIndex).toBe(index)
    }

    expect('Application is missing <v-app> component.').toHaveBeenTipped()
  })

  it('should delete a tagged item when selected and backspace/delete is pressed', async () => {
    const wrapper = mount(VSelect, {
      attachToDocument: true,
      propsData: {
        tags: true,
        value: ['foo', 'bar']
      }
    })

    const input = wrapper.find('input')[0]

    const change = jest.fn()
    wrapper.vm.$on('change', change)

    wrapper.vm.focus()

    input.trigger('keydown.left')
    expect(wrapper.vm.selectedIndex).toBe(1)

    input.trigger('keydown.delete')
    expect(change).toHaveBeenCalledWith(['foo'])
    expect(wrapper.vm.selectedIndex).toBe(0)

    input.element.dispatchEvent(backspace) // Avoriaz doesn't wrap keydown.backspace
    expect(change).toHaveBeenCalledWith([])
    expect(wrapper.vm.selectedIndex).toBe(-1)

    expect('Application is missing <v-app> component.').toHaveBeenTipped()
  })

  it('should add a tag on tab using the first suggestion', async () => {
    const wrapper = mount(VSelect, {
      attachToDocument: true,
      propsData: {
        tags: true,
        value: [],
        items: ['bar']
      }
    })

    const input = wrapper.find('input')[0]

    const change = jest.fn()
    wrapper.vm.$on('change', change)

    wrapper.vm.focus()
    await wrapper.vm.$nextTick()

    input.element.value = 'b'
    input.trigger('input')
    await wrapper.vm.$nextTick()
    input.trigger('keydown.tab')
    await wrapper.vm.$nextTick()

    expect(change).toBeCalledWith(['bar'])
    expect('Application is missing <v-app> component.').toHaveBeenTipped()
  })

  it('should add a tag on tab using the current searchValue', async () => {
    const wrapper = mount(VSelect, {
      attachToDocument: true,
      propsData: {
        tags: true,
        value: [],
        items: ['bar']
      }
    })

    const input = wrapper.find('input')[0]

    const change = jest.fn()
    const blur = jest.fn()
    wrapper.vm.$on('change', change)
    wrapper.vm.$on('blur', blur)

    wrapper.vm.focus()
    await wrapper.vm.$nextTick()
    wrapper.setProps({ searchInput: 'ba' })
    await wrapper.vm.$nextTick()
    input.trigger('keydown.tab')
    await wrapper.vm.$nextTick()
    expect(change).toBeCalledWith(['bar'])

    wrapper.setProps({ searchInput: 'it' })
    await wrapper.vm.$nextTick()
    input.trigger('keydown.tab')
    await wrapper.vm.$nextTick()
    expect(change).toBeCalledWith(['bar', 'it'])

    expect('Application is missing <v-app> component.').toHaveBeenTipped()
  })

  it('should add a tag on enter using the current searchValue', async () => {
    const wrapper = mount(VSelect, {
      attachToDocument: true,
      propsData: {
        tags: true,
        value: [],
        items: ['bar']
      }
    })

    const input = wrapper.find('input')[0]

    const change = jest.fn()
    wrapper.vm.$on('change', change)

    wrapper.vm.focus()
    await wrapper.vm.$nextTick()

    input.element.value = 'ba'
    input.trigger('input')
    input.element.setSelectionRange(2, 2)
    await wrapper.vm.$nextTick()
    input.trigger('keydown.right')
    await wrapper.vm.$nextTick()
    input.trigger('keydown.enter')
    await wrapper.vm.$nextTick()

    expect(change).toBeCalledWith(['ba'])
    expect('Application is missing <v-app> component.').toHaveBeenTipped()
  })

  it('should add a tag on left arrow and select the previous tag', async () => {
    const wrapper = mount(VSelect, {
      attachToDocument: true,
      propsData: {
        tags: true,
        value: ['foo'],
        items: ['foo', 'bar']
      }
    })

    const input = wrapper.find('input')[0]

    const change = jest.fn()
    wrapper.vm.$on('change', change)

    wrapper.vm.focus()
    await wrapper.vm.$nextTick()

    input.element.value = 'b'
    input.trigger('input')
    input.trigger('keydown.left')
    await wrapper.vm.$nextTick()

    expect(change).toBeCalledWith(['foo', 'b'])
    expect(wrapper.vm.selectedIndex).toBe(0)
    expect('Application is missing <v-app> component.').toHaveBeenTipped()
  })

  it('should remove a duplicate tag and add it to the end', async () => {
    const wrapper = mount(VSelect, {
      attachToDocument: true,
      propsData: {
        tags: true,
        value: ['foo', 'bar']
      }
    })

    const input = wrapper.find('input')[0]

    const change = jest.fn()
    wrapper.vm.$on('change', change)

    wrapper.vm.focus()
    await wrapper.vm.$nextTick()

    input.element.value = 'foo'
    input.trigger('input')
    input.trigger('keydown.tab')
    await wrapper.vm.$nextTick()

    expect(change).toBeCalledWith(['bar', 'foo'])
    expect('Application is missing <v-app> component.').toHaveBeenTipped()
  })

  it('should add tag with valid search value on blur', async () => {
    const wrapper = mount(VSelect, {
      attachToDocument: true,
      propsData: {
        tags: true
      }
    })

    const input = wrapper.find('input')[0]

    const change = jest.fn()
    wrapper.vm.$on('change', change)

    wrapper.vm.focus()
    await wrapper.vm.$nextTick()

    input.element.value = 'bar'
    input.trigger('input')
    await wrapper.vm.$nextTick()

    wrapper.vm.blur()
    await wrapper.vm.$nextTick() // First tick processes blur, menu sets isActive false, adds tag
    await wrapper.vm.$nextTick() // Second tick processes change after tag added

    expect(change).toBeCalledWith(['bar'])
    expect('Application is missing <v-app> component.').toHaveBeenTipped()
  })
})


require('../../stylus/components/_grid.styl')

import Grid from './grid'

export default Grid('layout')


import { test } from '~util/testing'
import { VRadioGroup, VRadio } from '~components/VRadioGroup'

test('VRadio.vue', ({ mount }) => {
  it('should advise about v-radio-group being necessary', () => {
    mount(VRadio, {
      provide: {
        name: () => 'name',
        registerChild: () => {},
        unregisterChild: () => {},
        isMandatory: () => false,
      }
    })

    expect('immediate parent of v-radio-group').toHaveBeenTipped()
  })

  // TODO: Enable test when there's a way to test $parent.$vnode.tag
  it.skip('should not advise about v-radio-group being necessary in VRadioGroup', () => {
    const wrapper = mount(VRadioGroup, {
      slots: {
        default: [VRadio]
      }
    })
    // no expectation other than a lack of tip
  })

  it('should render role and aria-checked attributes on input group', () => {
    const wrapper = mount(VRadio, {
      data: {
        isActive: false
      },
      provide: {
        name: () => 'name',
        registerChild: () => {},
        unregisterChild: () => {},
        isMandatory: () => false
      }
    })

    let inputGroup = wrapper.find('.input-group')[0]
    expect(inputGroup.getAttribute('role')).toBe('radio')
    expect(inputGroup.getAttribute('aria-checked')).toBe('false')

    wrapper.setData({ 'isActive': true })
    inputGroup = wrapper.find('.input-group')[0]
    expect(inputGroup.getAttribute('aria-checked')).toBe('true')
    expect(wrapper.html()).toMatchSnapshot()

    expect('immediate parent of v-radio-group').toHaveBeenTipped()
  })

  it('should render aria-label attribute with label value on input group', () => {
    const wrapper = mount(VRadio, {
      propsData: {
        label: 'Test'
      },
      attrs: {},
      provide: {
        name: () => 'name',
        registerChild: () => {},
        unregisterChild: () => {},
        isMandatory: () => false
      }
    })

    const inputGroup = wrapper.find('.input-group')[0]
    expect(inputGroup.getAttribute('aria-label')).toBe('Test')
    expect(wrapper.html()).toMatchSnapshot()

    expect('immediate parent of v-radio-group').toHaveBeenTipped()
  })

  it('should not render aria-label attribute with no label value on input group', () => {
    const wrapper = mount(VRadio, {
      propsData: {
        label: null
      },
      provide: {
        name: () => 'name',
        registerChild: () => {},
        unregisterChild: () => {},
        isMandatory: () => false
      }
    })

    const inputGroup = wrapper.find('.input-group')[0]
    expect(inputGroup.element.getAttribute('aria-label')).toBeFalsy()
    expect(wrapper.html()).toMatchSnapshot()

    expect('immediate parent of v-radio-group').toHaveBeenTipped()
  })

  it('should render proper input name', () => {
    const wrapper = mount(VRadio, {
      provide: {
        name: () => 'name',
        registerChild: () => {},
        unregisterChild: () => {},
        isMandatory: () => false
      }
    })

    const input = wrapper.find('input')[0]
    expect(input.getAttribute('name')).toBe('name')
    expect(wrapper.html()).toMatchSnapshot()

    expect('immediate parent of v-radio-group').toHaveBeenTipped()
  })

  it('should register and unregister', () => {
    const register = jest.fn()
    const unregister = jest.fn()

    const wrapper = mount(VRadio, {
      provide: {
        name: () => 'name',
        registerChild: register,
        unregisterChild: unregister,
        isMandatory: () => false
      }
    })

    expect(register).toHaveBeenCalled()
    wrapper.destroy()
    expect(unregister).toHaveBeenCalled()

    expect('immediate parent of v-radio-group').toHaveBeenTipped()
  })

  it('should not render ripple when ripple prop is false', () => {
    const wrapper = mount(VRadio, {
      propsData: {
        ripple: false
      },
      provide: {
        name: () => 'name',
        registerChild: () => {},
        unregisterChild: () => {},
        isMandatory: () => false
      }
    })

    const ripple = wrapper.find('.input-group--selection-controls__ripple')

    expect(ripple.length).toBe(0)

    expect('immediate parent of v-radio-group').toHaveBeenTipped()
  })

  it('should render ripple with data attribute when ripple prop is true', () => {
    const wrapper = mount(VRadio, {
      propsData: {
        ripple: true
      },
      provide: {
        name: () => 'name',
        registerChild: () => {},
        unregisterChild: () => {},
        isMandatory: () => false
      }
    })

    const ripple = wrapper.find('.input-group--selection-controls__ripple')[0]

    expect(ripple.getAttribute('data-ripple')).toBe('true')

    expect('immediate parent of v-radio-group').toHaveBeenTipped()
  })
})


@import '../bootstrap'

.snack
  background-color: $snackbar-background-color
  position: fixed
  display: flex
  height: 0
  pointer-events: none
  visibility: visible
  z-index: 7

  &--absolute
    position: absolute

  &--top
    top: 0
    left: 50%
    transform: translate(-50%, 0)

  &--bottom
    bottom: 48px
    left: 50%
    transform: translate(-50%, 0)

  &--left
    left: 8px
    right: initial
    transform: none

    &.snack--top
      top: 8px

    &.snack--bottom
      bottom: 56px

  &--right
    left: initial
    right: 8px
    transform: none

    &.snack--top
      top: 8px

    &.snack--bottom
      top: initial
      bottom: 56px

  &__content
    background-color: inherit
    padding: 14px 24px
    border-radius: 2px
    pointer-events: auto
    max-width: 568px
    min-width: 288px
    height: 48px
    align-items: center
    color: #fff
    display: flex
    font-size: 14px
    justify-content: space-between
    transition: .4s $transition.swing
    position: relative !important
    elevation(6)

    .btn
      margin: 0 0 0 48px

.snack
  &--multi-line &__content
    height: 80px
    padding: 24px

  &--bottom, &--right
    &.snack--multi-line
      bottom: 80px

  &--vertical &__content
    height: 112px
    padding: 24px 24px 14px
    flex-direction: column
    align-items: initial

    .btn
      align-self: flex-end

  &--bottom, &--right
    &.snack--vertical
      bottom: 112px

@media $display-breakpoints.xs-only
  .snack
    width: 100%
    left: 0
    right: initial
    transform: none

    &--right, &--left
      &.snack--top
        top: 0

      &.snack--bottom
        bottom: 48px

    &__content
      border-radius: 0
      max-width: 100%
      width: 100%

      .btn
        margin: 0 0 0 24px


(!this.tags || this.filteredItems.length > 0) &&

(!this.combobox || this.filteredItems.length > 0),

keep keep keep replace replace keep keep keep

(!this.isAnyValueAllowed || this.filteredItems.length > 0),


import VSystemBar from './VSystemBar'

/* istanbul ignore next */
VSystemBar.install = function install (Vue) {
  Vue.component(VSystemBar.name, VSystemBar)
}

export default VSystemBar


require('../../stylus/components/_steppers.styl')

import Themeable from '../../mixins/themeable'

export default {
  name: 'v-stepper',

  mixins: [Themeable],

  provide () {
    return {
      stepClick: this.stepClick
    }
  },

  data () {
    return {
      inputValue: null,
      isBooted: false,
      steps: [],
      content: [],
      isReverse: false
    }
  },

  props: {
    nonLinear: Boolean,
    altLabels: Boolean,
    vertical: Boolean,
    value: [Number, String]
  },

  computed: {
    classes () {
      return {
        'stepper': true,
        'stepper--is-booted': this.isBooted,
        'stepper--vertical': this.vertical,
        'stepper--alt-labels': this.altLabels,
        'stepper--non-linear': this.nonLinear,
        'theme--dark': this.dark,
        'theme--light': this.light
      }
    }
  },

  watch: {
    inputValue (val, prev) {
      this.isReverse = Number(val) < Number(prev)
      this.steps.forEach(i => i.toggle(this.inputValue))
      this.content.forEach(i => i.toggle(this.inputValue, this.isReverse))

      this.$emit('input', this.inputValue)
      prev && (this.isBooted = true)
    },
    value () {
      this.getSteps()
      this.$nextTick(() => (this.inputValue = this.value))
    }
  },

  mounted () {
    this.$vuetify.load(this.init)
  },

  methods: {
    init () {
      this.getSteps()

      this.inputValue = this.value || this.steps[0].step || 1
    },
    getSteps () {
      this.steps = []
      this.content = []
      this.$children.forEach(i => {
        if (i.$options._componentTag === 'v-stepper-step') {
          this.steps.push(i)
        } else if (i.$options._componentTag === 'v-stepper-content') {
          i.isVertical = this.vertical
          this.content.push(i)
        }
      })
    },
    stepClick (step) {
      this.getSteps()
      this.$nextTick(() => (this.inputValue = step))
    }
  },

  render (h) {
    return h('div', {
      'class': this.classes
    }, this.$slots.default)
  }
}


import { addOnceEventListener } from './helpers'

/**
 * @mixin
 */
export default {
  mounted () {
    addOnceEventListener(window, 'touchstart', () => {
      this.$vuetify.touchSupport = true
    })
  }
}


@import '../bootstrap'
@import '../theme'

/* Theme */
tabs($material)
  .tabs__item
    color: $material.tabs.inactive

    &.tabs__item--active
      color: $material.tabs.active

    &.tabs__item--disabled
      color: $material.buttons.disabled

  .tabs__bar
    background-color: $material.cards

    .icon--left,
    .icon--right
      color: $material.icons.inactive

theme(tabs, "tabs__bar")

.tabs
  overflow: hidden
  position: relative
  width: 100%

  &--grow .tabs__bar
    .tabs__li
      flex: 1

  &--fixed > .tabs__bar
    .tabs__li
      min-width: 160px
      max-width: 264px
      flex: 0 1 160px

      @media $display-breakpoints.xs-only
        min-width: 72px

  &--centered .tabs__bar
    .tabs__container
      justify-content: center

  &--icons
    .tabs__bar
      height: 72px

    .tabs__item
      flex-direction: column

  &.tabs--mobile .tabs__bar
    .icon--left, .icon--right
      display: none

    .tabs__wrapper
      &--scrollable.tabs__wrapper--overflow
        overflow: hidden !important

  &:not(.tabs--grow):not(.tabs--mobile)
    .tabs__item
      padding: 0 24px

  &:not(.tabs--centered):not(.tabs--grow):not(.tabs--mobile)
    .tabs__wrapper
      &--scrollable
        margin: 0 60px
        overflow: hidden !important

  &__bar
    width: 100%
    position: relative
    height: 48px

    @media $display-breakpoints.sm-and-down
      padding-left: 0

    .icon--left, .icon--right
      position: absolute
      top: 0
      width: 32px
      align-items: center
      height: 100%
      cursor: pointer
      user-select: none

    .icon--left
      left: 28px

    .icon--right
      right: 28px

  &__container
    display: flex
    height: 100%
    width: 100%
    position: absolute
    padding: 0
    top: 0
    align-items: center
    list-style: none
    will-change: transform
    transition: transform 0.6s cubic-bezier(0.86, 0, 0.07, 1)

    > li:not(.tabs__slider)
      height: 100%

    &-left
      position: absolute
      left: 0
      top: 0
      height: 100%
      width: 32px
      display: flex
      align-items: center

  &__wrapper
    position: relative
    overflow-x: auto
    height: inherit

  &__item
    align-items: center
    display: flex
    flex-shrink: 0
    justify-content: center
    height: 100%
    padding: 0 12px
    position: relative
    text-align: center
    text-decoration: none
    text-transform: uppercase
    text-overflow: ellipsis
    white-space: nowrap

    .icon
      color: inherit
      margin: 0 0 5px

    &--disabled
      pointer-events: none

  &__items
    position: relative
    overflow: hidden

  &__content
    transition: $primary-transition
    width: 100%

    .card
      border-radius: 0 0 2px 2px

  &__slider
    position: absolute
    bottom: 0
    height: 2px
    transition: $primary-transition


import Colorable from './colorable'
import Input from './input'

export default {
  mixins: [Input, Colorable],

  model: {
    prop: 'inputValue',
    event: 'change'
  },

  data: () => ({
    defaultColor: 'accent'
  }),

  props: {
    id: String,
    inputValue: null,
    falseValue: null,
    trueValue: null
  },

  computed: {
    isActive () {
      if ((Array.isArray(this.inputValue))
      ) {
        return this.inputValue.indexOf(this.value) !== -1
      }

      if (!this.trueValue || !this.falseValue) {
        return this.value
          ? this.value === this.inputValue
          : Boolean(this.inputValue)
      }

      return this.inputValue === this.trueValue
    },
    isDirty () {
      return this.isActive
    }
  },

  watch: {
    indeterminate (val) {
      this.inputIndeterminate = val
    }
  },

  methods: {
    genLabel () {
      return this.$createElement('label', {
        on: { click: this.toggle },
        attrs: {
          for: this.id
        }
      }, this.$slots.label || this.label)
    },
    toggle () {
      if (this.disabled) {
        return
      }

      let input = this.inputValue
      if (Array.isArray(input)) {
        input = input.slice()
        const i = input.indexOf(this.value)

        if (i === -1) {
          input.push(this.value)
        } else {
          input.splice(i, 1)
        }
      } else if (this.trueValue || this.falseValue) {
        input = input === this.trueValue ? this.falseValue : this.trueValue
      } else if (this.value) {
        input = this.value === this.inputValue
          ? null
          : this.value
      } else {
        input = !input
      }

      this.validate(true, input)

      this.$emit('change', input)
    }
  }
}


.DS_Store
node_modules/
.vscode/
npm-debug.log
.idea/
coverage/
Playground.vue
report.html
.vs/
dev/*.js
dev/*css
!dev/index.js
!dev/router.js
!dev/prepare-commit-message.js
/es5
/dist
/release


if (err) {
console.log(err);
return false;
}
const resultsFromServer = [];

if (results.users[0] && results.users[0].username === currentFilter) {

if (results.rooms[0] && results.rooms[0].username === currentFilter) {

import { Meteor } from 'meteor/meteor';
import { DDPRateLimiter } from 'meteor/ddp-rate-limiter';
import s from 'underscore.string';

import { hasPermission } from '../../app/authorization';
import { Users, Subscriptions, Rooms } from '../../app/models';
import { settings } from '../../app/settings';
import { roomTypes } from '../../app/utils';

function fetchRooms(userId, rooms) {
	if (!settings.get('Store_Last_Message') || hasPermission(userId, 'preview-c-room')) {
		return rooms;
	}

	return rooms.map((room) => {
		delete room.lastMessage;
		return room;
	});
}

Meteor.methods({
	spotlight(text, usernames, type = { users: true, rooms: true }, rid) {
		const searchForChannels = text[0] === '#';
		const searchForDMs = text[0] === '@';
		if (searchForChannels) {
			type.users = false;
			text = text.slice(1);
		}
		if (searchForDMs) {
			type.rooms = false;
			text = text.slice(1);
		}
		const regex = new RegExp(s.trim(s.escapeRegExp(text)), 'i');
		const result = {
			users: [],
			rooms: [],
		};
		const roomOptions = {
			limit: 5,
			fields: {
				t: 1,
				name: 1,
				joinCodeRequired: 1,
				lastMessage: 1,
			},
			sort: {
				name: 1,
			},
		};
		const { userId } = this;
		if (userId == null) {
			if (settings.get('Accounts_AllowAnonymousRead') === true) {
				result.rooms = fetchRooms(userId, Rooms.findByNameAndTypeNotDefault(regex, 'c', roomOptions).fetch());
			}
			return result;
		}
		const userOptions = {
			limit: 5,
			fields: {
				username: 1,
				name: 1,
				status: 1,
				statusText: 1,
			},
			sort: {},
		};
		if (settings.get('UI_Use_Real_Name')) {
			userOptions.sort.name = 1;
		} else {
			userOptions.sort.username = 1;
		}

		if (hasPermission(userId, 'view-outside-room')) {
			if (type.users === true && hasPermission(userId, 'view-d-room')) {
				result.users = Users.findByActiveUsersExcept(text, usernames, userOptions).fetch();
			}

			if (type.rooms === true && hasPermission(userId, 'view-c-room')) {
				const searchableRoomTypes = Object.entries(roomTypes.roomTypes)
					.filter((roomType) => roomType[1].includeInRoomSearch())
					.map((roomType) => roomType[0]);

				const roomIds = Subscriptions.findByUserIdAndTypes(userId, searchableRoomTypes, { fields: { rid: 1 } }).fetch().map((s) => s.rid);
				result.rooms = fetchRooms(userId, Rooms.findByNameAndTypesNotInIds(regex, searchableRoomTypes, roomIds, roomOptions).fetch());
			}
		} else if (type.users === true && rid) {
			const subscriptions = Subscriptions.find({
				rid,
				'u.username': {
					$regex: regex,
					$nin: [...usernames, Meteor.user().username],
				},
			}, { limit: userOptions.limit }).fetch().map(({ u }) => u._id);
			result.users = Users.find({ _id: { $in: subscriptions } }, {
				fields: userOptions.fields,
				sort: userOptions.sort,
			}).fetch();
		}

		return result;
	},
});

DDPRateLimiter.addRule({
	type: 'method',
	name: 'spotlight',
	userId(/* userId*/) {
		return true;
	},
}, 100, 100000);


import { Match, check } from 'meteor/check';

import { hasPermission } from '../../../../authorization/server';
import { API } from '../../../../api';
import { findRooms } from '../../../server/api/lib/rooms';

const validateDateParams = (property, date) => {
	if (date) {
		date = JSON.parse(date);
	}
	if (date && date.start && isNaN(Date.parse(date.start))) {
		throw new Error(`The "${ property }.start" query parameter must be a valid date.`);
	}
	if (date && date.end && isNaN(Date.parse(date.end))) {
		throw new Error(`The "${ property }.end" query parameter must be a valid date.`);
	}
	return date;
};

API.v1.addRoute('livechat/rooms', { authRequired: true }, {
	get() {
		if (!hasPermission(this.userId, 'view-livechat-manager')) {
			return API.v1.unauthorized();
		}
		const { offset, count } = this.getPaginationItems();
		const { sort, fields } = this.parseJsonQuery();
		const { agents, departmentId, open, tags, roomName } = this.requestParams();
		let { createdAt, customFields, closedAt } = this.requestParams();
		check(agents, Match.Maybe([String]));
		check(roomName, Match.Maybe(String));
		check(departmentId, Match.Maybe(String));
		check(open, Match.Maybe(String));
		check(tags, Match.Maybe([String]));

		createdAt = validateDateParams('createdAt', createdAt);
		closedAt = validateDateParams('closedAt', closedAt);

		if (customFields) {
			customFields = JSON.parse(customFields);
		}

		return API.v1.success(Promise.await(findRooms({
			agents,
			roomName,
			departmentId,
			open: open && open === 'true',
			createdAt,
			closedAt,
			tags,
			customFields,
			options: { offset, count, sort, fields },
		})));
	},
});


const roomFilter = (room) => !resultsFromClient.find((item) => [item.rid, item._id].includes(room._id));


<template name="livechatReadOnly">
	{{#if roomOpen}}
		{{#if showPreview}}
			{{#if isPreparing}}
				{{> loading}}
			{{else}}
				{{#if inquiryOpen}}

						{{{_ "you_are_in_preview_mode_of_incoming_livechat"}}}
						<button class="rc-button rc-button--primary rc-button--small rc-message-box__take-it-button js-take-it">{{_ "Take_it"}}</button>

				{{else}}
					{{_ "room_is_read_only"}}
				{{/if}}
			{{/if}}
		{{/if}}
	{{else}}

{{_ "This_conversation_is_already_closed"}}

	{{/if}}
</template>


<template name="CreateDiscussion">

{{_ "Discussion_description"}}

			<form id="create-discussion" class="create-channel__content">
				{{#unless disabled}}
				{{> SearchCreateDiscussion
					onClickTag=onClickTagRoom
					deleteLastItem=deleteLastItemRoom
					list=selectedRoom
					onSelect=onSelectRoom
					collection=roomCollection
					field='name'
					sort='name'
					label="Discussion_target_channel"
					placeholder="Discussion_target_channel_description"
					name="parentChannel"
					disabled=disabled
					selector=roomSelector
					description=targetChannelText
					noMatchTemplate="roomSearchEmpty"
					templateItem="popupList_item_channel"
					modifier=roomModifier
				}} {{else}}

{{_ "Discussion_target_channel"}}

								<input disabled name="parentChannel" id="parentChannel" value={{parentChannel}} class="rc-input__element" />

				{{/unless}}

{{_ "Discussion_name"}}

									{{> icon block="rc-input__icon-svg" icon="discussion"}}

								<input name="discussion_name" id="discussion_name" class="rc-input__element" placeholder="{{_ 'New_discussion_name'}}"
									maxlength="{{maxMessageLength}}" value="{{nameSuggestion}}"/>

					{{> SearchCreateDiscussion
						onClickTag=onClickTagUser
						deleteLastItem=deleteLastItemUser
						list=selectedUsers
						onSelect=onSelectUser
						collection='UserAndRoom'
						endpoint='users.autocomplete'
						field='username'
						sort='username'
						label="Invite_Users"
						placeholder="Username_Placeholder"
						name="users"
						icon="at"
						noMatchTemplate="userSearchEmpty"
						templateItem="popupList_item_default"
						modifier=userModifier
					}}

{{_ "Discussion_first_message_title"}}

								<textarea rows="5" name="discussion_message" id="discussion_message" class="rc-input__element" placeholder="{{_ 'New_discussion_first_message'}}"
									maxlength="{{maxMessageLength}}"></textarea>

			</form>

				<button form="create-discussion" class="rc-button rc-button--primary js-save-discussion {{createIsDisabled}}" {{createIsDisabled}}>{{_ " Create "}}</button>

</template>

<template name="SearchCreateDiscussion">

{{_ label}}

				{{# if icon}}

					{{> icon block="rc-input__icon-svg" icon=icon}}

				{{/if}}

					{{#each item in list}} {{> tag item}} {{/each}}
					<input type="text" id="{{name}}" class="rc-tags__input" placeholder="{{_ placeholder}}" name="{{name}}" autocomplete="off" {{disabled}} />

			{{#with config}}
				{{#if autocomplete 'isShowing'}}
					{{> popupList data=config items=items ready=(autocomplete 'isLoaded')}}
				{{/if}}
			{{/with}}

{{ description }}

</template>


import { ResponsiveHeatMap } from '@nivo/heatmap';
import { Box, Flex, Select, Skeleton } from '@rocket.chat/fuselage';
import moment from 'moment';
import React, { useMemo, useState } from 'react';

import { useTranslation } from '../../../../../../client/contexts/TranslationContext';
import { useEndpointData } from '../../../../../../client/hooks/useEndpointData';
import { Section } from '../Section';

export function UsersByTimeOfTheDaySection() {
	const t = useTranslation();

	const periodOptions = useMemo(() => [
		['last 7 days', t('Last_7_days')],
		['last 30 days', t('Last_30_days')],
		['last 90 days', t('Last_90_days')],
	], [t]);

	const [periodId, setPeriodId] = useState('last 7 days');

	const period = useMemo(() => {
		switch (periodId) {
			case 'last 7 days':
				return {
					start: moment().set({ hour: 0, minute: 0, second: 0, millisecond: 0 }).subtract(7, 'days'),
					end: moment().set({ hour: 0, minute: 0, second: 0, millisecond: 0 }).subtract(1),
				};

			case 'last 30 days':
				return {
					start: moment().set({ hour: 0, minute: 0, second: 0, millisecond: 0 }).subtract(30, 'days'),
					end: moment().set({ hour: 0, minute: 0, second: 0, millisecond: 0 }).subtract(1),
				};

			case 'last 90 days':
				return {
					start: moment().set({ hour: 0, minute: 0, second: 0, millisecond: 0 }).subtract(90, 'days'),
					end: moment().set({ hour: 0, minute: 0, second: 0, millisecond: 0 }).subtract(1),
				};
		}
	}, [periodId]);

	const handlePeriodChange = (periodId) => setPeriodId(periodId);

	const params = useMemo(() => ({
		start: period.start.toISOString(),
		end: period.end.toISOString(),
	}), [period]);

	const data = useEndpointData('engagement-dashboard/users/users-by-time-of-the-day-in-a-week', params);

	const [
		dates,
		values,
	] = useMemo(() => {
		if (!data) {
			return [];
		}

		const dates = Array.from({ length: moment(period.end).diff(period.start, 'days') + 1 },
			(_, i) => moment(period.start).add(i, 'days'));

		const values = Array.from({ length: 24 }, (_, hour) => ({
			hour: String(hour),
			...dates.map((date) => ({ [date.toISOString()]: 0 }))
				.reduce((obj, elem) => ({ ...obj, ...elem }), {}),
		}));

		for (const { users, hour, day, month, year } of data.week) {
			const date = moment([year, month - 1, day, 0, 0, 0, 0]).toISOString();
			values[hour][date] += users;
		}

		return [
			dates.map((date) => date.toISOString()),
			values,
		];
	}, [data]);

	return <Section
		title={t('Users_by_time_of_day')}
		filter={<Select options={periodOptions} value={periodId} onChange={handlePeriodChange} />}
	>
		<Flex.Container>
			{data
				? <Box style={{ height: 696 }}>
					<Flex.Item align='stretch' grow={1} shrink={0}>
						<Box style={{ position: 'relative' }}>
							<Box style={{ position: 'absolute', width: '100%', height: '100%' }}>
								<ResponsiveHeatMap
									data={values}
									indexBy='hour'
									keys={dates}
									padding={4}
									margin={{
										// TODO: Get it from theme
										left: 40,
										bottom: 20,
									}}
									colors={[
										// TODO: Get it from theme
										'#E8F2FF',
										'#D1EBFE',
										'#A4D3FE',
										'#76B7FC',
										'#549DF9',
										'#1D74F5',
										'#10529E',
									]}
									cellOpacity={1}
									enableLabels={false}
									axisTop={null}
									axisRight={null}
									axisBottom={{
										// TODO: Get it from theme
										tickSize: 0,
										tickPadding: 4,
										tickRotation: 0,
										format: (isoString) => (dates.length === 7 ? moment(isoString).format('dddd') : ''),
									}}
									axisLeft={{
										// TODO: Get it from theme
										tickSize: 0,
										tickPadding: 4,
										tickRotation: 0,
										format: (hour) => moment().set({ hour: parseInt(hour, 10), minute: 0, second: 0 }).format('LT'),
									}}
									hoverTarget='cell'
									animate={dates.length <= 7}
									motionStiffness={90}
									motionDamping={15}
									theme={{
										// TODO: Get it from theme
										axis: {
											ticks: {
												text: {
													fill: '#9EA2A8',
													fontFamily: 'Inter, -apple-system, system-ui, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Helvetica Neue", "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Meiryo UI", Arial, sans-serif',
													fontSize: 10,
													fontStyle: 'normal',
													fontWeight: '600',
													letterSpacing: '0.2px',
													lineHeight: '12px',
												},
											},
										},
										tooltip: {
											container: {
												backgroundColor: '#1F2329',
												boxShadow: '0px 0px 12px rgba(47, 52, 61, 0.12), 0px 0px 2px rgba(47, 52, 61, 0.08)',
												borderRadius: 2,
											},
										},
									}}
									tooltip={({ value }) => <Box fontScale='p2' color='alternative'>
										{t('Value_users', { value })}
									</Box>}
								/>
							</Box>
						</Box>
					</Flex.Item>
				</Box>
				: <Skeleton variant='rect' height={696} />}
		</Flex.Container>
	</Section>;
}


t: 'd',
name: user.username,
fname: user.name,
})));


import { Template } from 'meteor/templating';

import { settings } from '../../../app/settings';

Template.message.helpers({
	readReceipt() {
		if (!settings.get('Message_Read_Receipt_Enabled')) {
			return;
		}

		return {
			readByEveryone: (!this.msg.unread && 'read') || 'color-component-color',
		};
	},
});


import { Meteor } from 'meteor/meteor';

import { settings } from '../../settings';

Meteor.startup(function() {
	settings.addGroup('SMS', function() {
		this.add('SMS_Enabled', false, {
			type: 'boolean',
			i18nLabel: 'Enabled',
		});

		this.add('SMS_Service', 'twilio', {
			type: 'select',
			values: [
				{
					key: 'twilio',
					i18nLabel: 'Twilio',
				},
				{
					key: 'voxtelesys',
					i18nLabel: 'Voxtelesys',
				},
				{
					key: 'mobex',
					i18nLabel: 'Mobex',
				},
			],
			i18nLabel: 'Service',
		});

		this.add('SMS_Default_Omnichannel_Department', '', {
			type: 'string',
		});

		this.section('Twilio', function() {
			this.add('SMS_Twilio_Account_SID', '', {
				type: 'string',
				enableQuery: {
					_id: 'SMS_Service',
					value: 'twilio',
				},
				i18nLabel: 'Account_SID',
				secret: true,
			});
			this.add('SMS_Twilio_authToken', '', {
				type: 'string',
				enableQuery: {
					_id: 'SMS_Service',
					value: 'twilio',
				},
				i18nLabel: 'Auth_Token',
				secret: true,
			});
			this.add('SMS_Twilio_FileUpload_Enabled', true, {
				type: 'boolean',
				enableQuery: {
					_id: 'SMS_Service',
					value: 'twilio',
				},
				i18nLabel: 'FileUpload_Enabled',
				secret: true,
			});
			this.add('SMS_Twilio_FileUpload_MediaTypeWhiteList', 'image/*,audio/*,video/*,text/*,application/pdf', {
				type: 'string',
				enableQuery: {
					_id: 'SMS_Service',
					value: 'twilio',
				},
				i18nLabel: 'FileUpload_MediaTypeWhiteList',
				i18nDescription: 'FileUpload_MediaTypeWhiteListDescription',
				secret: true,
			});
		});

		this.section('Voxtelesys', function() {
			this.add('SMS_Voxtelesys_authToken', '', {
				type: 'string',
				enableQuery: {
					_id: 'SMS_Service',
					value: 'voxtelesys',
				},
				i18nLabel: 'Auth_Token',
				secret: true,
			});
			this.add('SMS_Voxtelesys_URL', 'https://smsapi.voxtelesys.net/api/v1/sms', {
				type: 'string',
				enableQuery: {
					_id: 'SMS_Service',
					value: 'voxtelesys',
				},
				i18nLabel: 'URL',
				secret: true,
			});
			this.add('SMS_Voxtelesys_FileUpload_Enabled', true, {
				type: 'boolean',
				enableQuery: {
					_id: 'SMS_Service',
					value: 'voxtelesys',
				},
				i18nLabel: 'FileUpload_Enabled',
				secret: true,
			});
			this.add('SMS_Voxtelesys_FileUpload_MediaTypeWhiteList', 'image/*,audio/*,video/*,text/*,application/pdf', {
				type: 'string',
				enableQuery: {
					_id: 'SMS_Service',
					value: 'voxtelesys',
				},
				i18nLabel: 'FileUpload_MediaTypeWhiteList',
				i18nDescription: 'FileUpload_MediaTypeWhiteListDescription',
				secret: true,
			});
		});

		this.section('Mobex', function() {
			this.add('SMS_Mobex_gateway_address', '', {
				type: 'string',
				enableQuery: {
					_id: 'SMS_Service',
					value: 'mobex',
				},
				i18nLabel: 'Mobex_sms_gateway_address',
				i18nDescription: 'Mobex_sms_gateway_address_desc',
			});
			this.add('SMS_Mobex_restful_address', '', {
				type: 'string',
				enableQuery: {
					_id: 'SMS_Service',
					value: 'mobex',
				},
				i18nLabel: 'Mobex_sms_gateway_restful_address',
				i18nDescription: 'Mobex_sms_gateway_restful_address_desc',
			});
			this.add('SMS_Mobex_username', '', {
				type: 'string',
				enableQuery: {
					_id: 'SMS_Service',
					value: 'mobex',
				},
				i18nLabel: 'Mobex_sms_gateway_username',
			});
			this.add('SMS_Mobex_password', '', {
				type: 'password',
				enableQuery: {
					_id: 'SMS_Service',
					value: 'mobex',
				},
				i18nLabel: 'Mobex_sms_gateway_password',
			});
			this.add('SMS_Mobex_from_number', '', {
				type: 'int',
				enableQuery: {
					_id: 'SMS_Service',
					value: 'mobex',
				},
				i18nLabel: 'Mobex_sms_gateway_from_number',
				i18nDescription: 'Mobex_sms_gateway_from_number_desc',
			});
			this.add('SMS_Mobex_from_numbers_list', '', {
				type: 'string',
				enableQuery: {
					_id: 'SMS_Service',
					value: 'mobex',
				},
				i18nLabel: 'Mobex_sms_gateway_from_numbers_list',
				i18nDescription: 'Mobex_sms_gateway_from_numbers_list_desc',
			});
		});
	});
});


resultsFromServer.push(...results.rooms.filter((room) => !resultsFromClient.find((item) => [item.rid, item._id].includes(room._id))));


import { callbacks } from '../../../callbacks';
import { LivechatRooms } from '../../../models';

callbacks.add('afterSaveMessage', function(message, room) {
	if (!(typeof room.t !== 'undefined' && room.t === 'l' && room.v && room.v.token)) {
		return message;
	}
	if (message.token) {
		LivechatRooms.setVisitorLastMessageTimestampByRoomId(room._id, message.ts);
	}
	return message;
}, callbacks.priority.HIGH, 'save-last-visitor-message-timestamp');


import { Skeleton } from '@rocket.chat/fuselage';
import React from 'react';

import Subtitle from '../../components/basic/Subtitle';
import { useTranslation } from '../../contexts/TranslationContext';
import { useFormatDateAndTime } from '../../hooks/useFormatDateAndTime';
import { useFormatDuration } from '../../hooks/useFormatDuration';
import { DescriptionList } from './DescriptionList';

export function RocketChatSection({ info, statistics, isLoading }) {
	const t = useTranslation();
	const formatDateAndTime = useFormatDateAndTime();
	const formatDuration = useFormatDuration();

	const s = (fn) => (isLoading ? <Skeleton width='50%' /> : fn());

	const appsEngineVersion = info && info.marketplaceApiVersion;

	return <DescriptionList
		data-qa='rocket-chat-list'
		title={<Subtitle data-qa='rocket-chat-title'>{t('Rocket.Chat')}</Subtitle>}
	>
		<DescriptionList.Entry label={t('Version')}>{s(() => statistics.version)}</DescriptionList.Entry>
		{appsEngineVersion && <DescriptionList.Entry label={t('Apps_Engine_Version')}>{appsEngineVersion}</DescriptionList.Entry>}
		<DescriptionList.Entry label={t('DB_Migration')}>{s(() => statistics.migration.version)}</DescriptionList.Entry>
		<DescriptionList.Entry label={t('DB_Migration_Date')}>{s(() => formatDateAndTime(statistics.migration.lockedAt))}</DescriptionList.Entry>
		<DescriptionList.Entry label={t('Installed_at')}>{s(() => formatDateAndTime(statistics.installedAt))}</DescriptionList.Entry>
		<DescriptionList.Entry label={t('Uptime')}>{s(() => formatDuration(statistics.process.uptime))}</DescriptionList.Entry>
		<DescriptionList.Entry label={t('Deployment_ID')}>{s(() => statistics.uniqueId)}</DescriptionList.Entry>
		<DescriptionList.Entry label={t('PID')}>{s(() => statistics.process.pid)}</DescriptionList.Entry>
		<DescriptionList.Entry label={t('Running_Instances')}>{s(() => statistics.instanceCount)}</DescriptionList.Entry>
		<DescriptionList.Entry label={t('OpLog')}>{s(() => (statistics.oplogEnabled ? t('Enabled') : t('Disabled')))}</DescriptionList.Entry>
	</DescriptionList>;
}


import { check } from 'meteor/check';
import { Accounts } from 'meteor/accounts-base';

import * as Mailer from '../../../mailer';
import { Users, Subscriptions } from '../../../models';
import { settings } from '../../../settings';

export function setUserActiveStatus(userId, active) {
	check(userId, String);
	check(active, Boolean);

	const user = Users.findOneById(userId);

	if (!user) {
		return false;
	}

	Users.setUserActive(userId, active);

	if (user.username) {
		Subscriptions.setArchivedByUsername(user.username, !active);
	}

	if (active === false) {
		Users.unsetLoginTokens(userId);
	} else {
		Users.unsetReason(userId);
	}
	if (active && !settings.get('Accounts_Send_Email_When_Activating')) {
		return true;
	}
	if (!active && !settings.get('Accounts_Send_Email_When_Deactivating')) {
		return true;
	}

	const destinations = Array.isArray(user.emails) && user.emails.map((email) => `${ user.name || user.username }<${ email.address }>`);

	const email = {
		to: destinations,
		from: settings.get('From_Email'),
		subject: Accounts.emailTemplates.userActivated.subject({ active }),
		html: Accounts.emailTemplates.userActivated.html({ active, name: user.name, username: user.username }),
	};

	Mailer.sendNoWrap(email);
}


const getFromServerDebounced = _.debounce(getFromServer, 500);

keep replace replace keep keep keep keep keep

if (resultsFromServer.length || exactUser || exactRoom) {

exactRoom = exactRoom ? [roomFilter(exactRoom)] : [];

exactUser = exactUser ? [userMap(exactUser)] : [];

const combinedResults = exactUser.concat(exactRoom, resultsFromClient, resultsFromServer);


import { Base } from '../../../../../app/models';

/**
 * Livechat Priority model
 */
export class LivechatPriority extends Base {
	constructor() {
		super('livechat_priority');

		this.tryEnsureIndex({ name: 1 }, { unique: true });
		this.tryEnsureIndex({ dueTimeInMinutes: 1 }, { unique: true });
	}

	// FIND
	findOneById(_id, options) {
		const query = { _id };

		return this.findOne(query, options);
	}

	findOneByIdOrName(_idOrName, options) {
		const query = {
			$or: [{
				_id: _idOrName,
			}, {
				name: _idOrName,
			}],
		};

		return this.findOne(query, options);
	}

	createOrUpdatePriority(_id, { name, description, color, dueTimeInMinutes }) {
		const record = {
			name,
			description,
			color,
			dueTimeInMinutes: parseInt(dueTimeInMinutes),
		};

		if (_id) {
			this.update({ _id }, { $set: record });
		} else {
			_id = this.insert(record);
		}

		return Object.assign(record, { _id });
	}

	// REMOVE
	removeById(_id) {
		const query = { _id };

		return this.remove(query);
	}
}

export default new LivechatPriority();


The forwarding to the target department is not allowed.

You reached the maximum number of guest users allowed by your license. Contact sale@rocket.chat for a new license.

The maximum number of simultaneous chats per agent has been reached.

Enable automatic closing of rooms abandoned by the visitor

If your workspace is registered and license is provided by Rocket.Chat Cloud you don't need to manually update the license here.

The default RC role to be applied to user if the user has some LDAP role that is not mapped.

Enable role mapping from LDAP to Rocket.Chat

LDAP query to get the LDAP groups that the user is part of.

Role mapping in object format where the object key must be the LDAP role and the object value must be an array of RC roles. Example: { 'ldapRole': ['rcRole', 'anotherRCRole'] }

If the validation should occurs for each login (Be careful with this setting because it will overwrite the user roles in each login, otherwise this will be validated only at the moment of user creation).

List of departments allowed for forwarding (Optional)

Allow to set a restricted list of departments that can receive chats from this department

Custom message when room is automatically closed by visitor inactivity

The max. number of simultaneous chats that the agents can attend

Priority Changed: __user__ changed the priority to __priority__

Number of recent chats to calculate estimate wait time

This number defines the number of last served rooms that will be used to calculate queue wait times.

There are no departments added to this tag yet

There are no departments added to this unit yet

There are no monitors added to this unit yet

Message that will be displayed to the visitors when they get in the queue


import { Meteor } from 'meteor/meteor';

import { Users } from '../../../models';
import { TOTP } from '../lib/totp';

Meteor.methods({
	'2fa:validateTempToken'(userToken) {
		if (!Meteor.userId()) {
			throw new Meteor.Error('not-authorized');
		}

		const user = Meteor.user();

		if (!user.services || !user.services.totp || !user.services.totp.tempSecret) {
			throw new Meteor.Error('invalid-totp');
		}

		const verified = TOTP.verify({
			secret: user.services.totp.tempSecret,
			token: userToken,
		});

		if (verified) {
			const { codes, hashedCodes } = TOTP.generateCodes();

			Users.enable2FAAndSetSecretAndCodesByUserId(Meteor.userId(), user.services.totp.tempSecret, hashedCodes);
			return { codes };
		}
	},
});


spotlight(text, usernames, type = { users: true, rooms: true }, rid) {

const searchForChannels = text[0] === '#';

spotlight(text, usernames = [], type = { users: true, rooms: true }, rid) {

import { Meteor } from 'meteor/meteor';
import { Tracker } from 'meteor/tracker';
import { ReactiveVar } from 'meteor/reactive-var';
import { FlowRouter } from 'meteor/kadira:flow-router';
import { Session } from 'meteor/session';
import { Template } from 'meteor/templating';
import { TAPi18n } from 'meteor/rocketchat:tap-i18n';
import _ from 'underscore';

import { toolbarSearch } from './sidebarHeader';
import { Rooms, Subscriptions } from '../../models';
import { roomTypes } from '../../utils';
import { hasAtLeastOnePermission } from '../../authorization';
import { menu } from '../../ui-utils';

let filterText = '';
let usernamesFromClient;
let resultsFromClient;

const isLoading = new ReactiveVar(false);

const getFromServer = (cb, type) => {
	isLoading.set(true);
	const currentFilter = filterText;

	Meteor.call('spotlight', currentFilter, usernamesFromClient, type, (err, results) => {
		if (currentFilter !== filterText) {
			return;
		}

		isLoading.set(false);

		if (err) {
			console.log(err);
			return false;
		}

		const resultsFromServer = [];

		resultsFromServer.push(...results.users.map((user) => ({
			_id: user._id,
			t: 'd',
			name: user.username,
			fname: user.name,
		})));

		resultsFromServer.push(...results.rooms.filter((room) => !resultsFromClient.find((item) => [item.rid, item._id].includes(room._id))));

		if (resultsFromServer.length) {
			cb(resultsFromClient.concat(resultsFromServer));
		}
	});
};

const getFromServerDebounced = _.debounce(getFromServer, 500);

Template.toolbar.helpers({
	results() {
		return Template.instance().resultsList.get();
	},
	getPlaceholder() {
		let placeholder = TAPi18n.__('Search');

		if (!Meteor.Device.isDesktop()) {
			return placeholder;
		} if (window.navigator.platform.toLowerCase().includes('mac')) {
			placeholder = `${ placeholder } (\u2318+K)`;
		} else {
			placeholder = `${ placeholder } (\u2303+K)`;
		}

		return placeholder;
	},
	popupConfig() {
		const config = {
			collection: Meteor.userId() ? Subscriptions : Rooms,
			template: 'toolbarSearchList',
			sidebar: true,
			emptyTemplate: 'toolbarSearchListEmpty',
			input: '.toolbar__search .rc-input__element',
			cleanOnEnter: true,
			closeOnEsc: true,
			blurOnSelectItem: true,
			isLoading,
			open: Template.instance().open,
			getFilter(collection, filter, cb) {
				filterText = filter;

				const type = {
					users: true,
					rooms: true,
				};

				const query = {
					rid: {
						$ne: Session.get('openedRoom'),
					},
				};

				if (!Meteor.userId()) {
					query._id = query.rid;
					delete query.rid;
				}
				const searchForChannels = filterText[0] === '#';
				const searchForDMs = filterText[0] === '@';
				if (searchForChannels) {
					filterText = filterText.slice(1);
					type.users = false;
					query.t = 'c';
				}

				if (searchForDMs) {
					filterText = filterText.slice(1);
					type.rooms = false;
					query.t = 'd';
				}

				const searchQuery = new RegExp(RegExp.escape(filterText), 'i');
				query.$or = [
					{ name: searchQuery },
					{ fname: searchQuery },
				];

				resultsFromClient = collection.find(query, { limit: 20, sort: { unread: -1, ls: -1 } }).fetch();

				const resultsFromClientLength = resultsFromClient.length;
				const user = Meteor.users.findOne(Meteor.userId(), { fields: { name: 1, username: 1 } });
				if (user) {
					usernamesFromClient = [user];
				}

				for (let i = 0; i < resultsFromClientLength; i++) {
					if (resultsFromClient[i].t === 'd') {
						usernamesFromClient.push(resultsFromClient[i].name);
					}
				}

				cb(resultsFromClient);

				// Use `filter` here to get results for `#` or `@` filter only
				if (resultsFromClient.length < 20) {
					getFromServerDebounced(cb, type);
				}
			},

			getValue(_id, collection, records) {
				const doc = _.findWhere(records, { _id });

				roomTypes.openRouteLink(doc.t, doc, FlowRouter.current().queryParams);
				menu.close();
			},
		};

		return config;
	},
});

Template.toolbar.events({
	'submit form'(e) {
		e.preventDefault();
		return false;
	},

	'click [role="search"] input'() {
		toolbarSearch.shortcut = false;
	},

	'click [role="search"] button, touchend [role="search"] button'(e) {
		if (hasAtLeastOnePermission(['create-c', 'create-p'])) {
			// TODO: resolve this name menu/sidebar/sidebav/flex...
			menu.close();
			FlowRouter.go('create-channel');
		} else {
			e.preventDefault();
		}
	},
});

Template.toolbar.onRendered(function() {
	this.$('.js-search').select().focus();
});

Template.toolbar.onCreated(function() {
	this.open = new ReactiveVar(true);

	Tracker.autorun(() => !this.open.get() && toolbarSearch.close());
});


import { hasPermissionAsync } from '../../../../authorization/server/functions/hasPermission';
import { LivechatDepartmentAgents } from '../../../../models/server/raw';

export async function findAgentDepartments({ userId, enabledDepartmentsOnly, agentId }) {
	if (!await hasPermissionAsync(userId, 'view-l-room')) {
		throw new Error('error-not-authorized');
	}
	if (enabledDepartmentsOnly) {
		return {
			departments: await LivechatDepartmentAgents.findActiveDepartmentsByAgentId(agentId),
		};
	}

	return {
		departments: await LivechatDepartmentAgents.find({ agentId }).toArray(),
	};
}


import { Mongo } from 'meteor/mongo';

import * as Models from '..';

const Roles = new Mongo.Collection(null);

Object.assign(Roles, {
	findUsersInRole(name, scope, options) {
		const role = this.findOne(name);
		const roleScope = (role && role.scope) || 'Users';
		const model = Models[roleScope];
		return model && model.findUsersInRoles && model.findUsersInRoles(name, scope, options);
	},

	isUserInRoles(userId, roles, scope) {
		roles = [].concat(roles);
		return roles.some((roleName) => {
			const role = this.findOne(roleName);
			const roleScope = (role && role.scope) || 'Users';
			const model = Models[roleScope];
			return model && model.isUserInRole && model.isUserInRole(userId, roleName, scope);
		});
	},
});

export { Roles };


if (hasPermission(userId, 'view-outside-room')) {

if (type.users === true && hasPermission(userId, 'view-d-room')) {

result.users = Users.findByActiveUsersExcept(text, usernames, userOptions).fetch();

if (type.rooms === true && hasPermission(userId, 'view-c-room')) {

const searchableRoomTypes = Object.entries(roomTypes.roomTypes)

keep keep keep keep replace keep keep keep keep keep

const exactUser = Users.findOneByUsernameIgnoringCase(text, userOptions);

if (exactUser && !usernames.includes(exactUser.username)) {

result.users = result.users.concat(Users.findByActiveUsersExcept(text, usernames, userOptions).fetch());


import { Meteor } from 'meteor/meteor';

if (Meteor.isClient) {
	module.exports = require('./client/index.js');
}
if (Meteor.isServer) {
	module.exports = require('./server/index.js');
}


import { settings } from '../../settings';

settings.addGroup('General', function() {
	this.section('REST API', function() {
		this.add('API_Upper_Count_Limit', 100, { type: 'int', public: false });
		this.add('API_Default_Count', 50, { type: 'int', public: false });
		this.add('API_Allow_Infinite_Count', true, { type: 'boolean', public: false });
		this.add('API_Enable_Direct_Message_History_EndPoint', false, { type: 'boolean', public: false });
		this.add('API_Enable_Shields', true, { type: 'boolean', public: false });
		this.add('API_Shield_Types', '*', { type: 'string', public: false, enableQuery: { _id: 'API_Enable_Shields', value: true } });
		this.add('API_Shield_user_require_auth', false, { type: 'boolean', public: false, enableQuery: { _id: 'API_Enable_Shields', value: true } });
		this.add('API_Enable_CORS', false, { type: 'boolean', public: false });
		this.add('API_CORS_Origin', '*', { type: 'string', public: false, enableQuery: { _id: 'API_Enable_CORS', value: true } });

		this.add('API_Use_REST_For_DDP_Calls', false, {
			type: 'boolean',
			public: true,
			alert: 'API_Use_REST_For_DDP_Calls_Alert',
		});
	});
});


const roomIds = Subscriptions.findByUserIdAndTypes(userId, searchableRoomTypes, { fields: { rid: 1 } }).fetch().map((s) => s.rid);

result.rooms = fetchRooms(userId, Rooms.findByNameAndTypesNotInIds(regex, searchableRoomTypes, roomIds, roomOptions).fetch());

} else if (type.users === true && rid) {

const subscriptions = Subscriptions.find({

keep keep keep replace keep keep keep keep

const exactRoom = Rooms.findOneByNameAndType(text, searchableRoomTypes, roomOptions);

result.rooms = result.rooms.concat(fetchRooms(userId, Rooms.findByNameAndTypesNotInIds(regex, searchableRoomTypes, roomIds, roomOptions).fetch()));


//! moment.js locale configuration
//! locale : Tibetan [bo]
//! author : Thupten N. Chakrishar : https://github.com/vajradog

;(function (global, factory) {
   typeof exports === 'object' && typeof module !== 'undefined'
       && typeof require === 'function' ? factory(require('../moment')) :
   typeof define === 'function' && define.amd ? define(['../moment'], factory) :
   factory(global.moment)
}(this, function (moment) { 'use strict';

    var symbolMap = {
        '1': '༡',
        '2': '༢',
        '3': '༣',
        '4': '༤',
        '5': '༥',
        '6': '༦',
        '7': '༧',
        '8': '༨',
        '9': '༩',
        '0': '༠'
    },
    numberMap = {
        '༡': '1',
        '༢': '2',
        '༣': '3',
        '༤': '4',
        '༥': '5',
        '༦': '6',
        '༧': '7',
        '༨': '8',
        '༩': '9',
        '༠': '0'
    };

    var bo = moment.defineLocale('bo', {
        months : 'ཟླ་བ་དང་པོ_ཟླ་བ་གཉིས་པ_ཟླ་བ་གསུམ་པ_ཟླ་བ་བཞི་པ_ཟླ་བ་ལྔ་པ_ཟླ་བ་དྲུག་པ_ཟླ་བ་བདུན་པ_ཟླ་བ་བརྒྱད་པ_ཟླ་བ་དགུ་པ_ཟླ་བ་བཅུ་པ_ཟླ་བ་བཅུ་གཅིག་པ_ཟླ་བ་བཅུ་གཉིས་པ'.split('_'),
        monthsShort : 'ཟླ་བ་དང་པོ_ཟླ་བ་གཉིས་པ_ཟླ་བ་གསུམ་པ_ཟླ་བ་བཞི་པ_ཟླ་བ་ལྔ་པ_ཟླ་བ་དྲུག་པ_ཟླ་བ་བདུན་པ_ཟླ་བ་བརྒྱད་པ_ཟླ་བ་དགུ་པ_ཟླ་བ་བཅུ་པ_ཟླ་བ་བཅུ་གཅིག་པ_ཟླ་བ་བཅུ་གཉིས་པ'.split('_'),
        weekdays : 'གཟའ་ཉི་མ་_གཟའ་ཟླ་བ་_གཟའ་མིག་དམར་_གཟའ་ལྷག་པ་_གཟའ་ཕུར་བུ_གཟའ་པ་སངས་_གཟའ་སྤེན་པ་'.split('_'),
        weekdaysShort : 'ཉི་མ་_ཟླ་བ་_མིག་དམར་_ལྷག་པ་_ཕུར་བུ_པ་སངས་_སྤེན་པ་'.split('_'),
        weekdaysMin : 'ཉི་མ་_ཟླ་བ་_མིག་དམར་_ལྷག་པ་_ཕུར་བུ_པ་སངས་_སྤེན་པ་'.split('_'),
        longDateFormat : {
            LT : 'A h:mm',
            LTS : 'A h:mm:ss',
            L : 'DD/MM/YYYY',
            LL : 'D MMMM YYYY',
            LLL : 'D MMMM YYYY, A h:mm',
            LLLL : 'dddd, D MMMM YYYY, A h:mm'
        },
        calendar : {
            sameDay : '[དི་རིང] LT',
            nextDay : '[སང་ཉིན] LT',
            nextWeek : '[བདུན་ཕྲག་རྗེས་མ], LT',
            lastDay : '[ཁ་སང] LT',
            lastWeek : '[བདུན་ཕྲག་མཐའ་མ] dddd, LT',
            sameElse : 'L'
        },
        relativeTime : {
            future : '%s ལ་',
            past : '%s སྔན་ལ',
            s : 'ལམ་སང',
            m : 'སྐར་མ་གཅིག',
            mm : '%d སྐར་མ',
            h : 'ཆུ་ཚོད་གཅིག',
            hh : '%d ཆུ་ཚོད',
            d : 'ཉིན་གཅིག',
            dd : '%d ཉིན་',
            M : 'ཟླ་བ་གཅིག',
            MM : '%d ཟླ་བ',
            y : 'ལོ་གཅིག',
            yy : '%d ལོ'
        },
        preparse: function (string) {
            return string.replace(/[༡༢༣༤༥༦༧༨༩༠]/g, function (match) {
                return numberMap[match];
            });
        },
        postformat: function (string) {
            return string.replace(/\d/g, function (match) {
                return symbolMap[match];
            });
        },
        meridiemParse: /མཚན་མོ|ཞོགས་ཀས|ཉིན་གུང|དགོང་དག|མཚན་མོ/,
        meridiemHour : function (hour, meridiem) {
            if (hour === 12) {
                hour = 0;
            }
            if ((meridiem === 'མཚན་མོ' && hour >= 4) ||
                    (meridiem === 'ཉིན་གུང' && hour < 5) ||
                    meridiem === 'དགོང་དག') {
                return hour + 12;
            } else {
                return hour;
            }
        },
        meridiem : function (hour, minute, isLower) {
            if (hour < 4) {
                return 'མཚན་མོ';
            } else if (hour < 10) {
                return 'ཞོགས་ཀས';
            } else if (hour < 17) {
                return 'ཉིན་གུང';
            } else if (hour < 20) {
                return 'དགོང་དག';
            } else {
                return 'མཚན་མོ';
            }
        },
        week : {
            dow : 0, // Sunday is the first day of the week.
            doy : 6  // The week that contains Jan 1st is the first week of the year.
        }
    });

    return bo;

}));


import stream from 'stream';
import zlib from 'zlib';
import util from 'util';

import { UploadFS } from 'meteor/jalik:ufs';

import { Logger } from '../../../logger';
import { FileUploadClass, FileUpload } from '../lib/FileUpload';

const logger = new Logger('FileUpload');

function ExtractRange(options) {
	if (!(this instanceof ExtractRange)) {
		return new ExtractRange(options);
	}

	this.start = options.start;
	this.stop = options.stop;
	this.bytes_read = 0;

	stream.Transform.call(this, options);
}
util.inherits(ExtractRange, stream.Transform);

ExtractRange.prototype._transform = function(chunk, enc, cb) {
	if (this.bytes_read > this.stop) {
		// done reading
		this.end();
	} else if (this.bytes_read + chunk.length < this.start) {
		// this chunk is still before the start byte
	} else {
		let start;
		let stop;

		if (this.start <= this.bytes_read) {
			start = 0;
		} else {
			start = this.start - this.bytes_read;
		}
		if ((this.stop - this.bytes_read + 1) < chunk.length) {
			stop = this.stop - this.bytes_read + 1;
		} else {
			stop = chunk.length;
		}
		const newchunk = chunk.slice(start, stop);
		this.push(newchunk);
	}
	this.bytes_read += chunk.length;
	cb();
};

const getByteRange = function(header) {
	if (header) {
		const matches = header.match(/(\d+)-(\d+)/);
		if (matches) {
			return {
				start: parseInt(matches[1], 10),
				stop: parseInt(matches[2], 10),
			};
		}
	}
	return null;
};

// code from: https://github.com/jalik/jalik-ufs/blob/master/ufs-server.js#L310
const readFromGridFS = function(storeName, fileId, file, req, res) {
	const store = UploadFS.getStore(storeName);
	const rs = store.getReadStream(fileId, file);
	const ws = new stream.PassThrough();

	[rs, ws].forEach((stream) => stream.on('error', function(err) {
		store.onReadError.call(store, err, fileId, file);
		res.end();
	}));

	ws.on('close', function() {
		// Close output stream at the end
		ws.emit('end');
	});

	const accept = req.headers['accept-encoding'] || '';

	// Transform stream
	store.transformRead(rs, ws, fileId, file, req);
	const range = getByteRange(req.headers.range);
	let out_of_range = false;
	if (range) {
		out_of_range = (range.start > file.size) || (range.stop <= range.start) || (range.stop > file.size);
	}

	// Compress data using gzip
	if (accept.match(/\bgzip\b/) && range === null) {
		res.setHeader('Content-Encoding', 'gzip');
		res.removeHeader('Content-Length');
		res.writeHead(200);
		ws.pipe(zlib.createGzip()).pipe(res);
	} else if (accept.match(/\bdeflate\b/) && range === null) {
		// Compress data using deflate
		res.setHeader('Content-Encoding', 'deflate');
		res.removeHeader('Content-Length');
		res.writeHead(200);
		ws.pipe(zlib.createDeflate()).pipe(res);
	} else if (range && out_of_range) {
		// out of range request, return 416
		res.removeHeader('Content-Length');
		res.removeHeader('Content-Type');
		res.removeHeader('Content-Disposition');
		res.removeHeader('Last-Modified');
		res.setHeader('Content-Range', `bytes */${ file.size }`);
		res.writeHead(416);
		res.end();
	} else if (range) {
		res.setHeader('Content-Range', `bytes ${ range.start }-${ range.stop }/${ file.size }`);
		res.removeHeader('Content-Length');
		res.setHeader('Content-Length', range.stop - range.start + 1);
		res.writeHead(206);
		logger.debug('File upload extracting range');
		ws.pipe(new ExtractRange({ start: range.start, stop: range.stop })).pipe(res);
	} else {
		res.writeHead(200);
		ws.pipe(res);
	}
};

const copyFromGridFS = function(storeName, fileId, file, out) {
	const store = UploadFS.getStore(storeName);
	const rs = store.getReadStream(fileId, file);

	[rs, out].forEach((stream) => stream.on('error', function(err) {
		store.onReadError.call(store, err, fileId, file);
		out.end();
	}));

	rs.pipe(out);
};

FileUpload.configureUploadsStore('GridFS', 'GridFS:Uploads', {
	collectionName: 'rocketchat_uploads',
});

FileUpload.configureUploadsStore('GridFS', 'GridFS:UserDataFiles', {
	collectionName: 'rocketchat_userDataFiles',
});

// DEPRECATED: backwards compatibility (remove)
UploadFS.getStores().rocketchat_uploads = UploadFS.getStores()['GridFS:Uploads'];

FileUpload.configureUploadsStore('GridFS', 'GridFS:Avatars', {
	collectionName: 'rocketchat_avatars',
});

new FileUploadClass({
	name: 'GridFS:Uploads',

	get(file, req, res) {
		file = FileUpload.addExtensionTo(file);

		res.setHeader('Content-Disposition', `attachment; filename*=UTF-8''${ encodeURIComponent(file.name) }`);
		res.setHeader('Last-Modified', file.uploadedAt.toUTCString());
		res.setHeader('Content-Type', file.type || 'application/octet-stream');
		res.setHeader('Content-Length', file.size);

		return readFromGridFS(file.store, file._id, file, req, res);
	},

	copy(file, out) {
		copyFromGridFS(file.store, file._id, file, out);
	},
});

new FileUploadClass({
	name: 'GridFS:UserDataFiles',

	get(file, req, res) {
		file = FileUpload.addExtensionTo(file);

		res.setHeader('Content-Disposition', `attachment; filename*=UTF-8''${ encodeURIComponent(file.name) }`);
		res.setHeader('Last-Modified', file.uploadedAt.toUTCString());
		res.setHeader('Content-Type', file.type);
		res.setHeader('Content-Length', file.size);

		return readFromGridFS(file.store, file._id, file, req, res);
	},

	copy(file, out) {
		copyFromGridFS(file.store, file._id, file, out);
	},
});

new FileUploadClass({
	name: 'GridFS:Avatars',

	get(file, req, res) {
		file = FileUpload.addExtensionTo(file);

		return readFromGridFS(file.store, file._id, file, req, res);
	},

	copy(file, out) {
		copyFromGridFS(file.store, file._id, file, out);
	},
});


import './startup';
import './methods/saveRoomSettings';

export { saveRoomTopic } from './functions/saveRoomTopic';
export { saveStreamingOptions } from './functions/saveStreamingOptions';
export { saveRoomName } from './functions/saveRoomName';