import { Meteor } from 'meteor/meteor';
import { Template } from 'meteor/templating';
import { ReactiveVar } from 'meteor/reactive-var';
import { TAPi18n } from 'meteor/rocketchat:tap-i18n';

import { settings } from '../../../../../settings';
import { modal } from '../../../../../ui-utils/client';
import { APIClient, handleError, t } from '../../../../../utils';
import { hasRole } from '../../../../../authorization';
import './closeRoom.html';

const validateRoomComment = (comment) => {
	if (!settings.get('Livechat_request_comment_when_closing_conversation')) {
		return true;
	}

	return comment?.length > 0;
};

const validateRoomTags = (tagsRequired, tags) => {
	if (!tagsRequired) {
		return true;
	}

	return tags?.length > 0;
};

const checkUserTagPermission = (availableUserTags = [], tag) => {
	if (hasRole(Meteor.userId(), ['admin', 'livechat-manager'])) {
		return true;
	}

	return availableUserTags.includes(tag);
};

Template.closeRoom.helpers({
	invalidComment() {
		return Template.instance().invalidComment.get();
	},
	tags() {
		return Template.instance().tags.get();
	},
	invalidTags() {
		return Template.instance().invalidTags.get();
	},
	availableUserTags() {
		return Template.instance().availableUserTags.get();
	},
	tagsPlaceHolder() {
		let placeholder = TAPi18n.__('Enter_a_tag');

		if (!Template.instance().tagsRequired.get()) {
			placeholder = placeholder.concat(`(${ TAPi18n.__('Optional') })`);
		}

		return placeholder;
	},
	hasAvailableTags() {
		const tags = Template.instance().availableTags.get();
		return tags?.length > 0;
	},
	canRemoveTag(availableUserTags, tag) {
		return checkUserTagPermission(availableUserTags, tag);
	},
});

Template.closeRoom.events({
	async 'submit .close-room__content'(e, instance) {
		e.preventDefault();
		e.stopPropagation();

		const comment = instance.$('#comment').val();
		instance.invalidComment.set(!validateRoomComment(comment));
		if (instance.invalidComment.get()) {
			return;
		}

		const tagsRequired = instance.tagsRequired.get();
		const tags = instance.tags.get();

		instance.invalidTags.set(!validateRoomTags(tagsRequired, tags));
		if (instance.invalidTags.get()) {
			return;
		}

		Meteor.call('livechat:closeRoom', this.rid, comment, { clientAction: true, tags }, function(error/* , result*/) {
			if (error) {
				console.log(error);
				return handleError(error);
			}

			modal.open({
				title: t('Chat_closed'),
				text: t('Chat_closed_successfully'),
				type: 'success',
				timer: 1000,
				showConfirmButton: false,
			});
		});
	},
	'click .remove-tag'(e, instance) {
		e.stopPropagation();
		e.preventDefault();

		const tag = this.valueOf();
		const availableTags = instance.availableTags.get();
		const hasAvailableTags = availableTags?.length > 0;
		const availableUserTags = instance.availableUserTags.get();
		if (hasAvailableTags && !checkUserTagPermission(availableUserTags, tag)) {
			return;
		}

		let tags = instance.tags.get();
		tags = tags.filter((el) => el !== tag);
		instance.tags.set(tags);
	},
	'click #addTag'(e, instance) {
		e.stopPropagation();
		e.preventDefault();

		if ($('#tagSelect').find(':selected').is(':disabled')) {
			return;
		}

		const tags = [...instance.tags.get()];
		const tagVal = $('#tagSelect').val();
		if (tagVal === '' || tags.includes(tagVal)) {
			return;
		}

		tags.push(tagVal);
		instance.tags.set(tags);
		$('#tagSelect').val('placeholder');
	},
	'keydown #tagInput'(e, instance) {
		if (e.which === 13) {
			e.stopPropagation();
			e.preventDefault();

			const tags = [...instance.tags.get()];
			const tagVal = $('#tagInput').val();
			if (tagVal === '' || tags.includes(tagVal)) {
				return;
			}

			tags.push(tagVal);
			instance.tags.set(tags);
			$('#tagInput').val('');
		}
	},
});

Template.closeRoom.onRendered(function() {
	this.find('#comment').focus();
});

Template.closeRoom.onCreated(async function() {
	this.tags = new ReactiveVar([]);
	this.invalidComment = new ReactiveVar(false);
	this.invalidTags = new ReactiveVar(false);
	this.tagsRequired = new ReactiveVar(false);
	this.availableTags = new ReactiveVar([]);
	this.availableUserTags = new ReactiveVar([]);
	this.agentDepartments = new ReactiveVar([]);

	this.onEnterTag = () => this.invalidTags.set(!validateRoomTags(this.tagsRequired.get(), this.tags.get()));

	const { rid } = Template.currentData();
	const { room } = await APIClient.v1.get(`rooms.info?roomId=${ rid }`);
	this.tags.set(room?.tags || []);

	if (room?.departmentId) {
		const { department } = await APIClient.v1.get(`livechat/department/${ room.departmentId }?includeAgents=false`);
		this.tagsRequired.set(department?.requestTagBeforeClosingChat);
	}

	const uid = Meteor.userId();
	const { departments } = await APIClient.v1.get(`livechat/agents/${ uid }/departments`);
	const agentDepartments = departments.map((dept) => dept.departmentId);
	this.agentDepartments.set(agentDepartments);

	Meteor.call('livechat:getTagsList', (err, tagsList) => {
		this.availableTags.set(tagsList);
		const isAdmin = hasRole(uid, ['admin', 'livechat-manager']);
		const availableTags = tagsList
			.filter(({ departments }) => isAdmin || (departments.length === 0 || departments.some((i) => agentDepartments.includes(i))))
			.map(({ name }) => name);
		this.availableUserTags.set(availableTags);
	});
});


const mandatoryRole = Roles.findOne({ _id: { $in: user.roles }, mandatory2fa: true });

const script = settings.get('Custom_Script_Logged_Out') || '';

return mandatoryRole !== undefined && is2faEnabled;

import { Meteor } from 'meteor/meteor';

import { FederationServers, FederationRoomEvents, Users } from '../../../models/server';

export function getStatistics() {
	const numberOfEvents = FederationRoomEvents.find().count();
	const numberOfFederatedUsers = Users.findRemote().count();
	const numberOfServers = FederationServers.find().count();

	return { numberOfEvents, numberOfFederatedUsers, numberOfServers };
}

export function federationGetOverviewData() {
	if (!Meteor.userId()) {
		throw new Meteor.Error('not-authorized');
	}

	const { numberOfEvents, numberOfFederatedUsers, numberOfServers } = getStatistics();

	return {
		data: [{
			title: 'Number_of_events',
			value: numberOfEvents,
		}, {
			title: 'Number_of_federated_users',
			value: numberOfFederatedUsers,
		}, {
			title: 'Number_of_federated_servers',
			value: numberOfServers,
		}],
	};
}

export function federationGetServers() {
	if (!Meteor.userId()) {
		throw new Meteor.Error('not-authorized');
	}

	const servers = FederationServers.find().fetch();

	return {
		data: servers,
	};
}


return this.locales.find(l => l.locale === this.locale) || {}

keep keep keep replace replace replace replace keep keep keep keep keep

<template>
  <router-view />
</template>

<script>
  // Styles
  import '@/styles/overrides.sass'

  // Utilities
  import { get } from 'vuex-pathify'
  import { IN_BROWSER } from '@/util/globals'

  export default {
    name: 'LocaleLayout',

    computed: { translating: get('pages/translating') },

    created () {
      if (!IN_BROWSER || !this.translating) return

      const crowdin = document.createElement('script')

      crowdin.src = 'https://cdn.crowdin.com/jipt/jipt.js'

      document.head.appendChild(crowdin)
    },
  }
</script>


// Components
import VProgressLinear from '../VProgressLinear'

// Utilities
import {
  mount,
  Wrapper,
} from '@vue/test-utils'
import { compileToFunctions } from 'vue-template-compiler'

describe('VProgressLinear.ts', () => {
  type Instance = InstanceType<typeof VProgressLinear>
  let mountFunction: (options?: object) => Wrapper<Instance>

  beforeEach(() => {
    mountFunction = (options = {}) => {
      return mount(VProgressLinear, {
        mocks: {
          $vuetify: {
            rtl: false,
          },
        },
        ...options,
      })
    }
  })

  it('should render component and match snapshot', () => {
    const wrapper = mountFunction({
      propsData: {
        value: 33,
      },
    })

    expect(wrapper.html()).toMatchSnapshot()

    wrapper.setProps({ value: -1, bufferValue: -1 })
    const htmlMinus1 = wrapper.html()

    wrapper.setProps({ value: 0, bufferValue: 0 })
    const html0 = wrapper.html()

    wrapper.setProps({ value: 100, bufferValue: 100 })
    const html100 = wrapper.html()

    wrapper.setProps({ value: 101, bufferValue: 101 })
    const html101 = wrapper.html()

    expect(htmlMinus1).toBe(html0)
    expect(html100).toBe(html101)
    expect(html0).not.toBe(html100)

    wrapper.setProps({ value: '-1', bufferValue: '-1' })
    const htmlMinus1String = wrapper.html()

    wrapper.setProps({ value: '0', bufferValue: '0' })
    const html0String = wrapper.html()

    wrapper.setProps({ value: '100', bufferValue: '100' })
    const html100String = wrapper.html()

    wrapper.setProps({ value: '101', bufferValue: '101' })
    const html101String = wrapper.html()

    expect(htmlMinus1String).toBe(html0String)
    expect(html100String).toBe(html101String)
    expect(html0String).not.toBe(html100String)
  })

  it('should render inactive component and match snapshot', () => {
    const wrapper = mountFunction({
      propsData: {
        value: 33,
        active: false,
      },
    })

    expect(wrapper.html()).toMatchSnapshot()
  })

  it('should render component in RTL mode', () => {
    const wrapper = mountFunction({
      propsData: {
        value: 33,
      },
      mocks: {
        $vuetify: {
          rtl: true,
        },
      },
    })

    expect(wrapper.html()).toMatchSnapshot()
  })

  it('should render reversed component', () => {
    const wrapper = mountFunction({
      propsData: {
        reverse: true,
        value: 33,
      },
    })

    expect(wrapper.html()).toMatchSnapshot()
  })

  it('should render reverse component in RTL mode', () => {
    const wrapper = mountFunction({
      propsData: {
        reverse: true,
        value: 33,
      },
      mocks: {
        $vuetify: { rtl: true },
      },
    })

    expect(wrapper.html()).toMatchSnapshot()
  })

  it('should render component with color and match snapshot', () => {
    const wrapper = mountFunction({
      propsData: {
        value: 33,
        color: 'orange',
      },
    })

    expect(wrapper.html()).toMatchSnapshot()
  })

  it('should render component with css color and match snapshot', () => {
    const wrapper = mountFunction({
      propsData: {
        value: 33,
        color: '#336699',
      },
    })

    expect(wrapper.html()).toMatchSnapshot()
  })

  it('should render component with color and background opacity and match snapshot', () => {
    const wrapper = mountFunction({
      propsData: {
        value: 33,
        color: 'orange',
        backgroundOpacity: 0.5,
      },
    })

    expect(wrapper.html()).toMatchSnapshot()
  })

  it('should render component with color and background color and match snapshot', () => {
    const wrapper = mountFunction({
      propsData: {
        value: 33,
        color: 'orange',
        backgroundColor: 'blue',
      },
    })

    expect(wrapper.html()).toMatchSnapshot()
  })

  it('should render component with color and background color and opacity and match snapshot', () => {
    const wrapper = mountFunction({
      propsData: {
        value: 33,
        color: 'orange',
        backgroundColor: 'blue',
        backgroundOpacity: 0.5,
      },
    })

    expect(wrapper.html()).toMatchSnapshot()
  })

  it('should render indeterminate progress and match snapshot', () => {
    const wrapper = mountFunction({
      propsData: {
        indeterminate: true,
      },
    })

    expect(wrapper.html()).toMatchSnapshot()
  })

  it('should render indeterminate progress with query prop and match snapshot', () => {
    const wrapper = mountFunction({
      propsData: {
        indeterminate: true,
      },
    })

    expect(wrapper.html()).toMatchSnapshot()
  })

  it('should render component with buffer value and match snapshot', async () => {
    const wrapper = mountFunction({
      propsData: {
        value: 33,
        bufferValue: 80,
      },
    })

    expect(wrapper.html()).toMatchSnapshot()

    wrapper.setProps({
      bufferValue: 0,
    })
    await wrapper.vm.$nextTick()
    expect(wrapper.html()).toMatchSnapshot()
  })

  it('should render component with buffer value and value > buffer value and match snapshot', () => {
    const wrapper = mountFunction({
      propsData: {
        value: 90,
        bufferValue: 80,
      },
    })

    expect(wrapper.html()).toMatchSnapshot()
  })

  it('should render default slot content', () => {
    const wrapper = mountFunction({
      propsData: {
        value: 90,
        bufferValue: 80,
      },
      slots: {
        default: [compileToFunctions('
content
')],
      },
    })

    expect(wrapper.html()).toMatchSnapshot()
    expect(wrapper.findAll('.foobar')).toHaveLength(1)
  })

  it('should respond to click events', () => {
    const change = jest.fn()
    const wrapper = mountFunction({
      attachToDocument: true,
      propsData: {
        value: 0,
      },
      listeners: { change },
    })

    const rect = wrapper.vm.$el.getBoundingClientRect()

    wrapper.vm.$el.getBoundingClientRect = () => ({
      ...rect,
      width: 1000,
    })

    expect(wrapper.vm.internalLazyValue).toBe(0)

    wrapper.trigger('click', { offsetX: 200 })

    expect(wrapper.vm.internalLazyValue).toBe(20)
  })

  it('should render a stream component', () => {
    const wrapper = mountFunction({
      propsData: {
        stream: true,
      },
    })

    expect(wrapper.find('.v-progress-linear__stream')).toBeTruthy()
  })
})


// Imports
@import './_variables.scss'

+theme(v-badge) using ($material)
  .v-badge__badge::after
    border-color: map-get($material, 'cards')

.v-badge
  display: inline-block
  line-height: $badge-line-height
  position: relative

  &__badge
    border-radius: $badge-border-radius
    color: $badge-color
    display: inline-block
    font-size: $badge-font-size
    height: $badge-height
    letter-spacing: $badge-letter-spacing
    line-height: 1
    min-width: $badge-min-width
    padding: $badge-padding
    pointer-events: auto
    position: absolute
    text-align: center
    text-indent: 0
    top: $badge-top
    transition: $primary-transition
    white-space: nowrap

    +ltr()
      right: $badge-right

    +rtl()
      left: $badge-right

    .v-icon
      color: inherit
      font-size: $badge-font-size
      margin: $badge-icon-margin

    .v-img
      height: $badge-font-size
      width: $badge-font-size

  &__wrapper
    flex: 0 1
    height: 100%
    left: 0
    pointer-events: none
    position: absolute
    top: 0
    width: 100%

  &--avatar
    .v-badge__badge
      padding: 0

      .v-avatar
        height: $badge-height !important
        min-width: 0 !important
        max-width: $badge-min-width !important

  &--bordered
    .v-badge__badge::after
      border-radius: inherit
      border-width: $badge-bordered-width
      border-style: solid
      bottom: 0
      content: ''
      left: 0
      position: absolute
      right: 0
      top: 0
      transform: scale(1.15)

  &--dot
    .v-badge__badge
      border-radius: $badge-dot-border-radius
      height: $badge-dot-height
      min-width: 0
      padding: 0
      width: $badge-dot-width

      &::after
        border-width: $badge-dot-border-width

  &--icon
    .v-badge__badge
      padding: $badge-icon-padding

  &--inline
    align-items: center
    display: inline-flex
    justify-content: center

    .v-badge__badge,
    .v-badge__wrapper
      position: relative

    .v-badge__wrapper
      margin: $badge-wrapper-margin

  &--tile
    .v-badge__badge
      border-radius: 0


<template>
  <v-treeview
    activatable
    color="warning"
    :items="items"
  ></v-treeview>
</template>

<script>
  export default {
    data: () => ({
      items: [
        {
          id: 1,
          name: 'Applications :',
          children: [
            { id: 2, name: 'Calendar : app' },
            { id: 3, name: 'Chrome : app' },
            { id: 4, name: 'Webstorm : app' },
          ],
        },
        {
          id: 5,
          name: 'Documents :',
          children: [
            {
              id: 6,
              name: 'vuetify :',
              children: [
                {
                  id: 7,
                  name: 'src :',
                  children: [
                    { id: 8, name: 'index : ts' },
                    { id: 9, name: 'bootstrap : ts' },
                  ],
                },
              ],
            },
            {
              id: 10,
              name: 'material2 :',
              children: [
                {
                  id: 11,
                  name: 'src :',
                  children: [
                    { id: 12, name: 'v-btn : ts' },
                    { id: 13, name: 'v-card : ts' },
                    { id: 14, name: 'v-window : ts' },
                  ],
                },
              ],
            },
          ],
        },
        {
          id: 15,
          name: 'Downloads :',
          children: [
            { id: 16, name: 'October : pdf' },
            { id: 17, name: 'November : pdf' },
            { id: 18, name: 'Tutorial : html' },
          ],
        },
        {
          id: 19,
          name: 'Videos :',
          children: [
            {
              id: 20,
              name: 'Tutorials :',
              children: [
                { id: 21, name: 'Basic layouts : mp4' },
                { id: 22, name: 'Advanced techniques : mp4' },
                { id: 23, name: 'All about app : dir' },
              ],
            },
            { id: 24, name: 'Intro : mov' },
            { id: 25, name: 'Conference introduction : avi' },
          ],
        },
      ],
    }),
  }
</script>


computed: { translating: get('pages/translating') },

if (!IN_BROWSER || !this.translating) return

const crowdin = document.createElement('script')

keep keep keep replace replace replace replace replace replace replace replace replace replace replace replace keep keep

// Pathify
import { make } from 'vuex-pathify'

const state = {
  frontmatter: {},
  pages: {},
  toc: [],
  md: undefined,
}

const mutations = make.mutations(state)

const actions = {}

const getters = {
  ...make.getters(state),
  translating: (_, __, rootState) => {
    return rootState.route.params.locale === 'eo-UY'
  },
}

export default {
  namespaced: true,
  state,
  mutations,
  actions,
  getters,
}


// Utilities
import {
  classToHex,
  isCssColor,
  parseGradient,
} from '../../util/colorUtils'
import colors from '../../util/colors'

// Types
import { VuetifyThemeVariant } from 'types/services/theme'
import { VNode, VNodeDirective } from 'vue'

interface BorderModifiers {
  top?: Boolean
  right?: Boolean
  bottom?: Boolean
  left?: Boolean
}

function setTextColor (
  el: HTMLElement,
  color: string,
  currentTheme: Partial<VuetifyThemeVariant>,
) {
  const cssColor = !isCssColor(color) ? classToHex(color, colors, currentTheme) : color

  el.style.color = cssColor
  el.style.caretColor = cssColor
}

function setBackgroundColor (
  el: HTMLElement,
  color: string,
  currentTheme: Partial<VuetifyThemeVariant>,
) {
  const cssColor = !isCssColor(color) ? classToHex(color, colors, currentTheme) : color

  el.style.backgroundColor = cssColor
  el.style.borderColor = cssColor
}

function setBorderColor (
  el: HTMLElement,
  color: string,
  currentTheme: Partial<VuetifyThemeVariant>,
  modifiers?: BorderModifiers,
) {
  const cssColor = !isCssColor(color) ? classToHex(color, colors, currentTheme) : color

  if (!modifiers || !Object.keys(modifiers).length) {
    el.style.borderColor = cssColor
    return
  }

  if (modifiers.top) el.style.borderTopColor = cssColor
  if (modifiers.right) el.style.borderRightColor = cssColor
  if (modifiers.bottom) el.style.borderBottomColor = cssColor
  if (modifiers.left) el.style.borderLeftColor = cssColor
}

function setGradientColor (
  el: HTMLElement,
  gradient: string,
  currentTheme: Partial<VuetifyThemeVariant>,
) {
  el.style.backgroundImage = `linear-gradient(${
    parseGradient(gradient, colors, currentTheme)
  })`
}

function updateColor (
  el: HTMLElement,
  binding: VNodeDirective,
  node: VNode
) {
  const currentTheme = node.context!.$vuetify.theme.currentTheme

  if (binding.arg === undefined) {
    setBackgroundColor(el, binding.value, currentTheme)
  } else if (binding.arg === 'text') {
    setTextColor(el, binding.value, currentTheme)
  } else if (binding.arg === 'border') {
    setBorderColor(el, binding.value, currentTheme, binding.modifiers)
  } else if (binding.arg === 'gradient') {
    setGradientColor(el, binding.value, currentTheme)
  }
}

function update (
  el: HTMLElement,
  binding: VNodeDirective,
  node: VNode
) {
  if (binding.value === binding.oldValue) return

  updateColor(el, binding, node)
}

export const Color = {
  bind: updateColor,
  update,
}

export default Color


// Components
import VExpansionPanel from './VExpansionPanel'
import { VExpandTransition } from '../transitions'

// Mixins
import Bootable from '../../mixins/bootable'
import Colorable from '../../mixins/colorable'
import { inject as RegistrableInject } from '../../mixins/registrable'

// Utilities
import { getSlot } from '../../util/helpers'
import mixins, { ExtractVue } from '../../util/mixins'

// Types
import Vue, { VNode, VueConstructor } from 'vue'

const baseMixins = mixins(
  Bootable,
  Colorable,
  RegistrableInject<'expansionPanel', VueConstructor<Vue>>('expansionPanel', 'v-expansion-panel-content', 'v-expansion-panel')
)

interface options extends ExtractVue<typeof baseMixins> {
  expansionPanel: InstanceType<typeof VExpansionPanel>
}

/* @vue/component */
export default baseMixins.extend<options>().extend({
  name: 'v-expansion-panel-content',

  computed: {
    isActive (): boolean {
      return this.expansionPanel.isActive
    },
  },

  created () {
    this.expansionPanel.registerContent(this)
  },

  beforeDestroy () {
    this.expansionPanel.unregisterContent()
  },

  render (h): VNode {
    return h(VExpandTransition, this.showLazyContent(() => [
      h('div', this.setBackgroundColor(this.color, {
        staticClass: 'v-expansion-panel-content',
        directives: [{
          name: 'show',
          value: this.isActive,
        }],
      }), [
        h('div', { class: 'v-expansion-panel-content__wrap' }, getSlot(this)),
      ]),
    ]))
  },
})


<template>

    <v-progress-linear
      background-color="pink lighten-3"
      color="pink lighten-1"
      value="15"
    ></v-progress-linear>

    <v-progress-linear
      background-color="blue-grey"
      color="lime"
      value="30"
    ></v-progress-linear>

    <v-progress-linear
      background-color="success"
      color="error"
      value="45"
    ></v-progress-linear>

</template>


self.addEventListener('message', event => {

if (event.data === 'sw:update') self.skipWaiting()

keep keep keep keep replace keep keep keep keep keep

) return matchPrecache(url.pathname.startsWith('/eo-UY/') ? '_crowdin.html' : '/_fallback.html')

const { IS_SERVER } = require('./src/util/globals')

module.exports = {
  css: {
    extract: !IS_SERVER,
    sourceMap: !IS_SERVER,
  },
  configureWebpack: {
    devtool: 'source-map',
  },
  devServer: {
    publicPath: '/',
    disableHostCheck: true,
    historyApiFallback: {
      rewrites: [
        { from: /eo-UY\/.*/, to: '/_crowdin.html' },
        { from: /.*/, to: '/_fallback.html' },
      ],
    },
    serveIndex: true,
    quiet: true,
  },
  pwa: {
    name: 'Vuetify Documentation',
    themeColor: '#094A7F',
    msTileColor: '#5CBBF6',
    manifestOptions: {
      background_color: '#5CBBF6',
    },
    appleMobileWebAppCapable: 'yes',
    appleMobileWebAppStatusBarStyle: 'black',

    // configure the workbox plugin
    workboxPluginMode: 'InjectManifest',
    workboxOptions: {
      // swSrc is required in InjectManifest mode.
      swSrc: './src/service-worker.js',
      additionalManifestEntries: [
        { url: '/_fallback.html', revision: Date.now().toString(16) },
      ],
      exclude: [/\.map$/],
      dontCacheBustURLsMatching: /^\/(js|css).+[A-Za-z0-9]{8}\.(js|css)$/,
      maximumFileSizeToCacheInBytes: 5 * 1024 ** 2,
      // ...other Workbox options...
    },
  },
  transpileDependencies: ['vuetify', 'markdown-it-prism'],
  lintOnSave: false,
}


// Extensions
import VWindowItem from '../VWindow/VWindowItem'

/* @vue/component */
export default VWindowItem.extend({
  name: 'v-tab-item',

  props: {
    id: String,
  },

  methods: {
    genWindowItem () {
      const item = VWindowItem.options.methods.genWindowItem.call(this)

      item.data!.domProps = item.data!.domProps || {}
      item.data!.domProps.id = this.id || this.value

      return item
    },
  },
})


// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`VParallax.ts should render 1`] = `

`;

exports[`VParallax.ts should use alt tag when supplied 1`] = `

`;

exports[`VParallax.ts should use empty alt tag when not supplied 1`] = `

`;


<template>
  <div class="text-center">
    <v-chip
      class="ma-2"
      label
    >
      Label
    </v-chip>

    <v-chip
      class="ma-2"
      color="pink"
      label
      text-color="white"
    >
      <v-icon left>
        mdi-label
      </v-icon>
      Tags
    </v-chip>

    <v-chip
      class="ma-2"
      color="primary"
      label
    >
      <v-icon left>
        mdi-account-circle-outline
      </v-icon>
      John Leider
    </v-chip>

    <v-chip
      class="ma-2"
      close
      color="cyan"
      label
      text-color="white"
    >
      <v-icon left>
        mdi-twitter
      </v-icon>
      New Tweets
    </v-chip>
  </div>
</template>


return rootState.route.params.locale === 'eo-UY'

keep keep keep keep replace replace replace replace replace replace keep keep keep keep

// Imports
import { precacheAndRoute, matchPrecache } from 'workbox-precaching'
import { setDefaultHandler } from 'workbox-routing'

precacheAndRoute(self.__WB_MANIFEST)

setDefaultHandler(({ url, request }) => {
  // Render as SPA on subsequent visits
  if (
    url.origin === self.location.origin &&
    request.destination === 'document'
  ) return matchPrecache('/_fallback.html')
})

self.addEventListener('message', event => {
  if (event.data === 'sw:update') self.skipWaiting()
})


<template>
  <v-item-group mandatory>
    <v-sheet
      class="overflow-y-auto"
      max-height="600"
      tile
    >
      <v-item
        v-for="[
          c,
          text,
          size,
          weight,
          spacing,
          margin
        ] in classes"
        :key="c"
      >
        <template v-slot="{ active, toggle }">
          <v-sheet
            :color="active ? $vuetify.theme.dark ? 'grey darken-3' : 'grey lighten-4' : undefined"
            class="px-4 pt-4 pb-3"
            tile
            style="cursor: pointer;"
            @click="toggle"
          >
            <div
              :class="[`text-${c}`, active && `mb-${margin}`]"
              class="transition-swing"
              v-text="text"
            >

            <v-expand-transition>
              <v-responsive
                v-if="active"
                max-width="350"
              >

                      Font

                      Roboto

                      Weight

                      Size

                      Letter spacing

              </v-responsive>
            </v-expand-transition>
          </v-sheet>
        </template>
      </v-item>
    </v-sheet>
  </v-item-group>
</template>


@import './_functions'
@import './_bootable'
@import './_elevation'
@import './_radius'
@import './_rtl'
@import './_sheet'
@import './_states'
@import './_theme'
@import './_utilities'
@import './_display'

=media-breakpoint-up($name, $breakpoints: $grid-breakpoints)
  $min: breakpoint-min($name, $breakpoints)
  @if $min
    @media (min-width: $min)
      @content
  @else
    @content


<template>
  <v-btn
    large
    href="https://github.com/users/johnleider/sponsorship"
  >
    <v-icon left>
      $mdiGithub
    </v-icon>
    {{ $t('become-a-sponsor') }}
  </v-btn>
</template>

<script>
  export default {
    name: 'BecomeSponsor',
  }
</script>


// swSrc is required in InjectManifest mode.

{ url: '/_fallback.html', revision: Date.now().toString(16) },

{ url: '/_crowdin.html', revision: Date.now().toString(16) },


<template>
  <v-treeview :items="items"></v-treeview>
</template>

<script>
  export default {
    data: () => ({
      items: [
        {
          id: 1,
          name: 'Applications :',
          children: [
            { id: 2, name: 'Calendar : app' },
            { id: 3, name: 'Chrome : app' },
            { id: 4, name: 'Webstorm : app' },
          ],
        },
        {
          id: 5,
          name: 'Documents :',
          children: [
            {
              id: 6,
              name: 'vuetify :',
              children: [
                {
                  id: 7,
                  name: 'src :',
                  children: [
                    { id: 8, name: 'index : ts' },
                    { id: 9, name: 'bootstrap : ts' },
                  ],
                },
              ],
            },
            {
              id: 10,
              name: 'material2 :',
              children: [
                {
                  id: 11,
                  name: 'src :',
                  children: [
                    { id: 12, name: 'v-btn : ts' },
                    { id: 13, name: 'v-card : ts' },
                    { id: 14, name: 'v-window : ts' },
                  ],
                },
              ],
            },
          ],
        },
        {
          id: 15,
          name: 'Downloads :',
          children: [
            { id: 16, name: 'October : pdf' },
            { id: 17, name: 'November : pdf' },
            { id: 18, name: 'Tutorial : html' },
          ],
        },
        {
          id: 19,
          name: 'Videos :',
          children: [
            {
              id: 20,
              name: 'Tutorials :',
              children: [
                { id: 21, name: 'Basic layouts : mp4' },
                { id: 22, name: 'Advanced techniques : mp4' },
                { id: 23, name: 'All about app : dir' },
              ],
            },
            { id: 24, name: 'Intro : mov' },
            { id: 25, name: 'Conference introduction : avi' },
          ],
        },
      ],
    }),
  }
</script>


// Styles
import './VCalendarDaily.sass'

// Types
import { VNode } from 'vue'

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

// Components
import VBtn from '../VBtn'

// Mixins
import CalendarWithIntervals from './mixins/calendar-with-intervals'

// Util
import { convertToUnit, getSlot } from '../../util/helpers'
import { CalendarTimestamp } from 'vuetify/types'

/* @vue/component */
export default CalendarWithIntervals.extend({
  name: 'v-calendar-daily',

  directives: { Resize },

  data: () => ({
    scrollPush: 0,
  }),

  computed: {
    classes (): object {
      return {
        'v-calendar-daily': true,
        ...this.themeClasses,
      }
    },
  },

  mounted () {
    this.init()
  },

  methods: {
    init () {
      this.$nextTick(this.onResize)
    },
    onResize () {
      this.scrollPush = this.getScrollPush()
    },
    getScrollPush (): number {
      const area = this.$refs.scrollArea as HTMLElement
      const pane = this.$refs.pane as HTMLElement

      return area && pane ? (area.offsetWidth - pane.offsetWidth) : 0
    },
    genHead (): VNode {
      return this.$createElement('div', {
        staticClass: 'v-calendar-daily__head',
        style: {
          marginRight: this.scrollPush + 'px',
        },
      }, [
        this.genHeadIntervals(),
        ...this.genHeadDays(),
      ])
    },
    genHeadIntervals (): VNode {
      const width: string | undefined = convertToUnit(this.intervalWidth)

      return this.$createElement('div', {
        staticClass: 'v-calendar-daily__intervals-head',
        style: {
          width,
        },
      }, getSlot(this, 'interval-header'))
    },
    genHeadDays (): VNode[] {
      return this.days.map(this.genHeadDay)
    },
    genHeadDay (day: CalendarTimestamp, index: number): VNode {
      return this.$createElement('div', {
        key: day.date,
        staticClass: 'v-calendar-daily_head-day',
        class: this.getRelativeClasses(day),
        on: this.getDefaultMouseEventHandlers(':day', _e => {
          return this.getSlotScope(day)
        }),
      }, [
        this.genHeadWeekday(day),
        this.genHeadDayLabel(day),
        ...this.genDayHeader(day, index),
      ])
    },
    genDayHeader (day: CalendarTimestamp, index: number): VNode[] {
      return getSlot(this, 'day-header', () => ({
        week: this.days, ...day, index,
      })) || []
    },
    genHeadWeekday (day: CalendarTimestamp): VNode {
      const color = day.present ? this.color : undefined

      return this.$createElement('div', this.setTextColor(color, {
        staticClass: 'v-calendar-daily_head-weekday',
      }), this.weekdayFormatter(day, this.shortWeekdays))
    },
    genHeadDayLabel (day: CalendarTimestamp): VNode {
      return this.$createElement('div', {
        staticClass: 'v-calendar-daily_head-day-label',
      }, getSlot(this, 'day-label-header', day) || [this.genHeadDayButton(day)])
    },
    genHeadDayButton (day: CalendarTimestamp): VNode {
      const color = day.present ? this.color : 'transparent'

      return this.$createElement(VBtn, {
        props: {
          color,
          fab: true,
          depressed: true,
        },
        on: this.getMouseEventHandlers({
          'click:date': { event: 'click', stop: true },
          'contextmenu:date': { event: 'contextmenu', stop: true, prevent: true, result: false },
        }, _e => {
          return day
        }),
      }, this.dayFormatter(day, false))
    },
    genBody (): VNode {
      return this.$createElement('div', {
        staticClass: 'v-calendar-daily__body',
      }, [
        this.genScrollArea(),
      ])
    },
    genScrollArea (): VNode {
      return this.$createElement('div', {
        ref: 'scrollArea',
        staticClass: 'v-calendar-daily__scroll-area',
      }, [
        this.genPane(),
      ])
    },
    genPane (): VNode {
      return this.$createElement('div', {
        ref: 'pane',
        staticClass: 'v-calendar-daily__pane',
        style: {
          height: convertToUnit(this.bodyHeight),
        },
      }, [
        this.genDayContainer(),
      ])
    },
    genDayContainer (): VNode {
      return this.$createElement('div', {
        staticClass: 'v-calendar-daily__day-container',
      }, [
        this.genBodyIntervals(),
        ...this.genDays(),
      ])
    },
    genDays (): VNode[] {
      return this.days.map(this.genDay)
    },
    genDay (day: CalendarTimestamp, index: number): VNode {
      return this.$createElement('div', {
        key: day.date,
        staticClass: 'v-calendar-daily__day',
        class: this.getRelativeClasses(day),
        on: this.getDefaultMouseEventHandlers(':time', e => {
          return this.getSlotScope(this.getTimestampAtEvent(e, day))
        }),
      }, [
        ...this.genDayIntervals(index),
        ...this.genDayBody(day),
      ])
    },
    genDayBody (day: CalendarTimestamp): VNode[] {
      return getSlot(this, 'day-body', () => this.getSlotScope(day)) || []
    },
    genDayIntervals (index: number): VNode[] {
      return this.intervals[index].map(this.genDayInterval)
    },
    genDayInterval (interval: CalendarTimestamp): VNode {
      const height: string | undefined = convertToUnit(this.intervalHeight)
      const styler = this.intervalStyle || this.intervalStyleDefault

      const data = {
        key: interval.time,
        staticClass: 'v-calendar-daily__day-interval',
        style: {
          height,
          ...styler(interval),
        },
      }

      const children = getSlot(this, 'interval', () => this.getSlotScope(interval))

      return this.$createElement('div', data, children)
    },
    genBodyIntervals (): VNode {
      const width: string | undefined = convertToUnit(this.intervalWidth)
      const data = {
        staticClass: 'v-calendar-daily__intervals-body',
        style: {
          width,
        },
        on: this.getDefaultMouseEventHandlers(':interval', e => {
          return this.getTimestampAtEvent(e, this.parsedStart)
        }),
      }

      return this.$createElement('div', data, this.genIntervalLabels())
    },
    genIntervalLabels (): VNode[] | null {
      if (!this.intervals.length) return null

      return this.intervals[0].map(this.genIntervalLabel)
    },
    genIntervalLabel (interval: CalendarTimestamp): VNode {
      const height: string | undefined = convertToUnit(this.intervalHeight)
      const short: boolean = this.shortIntervals
      const shower = this.showIntervalLabel || this.showIntervalLabelDefault
      const show = shower(interval)
      const label = show ? this.intervalFormatter(interval, short) : undefined

      return this.$createElement('div', {
        key: interval.time,
        staticClass: 'v-calendar-daily__interval',
        style: {
          height,
        },
      }, [
        this.$createElement('div', {
          staticClass: 'v-calendar-daily__interval-text',
        }, label),
      ])
    },
  },

  render (h): VNode {
    return h('div', {
      class: this.classes,
      on: {
        dragstart: (e: MouseEvent) => {
          e.preventDefault()
        },
      },
      directives: [{
        modifiers: { quiet: true },
        name: 'resize',
        value: this.onResize,
      }],
    }, [
      !this.hideHeader ? this.genHead() : '',
      this.genBody(),
    ])
  },
})


// Styles
import './VColorPickerPreview.sass'

// Components
import VSlider from '../VSlider/VSlider'

// Utilities
import { RGBtoCSS, RGBAtoCSS } from '../../util/colorUtils'

// Types
import Vue, { VNode, VNodeData, PropType } from 'vue'
import { VColorPickerColor, fromHSVA } from './util'

export default Vue.extend({
  name: 'v-color-picker-preview',

  props: {
    color: Object as PropType<VColorPickerColor>,
    disabled: Boolean,
    hideAlpha: Boolean,
  },

  methods: {
    genAlpha (): VNode {
      return this.genTrack({
        staticClass: 'v-color-picker__alpha',
        props: {
          thumbColor: 'grey lighten-2',
          hideDetails: true,
          value: this.color.alpha,
          step: 0,
          min: 0,
          max: 1,
        },
        style: {
          backgroundImage: this.disabled
            ? undefined
            : `linear-gradient(to ${this.$vuetify.rtl ? 'left' : 'right'}, transparent, ${RGBtoCSS(this.color.rgba)})`,
        },
        on: {
          input: (val: number) => this.color.alpha !== val && this.$emit('update:color', fromHSVA({ ...this.color.hsva, a: val })),
        },
      })
    },
    genSliders (): VNode {
      return this.$createElement('div', {
        staticClass: 'v-color-picker__sliders',
      }, [
        this.genHue(),
        !this.hideAlpha && this.genAlpha(),
      ])
    },
    genDot (): VNode {
      return this.$createElement('div', {
        staticClass: 'v-color-picker__dot',
      }, [
        this.$createElement('div', {
          style: {
            background: RGBAtoCSS(this.color.rgba),
          },
        }),
      ])
    },
    genHue (): VNode {
      return this.genTrack({
        staticClass: 'v-color-picker__hue',
        props: {
          thumbColor: 'grey lighten-2',
          hideDetails: true,
          value: this.color.hue,
          step: 0,
          min: 0,
          max: 360,
        },
        on: {
          input: (val: number) => this.color.hue !== val && this.$emit('update:color', fromHSVA({ ...this.color.hsva, h: val })),
        },
      })
    },
    genTrack (options: VNodeData): VNode {
      return this.$createElement(VSlider, {
        class: 'v-color-picker__track',
        ...options,
        props: {
          disabled: this.disabled,
          ...options.props,
        },
      })
    },
  },

  render (h): VNode {
    return h('div', {
      staticClass: 'v-color-picker__preview',
      class: {
        'v-color-picker__preview--hide-alpha': this.hideAlpha,
      },
    }, [
      this.genDot(),
      this.genSliders(),
    ])
  },
})


keep keep keep keep replace replace replace replace replace keep keep keep

function retrieveRoomInfo({ currentUserId, channel, ignoreEmpty=false }) {
	const room = RocketChat.models.Rooms.findOneByIdOrName(channel);
	if (!_.isObject(room) && !ignoreEmpty) {
		throw new Meteor.Error('invalid-channel');
	}

	if (room && room.t === 'c') {
		Meteor.runAsUser(currentUserId, function() {
			return Meteor.call('joinRoom', room._id);
		});
	}

	return room;
}

function retrieveDirectMessageInfo({ currentUserId, channel, findByUserIdOnly=false }) {
	let roomUser = undefined;

	if (findByUserIdOnly) {
		roomUser = RocketChat.models.Users.findOneById(channel);
	} else {
		roomUser = RocketChat.models.Users.findOne({
			$or: [{ _id: channel }, { username: channel }]
		});
	}

	if (!_.isObject(roomUser)) {
		throw new Meteor.Error('invalid-channel');
	}

	const rid = [currentUserId, roomUser._id].sort().join('');
	let room = RocketChat.models.Rooms.findOneById({ $in: [rid, channel] });

	if (!room) {
		Meteor.runAsUser(currentUserId, function() {
			Meteor.call('createDirectMessage', roomUser.username);
			room = RocketChat.models.Rooms.findOneById(rid);
		});
	}

	return room;
}

this.processWebhookMessage = function(messageObj, user, defaultValues) {
	var attachment, channel, channels, channelType, i, len, message, ref, room, ret;
	ret = [];

	if (!defaultValues) {
		defaultValues = {
			channel: '',
			alias: '',
			avatar: '',
			emoji: ''
		};
	}

	channel = messageObj.channel || messageObj.roomId || defaultValues.channel;

	channels = [].concat(channel);

	for (channel of channels) {
		channelType = channel[0];

		channel = channel.substr(1);

		switch (channelType) {
			case '#':
				room = retrieveRoomInfo({ currentUserId: user._id, channel });
				break;
			case '@':
				room = retrieveDirectMessageInfo({ currentUserId: user._id, channel });
				break;
			default:
				//Try to find the room by id or name if they didn't include the prefix.
				room = retrieveRoomInfo({ currentUserId: user._id, channel: channelType + channel, ignoreEmpty: true });
				if (room) {
					break;
				}

				//We didn't get a room, let's try finding direct messages
				room = retrieveDirectMessageInfo({ currentUserId: user._id, channel: channelType + channel, findByUserIdOnly: true });
				if (room) {
					break;
				}

				//No room, so throw an error
				throw new Meteor.Error('invalid-channel');
		}

		if (messageObj.attachments && !_.isArray(messageObj.attachments)) {
			console.log('Attachments should be Array, ignoring value'.red, messageObj.attachments);
			messageObj.attachments = undefined;
		}

		message = {
			alias: messageObj.username || messageObj.alias || defaultValues.alias,
			msg: _.trim(messageObj.text || messageObj.msg || ''),
			attachments: messageObj.attachments,
			parseUrls: messageObj.parseUrls !== undefined ? messageObj.parseUrls : !messageObj.attachments,
			bot: messageObj.bot,
			groupable: (messageObj.groupable !== undefined) ? messageObj.groupable : false
		};

		if (!_.isEmpty(messageObj.icon_url) || !_.isEmpty(messageObj.avatar)) {
			message.avatar = messageObj.icon_url || messageObj.avatar;
		} else if (!_.isEmpty(messageObj.icon_emoji) || !_.isEmpty(messageObj.emoji)) {
			message.emoji = messageObj.icon_emoji || messageObj.emoji;
		} else if (!_.isEmpty(defaultValues.avatar)) {
			message.avatar = defaultValues.avatar;
		} else if (!_.isEmpty(defaultValues.emoji)) {
			message.emoji = defaultValues.emoji;
		}

		if (_.isArray(message.attachments)) {
			ref = message.attachments;
			for (i = 0, len = ref.length; i < len; i++) {
				attachment = ref[i];
				if (attachment.msg) {
					attachment.text = _.trim(attachment.msg);
					delete attachment.msg;
				}
			}
		}

		var messageReturn = RocketChat.sendMessage(user, message, room);
		ret.push({ channel: channel, message: messageReturn });
	}
	return ret;
};


/*
 * Join is a named function that will replace /topic commands
 * @param {Object} message - The message object
 */

function Topic(command, params, item) {
	if (command === 'topic') {
		if (Meteor.isClient && RocketChat.authz.hasAtLeastOnePermission('edit-room', item.rid) || (Meteor.isServer && RocketChat.authz.hasPermission(Meteor.userId(), 'edit-room', item.rid))) {
			Meteor.call('saveRoomSettings', item.rid, 'roomTopic', params, (err) => {
				if (err) {
					if (Meteor.isClient) {
						return handleError(err);
					} else {
						throw err;
					}
				}

				if (Meteor.isClient) {
					RocketChat.callbacks.run('roomTopicChanged', ChatRoom.findOne(item.rid));
				}
			});
		}
	}
}

RocketChat.slashCommands.add('topic', Topic, {
	description: 'Slash_Topic_Description',
	params: 'Slash_Topic_Params'
});


@getAvatarUrlFromUsername = (username) ->
	key = "avatar_random_#{username}"
	random = Session?.keys[key] or 0

	if not username?
		return

	cdnPrefix = (RocketChat.settings.get('CDN_PREFIX') or '').trim().replace(/\/$/, '')
	pathPrefix = (__meteor_runtime_config__.ROOT_URL_PATH_PREFIX or '').trim().replace(/\/$/, '')

	if cdnPrefix
		path = cdnPrefix + pathPrefix
	else if Meteor.isCordova
		# Meteor.absoluteUrl alread has path prefix
		path = Meteor.absoluteUrl().replace(/\/$/, '')
	else
		path = pathPrefix

	return "#{path}/avatar/#{encodeURIComponent(username)}?_dc=#{random}"


Meteor.methods
	updateOAuthApp: (applicationId, application) ->
		if not RocketChat.authz.hasPermission @userId, 'manage-oauth-apps'
			throw new Meteor.Error 'error-not-allowed', 'Not allowed', { method: 'updateOAuthApp' }

		if not _.isString(application.name) or application.name.trim() is ''
			throw new Meteor.Error 'error-invalid-name', 'Invalid name', { method: 'updateOAuthApp' }

		if not _.isString(application.redirectUri) or application.redirectUri.trim() is ''
			throw new Meteor.Error 'error-invalid-redirectUri', 'Invalid redirectUri', { method: 'updateOAuthApp' }

		if not _.isBoolean(application.active)
			throw new Meteor.Error 'error-invalid-arguments', 'Invalid arguments', { method: 'updateOAuthApp' }

		currentApplication = RocketChat.models.OAuthApps.findOne(applicationId)
		if not currentApplication?
			throw new Meteor.Error 'error-application-not-found', 'Application not found', { method: 'updateOAuthApp' }

		RocketChat.models.OAuthApps.update applicationId,
			$set:
				name: application.name
				active: application.active
				redirectUri: application.redirectUri
				_updatedAt: new Date
				_updatedBy: RocketChat.models.Users.findOne @userId, {fields: {username: 1}}

		return RocketChat.models.OAuthApps.findOne(applicationId)


let room = RocketChat.models.Rooms.findOneById({ $in: [rid, channel] });

keep keep keep keep add keep keep keep keep

/* eslint new-cap: 0 */

import loki from 'lokijs';
import {EventEmitter} from 'events';
import objectPath from 'object-path';

const logger = new Logger('BaseCache');

const lokiEq = loki.LokiOps.$eq;

loki.LokiOps.$eq = function(a, b) {
	if (Array.isArray(a)) {
		return loki.LokiOps.$containsAny(a, b);
	}
	return lokiEq(a, b);
};

loki.LokiOps.$in = loki.LokiOps.$containsAny;
loki.LokiOps.$nin = loki.LokiOps.$containsNone;

loki.LokiOps.$exists = function(a, b) {
	if (b) {
		return loki.LokiOps.$ne(a, undefined);
	}

	return loki.LokiOps.$eq(a, undefined);
};

loki.LokiOps.$elemMatch = function(a, b) {
	return _.findWhere(a, b);
};

const ignore = [
	'emit',
	'load',
	'on',
	'addToAllIndexes'
];

function traceMethodCalls(target) {
	target._stats = {};

	for (const property in target) {
		if (typeof target[property] === 'function' && ignore.indexOf(property) === -1) {
			target._stats[property] = {
				calls: 0,
				time: 0,
				avg: 0
			};
			const origMethod = target[property];
			target[property] = function(...args) {

				if (target.loaded !== true) {
					return origMethod.apply(target, args);
				}

				const startTime = RocketChat.statsTracker.now();
				const result = origMethod.apply(target, args);
				const time = Math.round(RocketChat.statsTracker.now() - startTime) / 1000;
				target._stats[property].time += time;
				target._stats[property].calls++;
				target._stats[property].avg = target._stats[property].time / target._stats[property].calls;

				return result;
			};
		}
	}

	setInterval(function() {
		for (const property in target._stats) {
			if (target._stats.hasOwnProperty(property) && target._stats[property].time > 0) {
				const tags = [`property:${property}`, `collection:${target.collectionName}`];
				RocketChat.statsTracker.timing('cache.methods.time', target._stats[property].avg, tags);
				RocketChat.statsTracker.increment('cache.methods.totalTime', target._stats[property].time, tags);
				RocketChat.statsTracker.increment('cache.methods.count', target._stats[property].calls, tags);
				target._stats[property].avg = 0;
				target._stats[property].time = 0;
				target._stats[property].calls = 0;
			}
		}
	}, 10000);

	target._getStatsAvg = function() {
		const stats = [];
		for (const property in target._stats) {
			if (target._stats.hasOwnProperty(property)) {
				stats.push([Math.round(target._stats[property].avg*100)/100, property]);
			}
		}
		return _.sortBy(stats, function(record) {
			return record[0];
		});
	};
}

class Adapter {
	loadDatabase(/*dbname, callback*/) {}
	saveDatabase(/*dbname, dbstring, callback*/) {}
	deleteDatabase(/*dbname, callback*/) {}
}

const db = new loki('rocket.chat.json', {adapter: Adapter});

class ModelsBaseCache extends EventEmitter {
	constructor(model) {
		super();

		traceMethodCalls(this);

		this.indexes = {};
		this.ignoreUpdatedFields = ['_updatedAt'];

		this.query = {};
		this.options = {};

		this.ensureIndex('_id', 'unique');

		this.joins = {};

		this.on('inserted', (...args) => { this.emit('changed', 'inserted', ...args); });
		this.on('removed', (...args) => { this.emit('changed', 'removed', ...args); });
		this.on('updated', (...args) => { this.emit('changed', 'updated', ...args); });

		this.on('beforeinsert', (...args) => { this.emit('beforechange', 'inserted', ...args); });
		this.on('beforeremove', (...args) => { this.emit('beforechange', 'removed', ...args); });
		this.on('beforeupdate', (...args) => { this.emit('beforechange', 'updated', ...args); });

		this.on('inserted', (...args) => { this.emit('sync', 'inserted', ...args); });
		this.on('updated', (...args) => { this.emit('sync', 'updated', ...args); });
		this.on('beforeremove', (...args) => { this.emit('sync', 'removed', ...args); });

		this.db = db;

		this.model = model;

		this.collectionName = this.model._db.collectionName;
		this.collection = this.db.addCollection(this.collectionName);
	}

	hasOne(join, {field, link}) {
		this.join({join, field, link, multi: false});
	}

	hasMany(join, {field, link}) {
		this.join({join, field, link, multi: true});
	}

	join({join, field, link, multi}) {
		if (!RocketChat.models[join]) {
			console.log(`Invalid cache model ${join}`);
			return;
		}

		RocketChat.models[join].cache.on('inserted', (record) => {
			this.processRemoteJoinInserted({join, field, link, multi, record: record});
		});

		RocketChat.models[join].cache.on('beforeupdate', (record, diff) => {
			if (diff[link.remote]) {
				this.processRemoteJoinRemoved({join, field, link, multi, record: record});
			}
		});

		RocketChat.models[join].cache.on('updated', (record, diff) => {
			if (diff[link.remote]) {
				this.processRemoteJoinInserted({join, field, link, multi, record: record});
			}
		});

		RocketChat.models[join].cache.on('removed', (record) => {
			this.processRemoteJoinRemoved({join, field, link, multi, record: record});
		});

		this.on('inserted', (localRecord) => {
			this.processLocalJoinInserted({join, field, link, multi, localRecord: localRecord});
		});

		this.on('beforeupdate', (localRecord, diff) => {
			if (diff[link.local]) {
				if (multi === true) {
					localRecord[field] = [];
				} else {
					localRecord[field] = undefined;
				}
			}
		});

		this.on('updated', (localRecord, diff) => {
			if (diff[link.local]) {
				this.processLocalJoinInserted({join, field, link, multi, localRecord: localRecord});
			}
		});
	}

	processRemoteJoinInserted({field, link, multi, record}) {
		let localRecords = this._findByIndex(link.local, objectPath.get(record, link.remote));

		if (!localRecords) {
			return;
		}

		if (!Array.isArray(localRecords)) {
			localRecords = [localRecords];
		}

		for (var i = 0; i < localRecords.length; i++) {
			const localRecord = localRecords[i];
			if (multi === true && !localRecord[field]) {
				localRecord[field] = [];
			}

			if (typeof link.transform === 'function') {
				record = link.transform(localRecord, record);
			}

			if (multi === true) {
				localRecord[field].push(record);
			} else {
				localRecord[field] = record;
			}

			this.emit(`join:${field}:inserted`, localRecord, record);
			this.emit(`join:${field}:changed`, 'inserted', localRecord, record);
		}
	}

	processLocalJoinInserted({join, field, link, multi, localRecord}) {
		let records = RocketChat.models[join].cache._findByIndex(link.remote, objectPath.get(localRecord, link.local));

		if (!Array.isArray(records)) {
			records = [records];
		}

		for (let i = 0; i < records.length; i++) {
			let record = records[i];

			if (typeof link.transform === 'function') {
				record = link.transform(localRecord, record);
			}

			if (multi === true) {
				localRecord[field].push(record);
			} else {
				localRecord[field] = record;
			}

			this.emit(`join:${field}:inserted`, localRecord, record);
			this.emit(`join:${field}:changed`, 'inserted', localRecord, record);
		}
	}

	processRemoteJoinRemoved({field, link, multi, record}) {
		let localRecords = this._findByIndex(link.local, objectPath.get(record, link.remote));

		if (!localRecords) {
			return;
		}

		if (!Array.isArray(localRecords)) {
			localRecords = [localRecords];
		}

		for (var i = 0; i < localRecords.length; i++) {
			const localRecord = localRecords[i];

			if (multi === true) {
				if (Array.isArray(localRecord[field])) {
					if (typeof link.remove === 'function') {
						link.remove(localRecord[field], record);
					} else if (localRecord[field].indexOf(record) > -1) {
						localRecord[field].splice(localRecord[field].indexOf(record), 1);
					}
				}
			} else {
				localRecord[field] = undefined;
			}

			this.emit(`join:${field}:removed`, localRecord, record);
			this.emit(`join:${field}:changed`, 'removed', localRecord, record);
		}
	}

	ensureIndex(fields, type='array') {
		if (!Array.isArray(fields)) {
			fields = [fields];
		}

		this.indexes[fields.join(',')] = {
			type: type,
			fields: fields,
			data: {}
		};
	}

	addToAllIndexes(record) {
		for (const indexName in this.indexes) {
			if (this.indexes.hasOwnProperty(indexName)) {
				this.addToIndex(indexName, record);
			}
		}
	}

	addToIndex(indexName, record) {
		const index = this.indexes[indexName];
		if (!index) {
			console.error(`Index not defined ${indexName}`);
			return;
		}

		const keys = [];
		for (const field of index.fields) {
			keys.push(objectPath.get(record, field));
		}
		const key = keys.join('|');

		if (index.type === 'unique') {
			index.data[key] = record;
			return;
		}

		if (index.type === 'array') {
			if (!index.data[key]) {
				index.data[key] = [];
			}
			index.data[key].push(record);
			return;
		}
	}

	removeFromAllIndexes(record) {
		for (const indexName in this.indexes) {
			if (this.indexes.hasOwnProperty(indexName)) {
				this.removeFromIndex(indexName, record);
			}
		}
	}

	removeFromIndex(indexName, record) {
		const index = this.indexes[indexName];
		if (!this.indexes[indexName]) {
			console.error(`Index not defined ${indexName}`);
			return;
		}

		if (!index.data) {
			return;
		}

		let key = [];
		for (const field of index.fields) {
			key.push(objectPath.get(record, field));
		}
		key = key.join('|');

		if (index.type === 'unique') {
			index.data[key] = undefined;
			return;
		}

		if (index.type === 'array') {
			if (!index.data[key]) {
				return;
			}
			const i = index.data[key].indexOf(record);
			if (i > -1) {
				index.data[key].splice(i, 1);
			}
			return;
		}
	}

	_findByIndex(index, keys) {
		const key = [].concat(keys).join('|');
		if (!this.indexes[index]) {
			return;
		}

		if (this.indexes[index].data) {
			const result = this.indexes[index].data[key];
			if (result) {
				return result;
			}
		}

		if (this.indexes[index].type === 'array') {
			return [];
		}
	}

	findByIndex(index, keys, options={}) {
		return {
			fetch: () => {
				return this.processQueryOptionsOnResult(this._findByIndex(index, keys), options);
			},

			count: () => {
				const records = this.findByIndex(index, keys, options).fetch();
				if (Array.isArray(records)) {
					return records.length;
				}
				return !records ? 0 : 1;
			},

			forEach: (fn) => {
				const records = this.findByIndex(index, keys, options).fetch();
				if (Array.isArray(records)) {
					return records.forEach(fn);
				}
				if (records) {
					return fn(records);
				}
			}
		};
	}

	load() {
		if (this.model._useCache === false) {
			return;
		}

		console.log('Will load cache for', this.collectionName);
		this.emit('beforeload');
		this.loaded = false;
		const time = RocketChat.statsTracker.now();
		const data = this.model.db.find(this.query, this.options).fetch();
		for (let i=0; i < data.length; i++) {
			this.insert(data[i]);
		}
		console.log(String(data.length), 'records load from', this.collectionName);
		RocketChat.statsTracker.timing('cache.load', RocketChat.statsTracker.now() - time, [`collection:${this.collectionName}`]);

		this.startSync();
		this.loaded = true;
		this.emit('afterload');
	}

	startSync() {
		if (this.model._useCache === false) {
			return;
		}

		this.model._db.on('change', ({action, id, data/*, oplog*/}) => {
			switch (action) {
				case 'insert':
					data._id = id;
					this.insert(data);
					break;

				case 'remove':
					this.removeById(id);
					break;

				case 'update:record':
					this.updateDiffById(id, data);
					break;

				case 'update:diff':
					this.updateDiffById(id, data);
					break;

				case 'update:query':
					this.update(data.query, data.update, data.options);
					break;
			}
		});
	}

	processQueryOptionsOnResult(result, options={}) {
		if (result === undefined || result === null) {
			return undefined;
		}

		if (Array.isArray(result)) {
			if (options.sort) {
				result = result.sort((a, b) => {
					let r = 0;
					for (const field in options.sort) {
						if (options.sort.hasOwnProperty(field)) {
							const direction = options.sort[field];
							let valueA;
							let valueB;
							if (field.indexOf('.') > -1) {
								valueA = objectPath.get(a, field);
								valueB = objectPath.get(b, field);
							} else {
								valueA = a[field];
								valueB = b[field];
							}
							if (valueA > valueB) {
								r = direction;
								break;
							}
							if (valueA < valueB) {
								r = -direction;
								break;
							}
						}
					}
					return r;
				});
			}

			if (typeof options.skip === 'number') {
				result.splice(0, options.skip);
			}

			if (typeof options.limit === 'number' && options.limit !== 0) {
				result.splice(options.limit);
			}
		}

		if (!options.fields) {
			options.fields = {};
		}

		const fieldsToRemove = [];
		const fieldsToGet = [];

		for (const field in options.fields) {
			if (options.fields.hasOwnProperty(field)) {
				if (options.fields[field] === 0) {
					fieldsToRemove.push(field);
				} else if (options.fields[field] === 1) {
					fieldsToGet.push(field);
				}
			}
		}

		if (fieldsToRemove.length > 0 && fieldsToGet.length > 0) {
			console.warn('Can\'t mix remove and get fields');
			fieldsToRemove.splice(0, fieldsToRemove.length);
		}

		if (fieldsToGet.length > 0 && fieldsToGet.indexOf('_id') === -1) {
			fieldsToGet.push('_id');
		}

		let pickFields = (obj, fields) => {
			let picked = {};
			fields.forEach((field) => {
				if (field.indexOf('.') !== -1) {
					objectPath.set(picked, field, objectPath.get(obj, field));
				} else {
					picked[field] = obj[field];
				}
			});
			return picked;
		};

		if (fieldsToRemove.length > 0 || fieldsToGet.length > 0) {
			if (Array.isArray(result)) {
				result = result.map((record) => {
					if (fieldsToRemove.length > 0) {
						return _.omit(record, ...fieldsToRemove);
					}

					if (fieldsToGet.length > 0) {
						return pickFields(record, fieldsToGet);
					}
				});
			} else {
				if (fieldsToRemove.length > 0) {
					return _.omit(result, ...fieldsToRemove);
				}

				if (fieldsToGet.length > 0) {
					return pickFields(result, fieldsToGet);
				}
			}
		}

		return result;
	}

	processQuery(query, parentField) {
		if (!query) {
			return query;
		}

		if (Match.test(query, String)) {
			return {
				_id: query
			};
		}

		if (Object.keys(query).length > 1 && parentField !== '$elemMatch') {
			const and = [];
			for (const field in query) {
				if (query.hasOwnProperty(field)) {
					and.push({
						[field]: query[field]
					});
				}
			}
			query = {$and: and};
		}

		for (const field in query) {
			if (query.hasOwnProperty(field)) {
				const value = query[field];
				if (value instanceof RegExp && field !== '$regex') {
					query[field] = {
						$regex: value
					};
				}

				if (field === '$and' || field === '$or') {
					query[field] = value.map((subValue) => {
						return this.processQuery(subValue, field);
					});
				}

				if (Match.test(value, Object) && Object.keys(value).length > 0) {
					query[field] = this.processQuery(value, field);
				}
			}
		}

		return query;
	}

	find(query, options={}) {
		return {
			fetch: () => {
				try {
					query = this.processQuery(query);
					return this.processQueryOptionsOnResult(this.collection.find(query), options);
				} catch (e) {
					console.error('Exception on cache find for', this.collectionName);
					console.error('Query:', JSON.stringify(query, null, 2));
					console.error('Options:', JSON.stringify(options, null, 2));
					console.error(e.stack);
					throw e;
				}
			},

			count: () => {
				try {
					query = this.processQuery(query);
					const { limit, skip } = options;
					return this.processQueryOptionsOnResult(this.collection.find(query), { limit, skip }).length;
				} catch (e) {
					console.error('Exception on cache find for', this.collectionName);
					console.error('Query:', JSON.stringify(query, null, 2));
					console.error('Options:', JSON.stringify(options, null, 2));
					console.error(e.stack);
					throw e;
				}
			},

			forEach: (fn) => {
				return this.find(query, options).fetch().forEach(fn);
			},

			observe: (obj) => {
				logger.debug(this.collectionName, 'Falling back observe to model with query:', query);
				return this.model.db.find(...arguments).observe(obj);
			},

			observeChanges: (obj) => {
				logger.debug(this.collectionName, 'Falling back observeChanges to model with query:', query);
				return this.model.db.find(...arguments).observeChanges(obj);
			},

			_publishCursor: (cursor, sub, collection) => {
				logger.debug(this.collectionName, 'Falling back _publishCursor to model with query:', query);
				return this.model.db.find(...arguments)._publishCursor(cursor, sub, collection);
			}
		};
	}

	findOne(query, options) {
		try {
			query = this.processQuery(query);
			return this.processQueryOptionsOnResult(this.collection.findOne(query), options);
		} catch (e) {
			console.error('Exception on cache findOne for', this.collectionName);
			console.error('Query:', JSON.stringify(query, null, 2));
			console.error('Options:', JSON.stringify(options, null, 2));
			console.error(e.stack);
			throw e;
		}
	}

	findOneById(_id, options) {
		return this.findByIndex('_id', _id, options).fetch();
	}

	findWhere(query, options) {
		query = this.processQuery(query);
		return this.processQueryOptionsOnResult(this.collection.findWhere(query), options);
	}

	addDynamicView() {
		return this.collection.addDynamicView(...arguments);
	}

	getDynamicView() {
		return this.collection.getDynamicView(...arguments);
	}

	insert(record) {
		if (Array.isArray(record)) {
			for (const item of record) {
				this.insert(item);
			}
		} else {
			// TODO remove - ignore updates in room.usernames
			if (this.collectionName === 'rocketchat_room' && record.usernames) {
				delete record.usernames;
			}
			this.emit('beforeinsert', record);
			this.addToAllIndexes(record);
			this.collection.insert(record);
			this.emit('inserted', record);
		}
	}

	updateDiffById(id, diff) {
		// TODO remove - ignore updates in room.usernames
		if (this.collectionName === 'rocketchat_room' && diff.usernames) {
			delete diff.usernames;
		}

		const record = this._findByIndex('_id', id);
		if (!record) {
			console.error('Cache.updateDiffById: No record', this.collectionName, id, diff);
			return;
		}

		const updatedFields = _.without(Object.keys(diff), ...this.ignoreUpdatedFields);

		if (updatedFields.length > 0) {
			this.emit('beforeupdate', record, diff);
		}

		for (let key in diff) {
			if (diff.hasOwnProperty(key)) {
				objectPath.set(record, key, diff[key]);
			}
		}

		this.collection.update(record);

		if (updatedFields.length > 0) {
			this.emit('updated', record, diff);
		}
	}

	updateRecord(record, update) {
		// TODO remove - ignore updates in room.usernames
		if (this.collectionName === 'rocketchat_room' && (record.usernames || (record.$set && record.$set.usernames))) {
			delete record.usernames;
			if (record.$set && record.$set.usernames) {
				delete record.$set.usernames;
			}
		}

		const topLevelFields = Object.keys(update).map(field => field.split('.')[0]);
		const updatedFields = _.without(topLevelFields, ...this.ignoreUpdatedFields);

		if (updatedFields.length > 0) {
			this.emit('beforeupdate', record, record);
		}

		if (update.$set) {
			_.each(update.$set, (value, field) => {
				objectPath.set(record, field, value);
			});
		}

		if (update.$unset) {
			_.each(update.$unset, (value, field) => {
				objectPath.del(record, field);
			});
		}

		if (update.$min) {
			_.each(update.$min, (value, field) => {
				let curValue = objectPath.get(record, field);
				if (curValue === undefined || value < curValue) {
					objectPath.set(record, field, value);
				}
			});
		}

		if (update.$max) {
			_.each(update.$max, (value, field) => {
				let curValue = objectPath.get(record, field);
				if (curValue === undefined || value > curValue) {
					objectPath.set(record, field, value);
				}
			});
		}

		if (update.$inc) {
			_.each(update.$inc, (value, field) => {
				let curValue = objectPath.get(record, field);
				if (curValue === undefined) {
					curValue = value;
				} else {
					curValue += value;
				}
				objectPath.set(record, field, curValue);
			});
		}

		if (update.$mul) {
			_.each(update.$mul, (value, field) => {
				let curValue = objectPath.get(record, field);
				if (curValue === undefined) {
					curValue = 0;
				} else {
					curValue *= value;
				}
				objectPath.set(record, field, curValue);
			});
		}

		if (update.$rename) {
			_.each(update.$rename, (value, field) => {
				let curValue = objectPath.get(record, field);
				if (curValue !== undefined) {
					objectPath.set(record, value, curValue);
					objectPath.del(record, field);
				}
			});
		}

		if (update.$pullAll) {
			_.each(update.$pullAll, (value, field) => {
				let curValue = objectPath.get(record, field);
				if (Array.isArray(curValue)) {
					curValue = _.difference(curValue, value);
					objectPath.set(record, field, curValue);
				}
			});
		}

		if (update.$pop) {
			_.each(update.$pop, (value, field) => {
				let curValue = objectPath.get(record, field);
				if (Array.isArray(curValue)) {
					if (value === -1) {
						curValue.shift();
					} else {
						curValue.pop();
					}
					objectPath.set(record, field, curValue);
				}
			});
		}

		if (update.$addToSet) {
			_.each(update.$addToSet, (value, field) => {
				let curValue = objectPath.get(record, field);
				if (curValue === undefined) {
					curValue = [];
				}
				if (Array.isArray(curValue)) {
					const length = curValue.length;

					if (value && value.$each && Array.isArray(value.$each)) {
						for (const valueItem of value.$each) {
							if (curValue.indexOf(valueItem) === -1) {
								curValue.push(valueItem);
							}
						}
					} else if (curValue.indexOf(value) === -1) {
						curValue.push(value);
					}

					if (curValue.length > length) {
						objectPath.set(record, field, curValue);
					}
				}
			});
		}

		this.collection.update(record);

		if (updatedFields.length > 0) {
			this.emit('updated', record, record);
		}
	}

	update(query, update, options = {}) {
		let records = options.multi ? this.find(query).fetch() : this.findOne(query) || [];
		if (!Array.isArray(records)) {
			records = [records];
		}

		for (const record of records) {
			this.updateRecord(record, update);
		}
	}

	removeById(id) {
		const record = this._findByIndex('_id', id);
		if (record) {
			this.emit('beforeremove', record);
			this.collection.removeWhere({_id: id});
			this.removeFromAllIndexes(record);
			this.emit('removed', record);
		}
	}
}

export default ModelsBaseCache;


<template name="adminEmojiEdit">

			{{> emojiEdit .}}

</template>


//! moment.js locale configuration
//! locale : Latvian [lv]
//! author : Kristaps Karlsons : https://github.com/skakri
//! author : Jānis Elmeris : https://github.com/JanisE

;(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 units = {
        'm': 'minūtes_minūtēm_minūte_minūtes'.split('_'),
        'mm': 'minūtes_minūtēm_minūte_minūtes'.split('_'),
        'h': 'stundas_stundām_stunda_stundas'.split('_'),
        'hh': 'stundas_stundām_stunda_stundas'.split('_'),
        'd': 'dienas_dienām_diena_dienas'.split('_'),
        'dd': 'dienas_dienām_diena_dienas'.split('_'),
        'M': 'mēneša_mēnešiem_mēnesis_mēneši'.split('_'),
        'MM': 'mēneša_mēnešiem_mēnesis_mēneši'.split('_'),
        'y': 'gada_gadiem_gads_gadi'.split('_'),
        'yy': 'gada_gadiem_gads_gadi'.split('_')
    };
    /**
     * @param withoutSuffix boolean true = a length of time; false = before/after a period of time.
     */
    function format(forms, number, withoutSuffix) {
        if (withoutSuffix) {
            // E.g. "21 minūte", "3 minūtes".
            return number % 10 === 1 && number % 100 !== 11 ? forms[2] : forms[3];
        } else {
            // E.g. "21 minūtes" as in "pēc 21 minūtes".
            // E.g. "3 minūtēm" as in "pēc 3 minūtēm".
            return number % 10 === 1 && number % 100 !== 11 ? forms[0] : forms[1];
        }
    }
    function relativeTimeWithPlural(number, withoutSuffix, key) {
        return number + ' ' + format(units[key], number, withoutSuffix);
    }
    function relativeTimeWithSingular(number, withoutSuffix, key) {
        return format(units[key], number, withoutSuffix);
    }
    function relativeSeconds(number, withoutSuffix) {
        return withoutSuffix ? 'dažas sekundes' : 'dažām sekundēm';
    }

    var lv = moment.defineLocale('lv', {
        months : 'janvāris_februāris_marts_aprīlis_maijs_jūnijs_jūlijs_augusts_septembris_oktobris_novembris_decembris'.split('_'),
        monthsShort : 'jan_feb_mar_apr_mai_jūn_jūl_aug_sep_okt_nov_dec'.split('_'),
        weekdays : 'svētdiena_pirmdiena_otrdiena_trešdiena_ceturtdiena_piektdiena_sestdiena'.split('_'),
        weekdaysShort : 'Sv_P_O_T_C_Pk_S'.split('_'),
        weekdaysMin : 'Sv_P_O_T_C_Pk_S'.split('_'),
        weekdaysParseExact : true,
        longDateFormat : {
            LT : 'HH:mm',
            LTS : 'HH:mm:ss',
            L : 'DD.MM.YYYY.',
            LL : 'YYYY. [gada] D. MMMM',
            LLL : 'YYYY. [gada] D. MMMM, HH:mm',
            LLLL : 'YYYY. [gada] D. MMMM, dddd, HH:mm'
        },
        calendar : {
            sameDay : '[Šodien pulksten] LT',
            nextDay : '[Rīt pulksten] LT',
            nextWeek : 'dddd [pulksten] LT',
            lastDay : '[Vakar pulksten] LT',
            lastWeek : '[Pagājušā] dddd [pulksten] LT',
            sameElse : 'L'
        },
        relativeTime : {
            future : 'pēc %s',
            past : 'pirms %s',
            s : relativeSeconds,
            m : relativeTimeWithSingular,
            mm : relativeTimeWithPlural,
            h : relativeTimeWithSingular,
            hh : relativeTimeWithPlural,
            d : relativeTimeWithSingular,
            dd : relativeTimeWithPlural,
            M : relativeTimeWithSingular,
            MM : relativeTimeWithPlural,
            y : relativeTimeWithSingular,
            yy : relativeTimeWithPlural
        },
        ordinalParse: /\d{1,2}\./,
        ordinal : '%d.',
        week : {
            dow : 1, // Monday is the first day of the week.
            doy : 4  // The week that contains Jan 4th is the first week of the year.
        }
    });

    return lv;

}));


//! moment.js locale configuration
//! locale : Portuguese (Brazil) [pt-br]
//! author : Caio Ribeiro Pereira : https://github.com/caio-ribeiro-pereira

;(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 pt_br = moment.defineLocale('pt-br', {
        months : 'Janeiro_Fevereiro_Março_Abril_Maio_Junho_Julho_Agosto_Setembro_Outubro_Novembro_Dezembro'.split('_'),
        monthsShort : 'Jan_Fev_Mar_Abr_Mai_Jun_Jul_Ago_Set_Out_Nov_Dez'.split('_'),
        weekdays : 'Domingo_Segunda-feira_Terça-feira_Quarta-feira_Quinta-feira_Sexta-feira_Sábado'.split('_'),
        weekdaysShort : 'Dom_Seg_Ter_Qua_Qui_Sex_Sáb'.split('_'),
        weekdaysMin : 'Dom_2ª_3ª_4ª_5ª_6ª_Sáb'.split('_'),
        weekdaysParseExact : true,
        longDateFormat : {
            LT : 'HH:mm',
            LTS : 'HH:mm:ss',
            L : 'DD/MM/YYYY',
            LL : 'D [de] MMMM [de] YYYY',
            LLL : 'D [de] MMMM [de] YYYY [às] HH:mm',
            LLLL : 'dddd, D [de] MMMM [de] YYYY [às] HH:mm'
        },
        calendar : {
            sameDay: '[Hoje às] LT',
            nextDay: '[Amanhã às] LT',
            nextWeek: 'dddd [às] LT',
            lastDay: '[Ontem às] LT',
            lastWeek: function () {
                return (this.day() === 0 || this.day() === 6) ?
                    '[Último] dddd [às] LT' : // Saturday + Sunday
                    '[Última] dddd [às] LT'; // Monday - Friday
            },
            sameElse: 'L'
        },
        relativeTime : {
            future : 'em %s',
            past : '%s atrás',
            s : 'poucos segundos',
            m : 'um minuto',
            mm : '%d minutos',
            h : 'uma hora',
            hh : '%d horas',
            d : 'um dia',
            dd : '%d dias',
            M : 'um mês',
            MM : '%d meses',
            y : 'um ano',
            yy : '%d anos'
        },
        ordinalParse: /\d{1,2}º/,
        ordinal : '%dº'
    });

    return pt_br;

}));


for (channel of channels) {
channelType = channel[0];
channel = channel.substr(1);


Meteor.methods({
	'livechat:getCustomFields'() {
		return RocketChat.models.LivechatCustomField.find().fetch();
	}
});


@ChatOAuthApps = new Meteor.Collection 'rocketchat_oauth_apps'


Meteor.methods({
	'livechat:removeCustomField'(_id) {
		if (!Meteor.userId() || !RocketChat.authz.hasPermission(Meteor.userId(), 'view-livechat-manager')) {
			throw new Meteor.Error('error-not-allowed', 'Not allowed', { method: 'livechat:removeCustomField' });
		}

		check(_id, String);

		var customField = RocketChat.models.LivechatCustomField.findOneById(_id, { fields: { _id: 1 } });

		if (!customField) {
			throw new Meteor.Error('error-invalid-custom-field', 'Custom field not found', { method: 'livechat:removeCustomField' });
		}

		return RocketChat.models.LivechatCustomField.removeById(_id);
	}
});


//Try to find the room by id or name if they didn't include the prefix.

room = retrieveRoomInfo({ currentUserId: user._id, channel: channelType + channel, ignoreEmpty: true });

//We didn't get a room, let's try finding direct messages


Package.describe({
	name: 'rocketchat:gitlab',
	version: '0.0.1',
	summary: 'RocketChat settings for GitLab Oauth Flow'
});

Package.onUse(function(api) {
	api.use('ecmascript');
	api.use('coffeescript');
	api.use('rocketchat:lib');
	api.use('rocketchat:custom-oauth');

	api.use('templating', 'client');

	api.addFiles('common.coffee');
	api.addFiles('gitlab-login-button.css', 'client');
	api.addFiles('startup.coffee', 'server');
});


This directory and the files immediately inside it are automatically generated
when you change this package's NPM dependencies. Commit the files in this
directory (npm-shrinkwrap.json, .gitignore, and this README) to source control
so that others run the same versions of sub-dependencies.

You should NOT check in the node_modules directory that Meteor automatically
creates; if you are using git, the .gitignore file tells git to ignore it.


RocketChat.Migrations.add
	version: 12
	up: ->
		# Set oldest user as admin, if none exists yet
		admin = RocketChat.models.Users.findOneAdmin true, { fields: { _id: 1 } }
		unless admin
			# get oldest user
			oldestUser = RocketChat.models.Users.findOne({}, { fields: { username: 1 }, sort: {createdAt: 1}})
			if oldestUser
				Meteor.users.update {_id: oldestUser._id}, {$set: {admin: true}}

				console.log "Set #{oldestUser.username} as admin for being the oldest user"


room = retrieveDirectMessageInfo({ currentUserId: user._id, channel: channelType + channel, findByUserIdOnly: true });


/* globals isSetNotNull, RocketChatFileEmojiCustomInstance */
Meteor.methods({
	deleteEmojiCustom(emojiID) {
		let emoji = null;

		if (RocketChat.authz.hasPermission(this.userId, 'manage-emoji')) {
			emoji = RocketChat.models.EmojiCustom.findOneByID(emojiID);
		} else {
			throw new Meteor.Error('not_authorized');
		}

		if (!isSetNotNull(() => emoji)) {
			throw new Meteor.Error('Custom_Emoji_Error_Invalid_Emoji', 'Invalid emoji', { method: 'deleteEmojiCustom' });
		}

		RocketChatFileEmojiCustomInstance.deleteFile(encodeURIComponent(`${emoji.name}.${emoji.extension}`));
		RocketChat.models.EmojiCustom.removeByID(emojiID);
		RocketChat.Notifications.notifyLogged('deleteEmojiCustom', {emojiData: emoji});

		return true;
	}
});


vm = Npm.require('vm')

compiledScripts = {}

getIntegrationScript = (integration) ->
	compiledScript = compiledScripts[integration._id]
	if compiledScript? and +compiledScript._updatedAt is +integration._updatedAt
		return compiledScript.script

	script = integration.scriptCompiled
	vmScript = undefined
	sandbox =
		_: _
		s: s
		console: console
		Store:
			set: (key, val) ->
				return store[key] = val
			get: (key) ->
				return store[key]

	try
		logger.incoming.info 'Will evaluate script of Trigger', integration.name
		logger.incoming.debug script

		vmScript = vm.createScript script, 'script.js'

		vmScript.runInNewContext sandbox

		if sandbox.Script?
			compiledScripts[integration._id] =
				script: new sandbox.Script()
				_updatedAt: integration._updatedAt

			return compiledScripts[integration._id].script
	catch e
		logger.incoming.error '[Error evaluating Script in Trigger', integration.name, ':]'
		logger.incoming.error script.replace(/^/gm, '  ')
		logger.incoming.error "[Stack:]"
		logger.incoming.error e.stack.replace(/^/gm, '  ')
		throw RocketChat.API.v1.failure 'error-evaluating-script'

	if not sandbox.Script?
		logger.incoming.error '[Class "Script" not in Trigger', integration.name, ']'
		throw RocketChat.API.v1.failure 'class-script-not-found'

Api = new Restivus
	enableCors: true
	apiPath: 'hooks/'
	auth:
		user: ->
			payloadKeys = Object.keys @bodyParams
			payloadIsWrapped = @bodyParams?.payload? and payloadKeys.length == 1

			if payloadIsWrapped and @request.headers['content-type'] is 'application/x-www-form-urlencoded'
				@bodyParams = JSON.parse @bodyParams.payload

			@integration = RocketChat.models.Integrations.findOne
				_id: @request.params.integrationId
				token: decodeURIComponent @request.params.token

			if not @integration?
				logger.incoming.info "Invalid integration id", @request.params.integrationId, "or token", @request.params.token
				return

			user = RocketChat.models.Users.findOne
				_id: @integration.userId

			return user: user

createIntegration = (options, user) ->
	logger.incoming.info 'Add integration'
	logger.incoming.debug options

	Meteor.runAsUser user._id, =>
		switch options['event']
			when 'newMessageOnChannel'
				options.data ?= {}

				if options.data.channel_name? and options.data.channel_name.indexOf('#') is -1
					options.data.channel_name = '#' + options.data.channel_name

				Meteor.call 'addOutgoingIntegration',
					username: 'rocket.cat'
					urls: [options.target_url]
					name: options.name
					channel: options.data.channel_name
					triggerWords: options.data.trigger_words

			when 'newMessageToUser'
				if options.data.username.indexOf('@') is -1
					options.data.username = '@' + options.data.username

				Meteor.call 'addOutgoingIntegration',
					username: 'rocket.cat'
					urls: [options.target_url]
					name: options.name
					channel: options.data.username
					triggerWords: options.data.trigger_words

	return RocketChat.API.v1.success()

removeIntegration = (options, user) ->
	logger.incoming.info 'Remove integration'
	logger.incoming.debug options

	integrationToRemove = RocketChat.models.Integrations.findOne urls: options.target_url
	Meteor.runAsUser user._id, =>
		Meteor.call 'deleteOutgoingIntegration', integrationToRemove._id

	return RocketChat.API.v1.success()

executeIntegrationRest = ->
	logger.incoming.info 'Post integration'
	logger.incoming.debug '@urlParams', @urlParams
	logger.incoming.debug '@bodyParams', @bodyParams

	if @integration.enabled isnt true
		return {} =
			statusCode: 503
			body: 'Service Unavailable'

	defaultValues =
		channel: @integration.channel
		alias: @integration.alias
		avatar: @integration.avatar
		emoji: @integration.emoji

	if @integration.scriptEnabled is true and @integration.scriptCompiled? and @integration.scriptCompiled.trim() isnt ''
		script = undefined
		try
			script = getIntegrationScript(@integration)
		catch e
			return e

		request =
			url:
				hash: @request._parsedUrl.hash
				search: @request._parsedUrl.search
				query: @queryParams
				pathname: @request._parsedUrl.pathname
				path: @request._parsedUrl.path
			url_raw: @request.url
			url_params: @urlParams
			content: @bodyParams
			content_raw: @request._readableState?.buffer?.toString()
			headers: @request.headers
			user:
				_id: @user._id
				name: @user.name
				username: @user.username

		try
			result = script.process_incoming_request({ request: request })

			if result?.error?
				return RocketChat.API.v1.failure result.error

			@bodyParams = result?.content

			logger.incoming.debug '[Process Incoming Request result of Trigger', @integration.name, ':]'
			logger.incoming.debug 'result', @bodyParams
		catch e
			logger.incoming.error '[Error running Script in Trigger', @integration.name, ':]'
			logger.incoming.error @integration.scriptCompiled.replace(/^/gm, '  ')
			logger.incoming.error "[Stack:]"
			logger.incoming.error e.stack.replace(/^/gm, '  ')
			return RocketChat.API.v1.failure 'error-running-script'

	if not @bodyParams?
		return RocketChat.API.v1.failure 'body-empty'

	@bodyParams.bot =
		i: @integration._id

	try
		message = processWebhookMessage @bodyParams, @user, defaultValues

		if _.isEmpty message
			return RocketChat.API.v1.failure 'unknown-error'

		return RocketChat.API.v1.success()
	catch e
		return RocketChat.API.v1.failure e.error

addIntegrationRest = ->
	return createIntegration @bodyParams, @user

removeIntegrationRest = ->
	return removeIntegration @bodyParams, @user

integrationSampleRest = ->
	logger.incoming.info 'Sample Integration'

	return {} =
		statusCode: 200
		body: [
			token: Random.id(24)
			channel_id: Random.id()
			channel_name: 'general'
			timestamp: new Date
			user_id: Random.id()
			user_name: 'rocket.cat'
			text: 'Sample text 1'
			trigger_word: 'Sample'
		,
			token: Random.id(24)
			channel_id: Random.id()
			channel_name: 'general'
			timestamp: new Date
			user_id: Random.id()
			user_name: 'rocket.cat'
			text: 'Sample text 2'
			trigger_word: 'Sample'
		,
			token: Random.id(24)
			channel_id: Random.id()
			channel_name: 'general'
			timestamp: new Date
			user_id: Random.id()
			user_name: 'rocket.cat'
			text: 'Sample text 3'
			trigger_word: 'Sample'
		]

integrationInfoRest = ->
	logger.incoming.info 'Info integration'

	return {} =
		statusCode: 200
		body:
			success: true

Api.addRoute ':integrationId/:userId/:token', authRequired: true, {post: executeIntegrationRest, get: executeIntegrationRest}
Api.addRoute ':integrationId/:token', authRequired: true, {post: executeIntegrationRest, get: executeIntegrationRest}

Api.addRoute 'sample/:integrationId/:userId/:token', authRequired: true, {get: integrationSampleRest}
Api.addRoute 'sample/:integrationId/:token', authRequired: true, {get: integrationSampleRest}

Api.addRoute 'info/:integrationId/:userId/:token', authRequired: true, {get: integrationInfoRest}
Api.addRoute 'info/:integrationId/:token', authRequired: true, {get: integrationInfoRest}

Api.addRoute 'add/:integrationId/:userId/:token', authRequired: true, {post: addIntegrationRest}
Api.addRoute 'add/:integrationId/:token', authRequired: true, {post: addIntegrationRest}

Api.addRoute 'remove/:integrationId/:userId/:token', authRequired: true, {post: removeIntegrationRest}
Api.addRoute 'remove/:integrationId/:token', authRequired: true, {post: removeIntegrationRest}


RocketChat.integrations.validateOutgoing = (integration, userId) ->
	if integration.channel?.trim? and integration.channel.trim() is ''
		delete integration.channel

	if integration.username.trim() is ''
		throw new Meteor.Error 'error-invalid-username', 'Invalid username', { method: 'addOutgoingIntegration' }

	if not Match.test integration.urls, [String]
		throw new Meteor.Error 'error-invalid-urls', 'Invalid URLs', { method: 'addOutgoingIntegration' }

	for url, index in integration.urls
		delete integration.urls[index] if url.trim() is ''

	integration.urls = _.without integration.urls, [undefined]

	if integration.urls.length is 0
		throw new Meteor.Error 'error-invalid-urls', 'Invalid URLs', { method: 'addOutgoingIntegration' }

	channels = if integration.channel then _.map(integration.channel.split(','), (channel) -> s.trim(channel)) else []

	scopedChannels = ['all_public_channels', 'all_private_groups', 'all_direct_messages']
	for channel in channels
		if channel[0] not in ['@', '#'] and channel not in scopedChannels
			throw new Meteor.Error 'error-invalid-channel-start-with-chars', 'Invalid channel. Start with @ or #', { method: 'updateIncomingIntegration' }

	if integration.triggerWords?
		if not Match.test integration.triggerWords, [String]
			throw new Meteor.Error 'error-invalid-triggerWords', 'Invalid triggerWords', { method: 'addOutgoingIntegration' }

		for triggerWord, index in integration.triggerWords
			delete integration.triggerWords[index] if triggerWord.trim() is ''

		integration.triggerWords = _.without integration.triggerWords, [undefined]

	if integration.scriptEnabled is true and integration.script? and integration.script.trim() isnt ''
		try
			babelOptions = Babel.getDefaultOptions({ runtime: false })
			babelOptions = _.extend(babelOptions, { compact: true, minified: true, comments: false })

			integration.scriptCompiled = Babel.compile(integration.script, babelOptions).code
			integration.scriptError = undefined
		catch e
			integration.scriptCompiled = undefined
			integration.scriptError = _.pick e, 'name', 'message', 'stack'

	for channel in channels
		if channel in scopedChannels
			if channel is 'all_public_channels'
				#No special permissions needed to add integration to public channels
			else if not RocketChat.authz.hasPermission userId, 'manage-integrations'
				throw new Meteor.Error 'error-invalid-channel', 'Invalid Channel', { method: 'addOutgoingIntegration' }
		else
			record = undefined
			channelType = channel[0]
			channel = channel.substr(1)

			switch channelType
				when '#'
					record = RocketChat.models.Rooms.findOne
						$or: [
							{_id: channel}
							{name: channel}
						]
				when '@'
					record = RocketChat.models.Users.findOne
						$or: [
							{_id: channel}
							{username: channel}
						]

			if record is undefined
				throw new Meteor.Error 'error-invalid-room', 'Invalid room', { method: 'addOutgoingIntegration' }

			if record.usernames? and
			(not RocketChat.authz.hasPermission userId, 'manage-integrations') and
			(RocketChat.authz.hasPermission userId, 'manage-own-integrations') and
			Meteor.user()?.username not in record.usernames
				throw new Meteor.Error 'error-invalid-channel', 'Invalid Channel', { method: 'addOutgoingIntegration' }

	user = RocketChat.models.Users.findOne({username: integration.username})

	if not user?
		throw new Meteor.Error 'error-invalid-user', 'Invalid user', { method: 'addOutgoingIntegration' }

	integration.type = 'webhook-outgoing'
	integration.userId = user._id
	integration.channel = channels

	return integration


for (let i = 0; i < keys.$in.length; i++) {

const result = this._findByIndex(index, keys.$in[i]);

function retrieveRoomInfo({ currentUserId, channel, ignoreEmpty=false }) {
	const room = RocketChat.models.Rooms.findOneByIdOrName(channel);
	if (!_.isObject(room) && !ignoreEmpty) {
		throw new Meteor.Error('invalid-channel');
	}

	if (room && room.t === 'c') {
		Meteor.runAsUser(currentUserId, function() {
			return Meteor.call('joinRoom', room._id);
		});
	}

	return room;
}

function retrieveDirectMessageInfo({ currentUserId, channel, findByUserIdOnly=false }) {
	let roomUser = undefined;

	if (findByUserIdOnly) {
		roomUser = RocketChat.models.Users.findOneById(channel);
	} else {
		roomUser = RocketChat.models.Users.findOne({
			$or: [{ _id: channel }, { username: channel }]
		});
	}

	if (!_.isObject(roomUser)) {
		throw new Meteor.Error('invalid-channel');
	}

	const rid = [currentUserId, roomUser._id].sort().join('');
	let room = RocketChat.models.Rooms.findOneById({ $in: [rid, channel] });

	if (!room) {
		Meteor.runAsUser(currentUserId, function() {
			Meteor.call('createDirectMessage', roomUser.username);
			room = RocketChat.models.Rooms.findOneById(rid);
		});
	}

	return room;
}

this.processWebhookMessage = function(messageObj, user, defaultValues) {
	var attachment, channel, channels, channelType, i, len, message, ref, room, ret;
	ret = [];

	if (!defaultValues) {
		defaultValues = {
			channel: '',
			alias: '',
			avatar: '',
			emoji: ''
		};
	}

	channel = messageObj.channel || messageObj.roomId || defaultValues.channel;

	channels = [].concat(channel);

	for (channel of channels) {
		channelType = channel[0];

		channel = channel.substr(1);

		switch (channelType) {
			case '#':
				room = retrieveRoomInfo({ currentUserId: user._id, channel });
				break;
			case '@':
				room = retrieveDirectMessageInfo({ currentUserId: user._id, channel });
				break;
			default:
				//Try to find the room by id or name if they didn't include the prefix.
				room = retrieveRoomInfo({ currentUserId: user._id, channel: channelType + channel, ignoreEmpty: true });
				if (room) {
					break;
				}

				//We didn't get a room, let's try finding direct messages
				room = retrieveDirectMessageInfo({ currentUserId: user._id, channel: channelType + channel, findByUserIdOnly: true });
				if (room) {
					break;
				}

				//No room, so throw an error
				throw new Meteor.Error('invalid-channel');
		}

		if (messageObj.attachments && !_.isArray(messageObj.attachments)) {
			console.log('Attachments should be Array, ignoring value'.red, messageObj.attachments);
			messageObj.attachments = undefined;
		}

		message = {
			alias: messageObj.username || messageObj.alias || defaultValues.alias,
			msg: _.trim(messageObj.text || messageObj.msg || ''),
			attachments: messageObj.attachments,
			parseUrls: messageObj.parseUrls !== undefined ? messageObj.parseUrls : !messageObj.attachments,
			bot: messageObj.bot,
			groupable: (messageObj.groupable !== undefined) ? messageObj.groupable : false
		};

		if (!_.isEmpty(messageObj.icon_url) || !_.isEmpty(messageObj.avatar)) {
			message.avatar = messageObj.icon_url || messageObj.avatar;
		} else if (!_.isEmpty(messageObj.icon_emoji) || !_.isEmpty(messageObj.emoji)) {
			message.emoji = messageObj.icon_emoji || messageObj.emoji;
		} else if (!_.isEmpty(defaultValues.avatar)) {
			message.avatar = defaultValues.avatar;
		} else if (!_.isEmpty(defaultValues.emoji)) {
			message.emoji = defaultValues.emoji;
		}

		if (_.isArray(message.attachments)) {
			ref = message.attachments;
			for (i = 0, len = ref.length; i < len; i++) {
				attachment = ref[i];
				if (attachment.msg) {
					attachment.text = _.trim(attachment.msg);
					delete attachment.msg;
				}
			}
		}

		var messageReturn = RocketChat.sendMessage(user, message, room);
		ret.push({ channel: channel, message: messageReturn });
	}
	return ret;
};


RocketChat.roomTypes = new class roomTypesServer extends roomTypesCommon
	### add a publish for a room type
	@param roomType: room type (e.g.: c (for channels), d (for direct channels))
	@param callback: function that will return the publish's data
	###
	setPublish: (roomType, callback) ->
		if @roomTypes[roomType]?.publish?
			throw new Meteor.Error 'route-publish-exists', 'Publish for the given type already exists'

		unless @roomTypes[roomType]?
			@roomTypes[roomType] = {}

		@roomTypes[roomType].publish = callback

	setRoomFind: (roomType, callback) ->
		if @roomTypes[roomType]?.roomFind?
			throw new Meteor.Error 'room-find-exists', 'Room find for the given type already exists'

		unless @roomTypes[roomType]?
			@roomTypes[roomType] = {}

		@roomTypes[roomType].roomFind = callback

	getRoomFind: (roomType) ->
		return unless @roomTypes[roomType]?.roomFind?
		return @roomTypes[roomType].roomFind

	### run the publish for a room type
	@param scope: Meteor publish scope
	@param roomType: room type (e.g.: c (for channels), d (for direct channels))
	@param identifier: identifier of the room
	###
	runPublish: (scope, roomType, identifier) ->
		return unless @roomTypes[roomType]?.publish?
		return @roomTypes[roomType].publish.call scope, identifier


atLeastOne = (permissions, scope) ->
	return _.some permissions, (permissionId) ->
		permission = ChatPermissions.findOne permissionId
		return permission and _.some permission.roles, (roleName) ->
			role = RocketChat.models.Roles.findOne roleName
			roleScope = role?.scope
			return RocketChat.models[roleScope]?.isUserInRole?(Meteor.userId(), roleName, scope)

all = (permissions, scope) ->
	return _.every permissions, (permissionId) ->
		permission = ChatPermissions.findOne permissionId
		return permission and _.some permission.roles, (roleName) ->
			role = RocketChat.models.Roles.findOne roleName
			roleScope = role?.scope
			return RocketChat.models[roleScope]?.isUserInRole?(Meteor.userId(), roleName, scope)

Template.registerHelper 'hasPermission', (permission, scope) ->
	return hasPermission(permission, scope, atLeastOne)

RocketChat.authz.hasAllPermission = (permissions, scope) ->
	return hasPermission(permissions, scope, all)

RocketChat.authz.hasAtLeastOnePermission = (permissions, scope) ->
	return hasPermission(permissions, scope, atLeastOne)

hasPermission = (permissions, scope, strategy) ->
	userId = Meteor.userId()

	unless userId
		return false

	unless RocketChat.authz.cachedCollection.ready.get()
		return false

	permissions = [].concat permissions

	return strategy(permissions, scope)