import { INodeProperties } from 'n8n-workflow';

export const activityOperations = [
	{
		displayName: 'Operation',
		name: 'operation',
		type: 'options',
		displayOptions: {
			show: {
				resource: [
					'activity',
				],
			},
		},
		options: [
			{
				name: 'Create',
				value: 'create',
				description: 'Create an activity',
			},
			{
				name: 'Delete',
				value: 'delete',
				description: 'Delete an activity',
			},
			{
				name: 'Get',
				value: 'get',
				description: 'Get an activity',
			},
			{
				name: 'Get All',
				value: 'getAll',
				description: 'Get all companies',
			},
			{
				name: 'Update',
				value: 'update',
				description: 'Update an activity',
			},
		],
		default: 'create',
		description: 'The operation to perform.',
	},
] as INodeProperties[];

export const activityFields = [

/* -------------------------------------------------------------------------- */
/*                                activity:create                             */
/* -------------------------------------------------------------------------- */
	{
		displayName: 'Title',
		name: 'title',
		type: 'string',
		default: '',
		displayOptions: {
			show: {
				resource: [
					'activity',
				],
				operation: [
					'create',
				],
			},
		},
		required: true,
	},
	{
		displayName: 'Owner',
		name: 'owner',
		type: 'options',
		default: '',
		typeOptions: {
			loadOptionsMethod: 'getUsers',
		},
		displayOptions: {
			show: {
				resource: [
					'activity',
				],
				operation: [
					'create',
				],
			},
		},
		required: true,
	},
	{
		displayName: 'Type',
		name: 'type',
		type: 'string',
		default: '',
		displayOptions: {
			show: {
				resource: [
					'activity',
				],
				operation: [
					'create',
				],
			},
		},
		description: 'This field displays activity type such as call, meeting etc.',
		required: true,
	},
	{
		displayName: 'RAW Data',
		name: 'rawData',
		type: 'boolean',
		displayOptions: {
			show: {
				resource: [
					'activity',
				],
				operation: [
					'create',
				],
			},
		},
		default: false,
		description: `If the data should include the fields details`,
	},
	{
		displayName: 'Additional Fields',
		name: 'additionalFields',
		type: 'collection',
		placeholder: 'Add Field',
		default: {},
		displayOptions: {
			show: {
				resource: [
					'activity',
				],
				operation: [
					'create',
				],
			},
		},
		options: [
			{
				displayName: 'Description',
				name: 'description',
				typeOptions: {
					alwaysOpenEditWindow: true,
				},
				type: 'string',
				default: '',
				description: 'This field contains details related to the activity.',
			},
			{
				displayName: 'Tags',
				name: 'tags',
				type: 'string',
				default: '',
				description: 'This field contains tags associated with an activity',
			},
			{
				displayName: 'Due Date',
				name: 'dueDate',
				type: 'dateTime',
				default: '',
				description: 'Expiry date of an activity.',
			},
			{
				displayName: 'Duration',
				name: 'duration',
				type: 'number',
				default: '',
				description: 'Time duration of an activity.',
			},
			{
				displayName: 'Is Calendar Invite',
				name: 'isCalendarInvite',
				type: 'boolean',
				default: false,
				description: 'This field is used to send calendar invite.',
			},
			{
				displayName: 'Is Completed',
				name: 'isCompleted',
				type: 'boolean',
				default: false,
				description: 'This field indicates whether the activity is completed or not.',
			},
		],
	},
/* -------------------------------------------------------------------------- */
/*                                activity:update                             */
/* -------------------------------------------------------------------------- */
	{
		displayName: 'Activity ID',
		name: 'id',
		type: 'string',
		default: '',
		required: true,
		displayOptions: {
			show: {
				resource: [
					'activity',
				],
				operation: [
					'update',
				],
			},
		},
		description: 'activity ID',
	},
	{
		displayName: 'RAW Data',
		name: 'rawData',
		type: 'boolean',
		displayOptions: {
			show: {
				resource: [
					'activity',
				],
				operation: [
					'update',
				],
			},
		},
		default: false,
		description: `If the data should include the fields details`,
	},
	{
		displayName: 'Update Fields',
		name: 'updateFields',
		type: 'collection',
		placeholder: 'Add Field',
		default: {},
		displayOptions: {
			show: {
				resource: [
					'activity',
				],
				operation: [
					'update',
				],
			},
		},
		options: [
			{
				displayName: 'Title',
				name: 'title',
				type: 'string',
				default: '',
			},
			{
				displayName: 'Type',
				name: 'type',
				type: 'string',
				default: '',
			},
			{
				displayName: 'Owner',
				name: 'owner',
				type: 'string',
				default: '',
			},
			{
				displayName: 'Description',
				name: 'description',
				typeOptions: {
					alwaysOpenEditWindow: true,
				},
				type: 'string',
				default: '',
				description: 'This field contains details related to the activity.',
			},
			{
				displayName: 'Tags',
				name: 'tags',
				type: 'string',
				default: '',
				description: 'This field contains tags associated with an activity',
			},
			{
				displayName: 'Due Date',
				name: 'dueDate',
				type: 'dateTime',
				default: '',
				description: 'Expiry date of an activity.',
			},
			{
				displayName: 'Duration',
				name: 'duration',
				type: 'number',
				default: '',
				description: 'Time duration of an activity.',
			},
			{
				displayName: 'Is Calendar Invite',
				name: 'isCalendarInvite',
				type: 'boolean',
				default: false,
				description: 'This field is used to send calendar invite.',
			},
			{
				displayName: 'Is Completed',
				name: 'isCompleted',
				type: 'boolean',
				default: false,
				description: 'This field indicates whether the activity is completed or not.',
			},
		],

	},
/* -------------------------------------------------------------------------- */
/*                                 activity:get                               */
/* -------------------------------------------------------------------------- */
	{
		displayName: 'Activity ID',
		name: 'id',
		type: 'string',
		default: '',
		required: true,
		displayOptions: {
			show: {
				resource: [
					'activity',
				],
				operation: [
					'get',
				],
			},
		},
		description: 'activity ID',
	},
	{
		displayName: 'RAW Data',
		name: 'rawData',
		type: 'boolean',
		displayOptions: {
			show: {
				resource: [
					'activity',
				],
				operation: [
					'get',
				],
			},
		},
		default: false,
		description: `If the data should include the fields details`,
	},
/* -------------------------------------------------------------------------- */
/*                                 activity:getAll                            */
/* -------------------------------------------------------------------------- */
	{
		displayName: 'Return All',
		name: 'returnAll',
		type: 'boolean',
		displayOptions: {
			show: {
				resource: [
					'activity',
				],
				operation: [
					'getAll',
				],
			},
		},
		default: false,
		description: 'If all results should be returned or only up to a given limit.',
	},
	{
		displayName: 'Limit',
		name: 'limit',
		type: 'number',
		displayOptions: {
			show: {
				resource: [
					'activity',
				],
				operation: [
					'getAll',
				],
				returnAll: [
					false,
				],
			},
		},
		typeOptions: {
			minValue: 1,
			maxValue: 25,
		},
		default: 10,
		description: 'How many results to return.',
	},
	{
		displayName: 'JSON Parameters',
		name: 'jsonParameters',
		type: 'boolean',
		default: false,
		description: '',
		displayOptions: {
			show: {
				operation: [
					'getAll',
				],
				resource: [
					'activity',
				],
			},
		},
	},
	{
		displayName: 'Options',
		name: 'options',
		type: 'collection',
		placeholder: 'Add Option',
		default: {},
		displayOptions: {
			show: {
				resource: [
					'activity',
				],
				operation: [
					'getAll',
				],
			},
		},
		options: [
			{
				displayName: 'Fields',
				name: 'fields',
				type: 'string',
				default: '',
				description: 'Comma separated list of fields to return.',
			},
			{
				displayName: 'Sort By',
				name: 'sortBy',
				type: 'string',
				default: '',
				description: 'The field to sort by.',
			},
			{
				displayName: 'Sort Order',
				name: 'sortOrder',
				type: 'options',
				options: [
					{
						name: 'ASC',
						value: 'asc',
					},
					{
						name: 'DESC',
						value: 'desc',
					},
				],
				default: 'desc',
				description: 'Sort order',
			},
		],
	},
	{
		displayName: 'Filters',
		name: 'filtersJson',
		type: 'json',
		typeOptions: {
			alwaysOpenEditWindow: true,
		},
		default: '',
		displayOptions: {
			show: {
				operation: [
					'getAll',
				],
				resource: [
					'activity',
				],
				jsonParameters: [
					true,
				],
			},
		},
	},
	{
		displayName: 'Filters',
		name: 'filters',
		placeholder: 'Add filter',
		type: 'fixedCollection',
		typeOptions: {
			multipleValues: false,
		},
		displayOptions: {
			show: {
				resource: [
					'activity',
				],
				operation: [
					'getAll',
				],
				jsonParameters: [
					false,
				],
			},
		},
		default: {},
		options: [
			{
				name: 'filtersUi',
				displayName: 'Filters',
				values: [
					{
						displayName: 'Operator',
						name: 'operator',
						type: 'options',
						options: [
							{
								name: 'AND',
								value: 'AND',
							},
							{
								name: 'OR',
								value: 'OR',
							},
						],
						default: 'AND',
					},
					{
						displayName: 'Conditions',
						name: 'conditions',
						placeholder: 'Add Condition',
						type: 'fixedCollection',
						typeOptions: {
							multipleValues: true,
						},
						default: {},
						options: [
							{
								name: 'conditionsUi',
								displayName: 'Conditions',
								values: [
									{
										displayName: 'Field',
										name: 'field',
										type: 'options',
										options: [
											{
												name: 'Title',
												value: 'title',
											},
											{
												name: 'Tags',
												value: 'tags',
											},
										],
										default: 'title',
									},
									{
										displayName: 'Condition',
										name: 'condition',
										type: 'options',
										options: [
											{
												name: 'Equals',
												value: 'EQUALS',
											},
											{
												name: 'Not Equals',
												value: 'NOT_EQUALS',
											},
											{
												name: 'Empty',
												value: 'EMPTY',
											},
											{
												name: 'Not Empty',
												value: 'NOT_EMPTY',
											},
											{
												name: 'CONTAINS',
												value: 'Contains',
											},
											{
												name: 'Does Not Contains',
												value: 'DOES_NOT_CONTAINS',
											},
											{
												name: 'Starts With',
												value: 'STARTS_WITH',
											},
											{
												name: 'Ends With',
												value: 'ENDS_WITH',
											},
										],
										default: 'EQUALS',
										description: 'Value of the property to set.',
									},
									{
										displayName: 'Value',
										name: 'value',
										type: 'string',
										default: '',
									},
								],
							},
						],
					},
				],
			},
		],
	},
/* -------------------------------------------------------------------------- */
/*                                activity:delete                             */
/* -------------------------------------------------------------------------- */
	{
		displayName: 'Activity ID',
		name: 'id',
		type: 'string',
		default: '',
		required: true,
		displayOptions: {
			show: {
				resource: [
					'activity',
				],
				operation: [
					'delete',
				],
			},
		},
		description: 'If more than one activity add them separated by ,',
	},
] as INodeProperties[];


import {
	INodeProperties,
} from 'n8n-workflow';

export const accountOperations = [
	{
		displayName: 'Operation',
		name: 'operation',
		type: 'options',
		default: 'getBalances',
		description: 'Operation to perform',
		options: [
			{
				name: 'Get Balances',
				value: 'getBalances',
				description: 'Retrieve balances for all account currencies of this user.',
			},
			{
				name: 'Get Currencies',
				value: 'getCurrencies',
				description: 'Retrieve currencies in the borderless account of this user.',
			},
			{
				name: 'Get Statement',
				value: 'getStatement',
				description: 'Retrieve the statement for the borderless account of this user.',
			},
		],
		displayOptions: {
			show: {
				resource: [
					'account',
				],
			},
		},
	},
] as INodeProperties[];

export const accountFields = [
	// ----------------------------------
	//      account: getBalances
	// ----------------------------------
	{
		displayName: 'Profile ID',
		name: 'profileId',
		type: 'options',
		required: true,
		default: [],
		typeOptions: {
			loadOptionsMethod: 'getProfiles',
		},
		description: 'ID of the user profile to retrieve the balance of.',
		displayOptions: {
			show: {
				resource: [
					'account',
				],
				operation: [
					'getBalances',
				],
			},
		},
	},

	// ----------------------------------
	//      account: getStatement
	// ----------------------------------
	{
		displayName: 'Profile ID',
		name: 'profileId',
		type: 'options',
		default: [],
		typeOptions: {
			loadOptionsMethod: 'getProfiles',
		},
		description: 'ID of the user profile whose account to retrieve the statement of.',
		displayOptions: {
			show: {
				resource: [
					'account',
				],
				operation: [
					'getStatement',
				],
			},
		},
	},
	{
		displayName: 'Borderless Account ID',
		name: 'borderlessAccountId',
		type: 'options',
		default: [],
		required: true,
		typeOptions: {
			loadOptionsMethod: 'getBorderlessAccounts',
			loadOptionsDependsOn: [
				'profileId',
			],
		},
		description: 'ID of the borderless account to retrieve the statement of.',
		displayOptions: {
			show: {
				resource: [
					'account',
				],
				operation: [
					'getStatement',
				],
			},
		},
	},
	{
		displayName: 'Currency',
		name: 'currency',
		type: 'string',
		default: '',
		// TODO: preload
		description: 'Code of the currency of the borderless account to retrieve the statement of.',
		displayOptions: {
			show: {
				resource: [
					'account',
				],
				operation: [
					'getStatement',
				],
			},
		},
	},
	{
		displayName: 'Additional Fields',
		name: 'additionalFields',
		type: 'collection',
		placeholder: 'Add Field',
		default: {},
		displayOptions: {
			show: {
				resource: [
					'account',
				],
				operation: [
					'getStatement',
				],
			},
		},
		options: [
			{
				displayName: 'Line Style',
				name: 'lineStyle',
				type: 'options',
				default: 'COMPACT',
				description: 'Line style to retrieve the statement in.',
				options: [
					{
						name: 'Compact',
						value: 'COMPACT',
						description: 'Single line per transaction.',
					},
					{
						name: 'Flat',
						value: 'FLAT',
						description: 'Separate lines for transaction fees.',
					},
				],
			},
			{
				displayName: 'Range',
				name: 'range',
				type: 'fixedCollection',
				placeholder: 'Add Range',
				default: {},
				options: [
					{
						displayName: 'Range Properties',
						name: 'rangeProperties',
						values: [
							{
								displayName: 'Range Start',
								name: 'intervalStart',
								type: 'dateTime',
								default: '',
							},
							{
								displayName: 'Range End',
								name: 'intervalEnd',
								type: 'dateTime',
								default: '',
							},
						],
					},
				],
			},
		],
	},
] as INodeProperties[];


import { RequestOptions } from 'oauth-1.0a';

import * as requestPromise from 'request-promise-native';

import { compare } from '@node-rs/bcrypt';

import * as promClient from 'prom-client';

keep keep keep keep replace keep keep keep

// IMPORRTANT! Do not switch to anther bcrypt library unless really needed and

// tested with all possible systems like Windows, Alpine on ARM, FreeBSD, ...

n8n Workflow Automation Tool

concurrently -k -n "TypeScript,Node" -c "yellow.bold,cyan.bold" "npm run watch" "nodemon"

echo "Building project..." && rm -rf dist && tsc -b && oclif-dev manifest

tslint --fix -p tsconfig.json -c tslint.json

automate automation IaaS iPaaS n8n workflow


import {
	OptionsWithUri,
} from 'request';

import {
	IExecuteFunctions,
	ILoadOptionsFunctions,
} from 'n8n-core';

import {
	IDataObject, NodeApiError,
} from 'n8n-workflow';

export async function sendyApiRequest(this: IExecuteFunctions | ILoadOptionsFunctions, method: string, path: string, body: any = {}, qs: IDataObject = {}, option = {}): Promise<any> { // tslint:disable-line:no-any

	const credentials = this.getCredentials('sendyApi') as IDataObject;

	body.api_key = credentials.apiKey;

	body.boolean = true;

	const options: OptionsWithUri = {
		headers: {
			'Content-Type': 'application/x-www-form-urlencoded',
		},
		method,
		form: body,
		qs,
		uri: `${credentials.url}${path}`,
	};

	try {
		//@ts-ignore
		return await this.helpers.request.call(this, options);
	} catch (error) {
		throw new NodeApiError(this.getNode(), error);
	}
}


How a digital strategist uses n8n for online marketing


// Components
import VTimePickerTitle from './VTimePickerTitle'
import VTimePickerClock from './VTimePickerClock'

// Mixins
import Picker from '../../mixins/picker'

// Utils
import { createRange } from '../../util/helpers'
import pad from '../VDatePicker/util/pad'

const rangeHours24 = createRange(24)
const rangeHours12am = createRange(12)
const rangeHours12pm = rangeHours12am.map(v => v + 12)
const rangeMinutes = createRange(60)

export default {
  name: 'v-time-picker',

  mixins: [Picker],

  data () {
    return {
      inputHour: null,
      inputMinute: null,
      period: 'am',
      selectingHour: true
    }
  },

  props: {
    allowedHours: Function,
    allowedMinutes: Function,
    format: {
      type: String,
      default: 'ampm',
      validator (val) {
        return ['ampm', '24hr'].includes(val)
      }
    },
    min: String,
    max: String,
    scrollable: Boolean,
    value: null
  },

  computed: {
    isAllowedHourCb () {
      if (!this.min && !this.max) return this.allowedHours

      const minHour = this.min ? this.min.split(':')[0] : 0
      const maxHour = this.max ? this.max.split(':')[0] : 23

      return val => {
        return val >= minHour * 1 &&
          val <= maxHour * 1 &&
          (!this.allowedHours || this.allowedHours(val))
      }
    },
    isAllowedMinuteCb () {
      const isHourAllowed = !this.allowedHours || this.allowedHours(this.inputHour)
      if (!this.min && !this.max) {
        return isHourAllowed ? this.allowedMinutes : () => false
      }

      const [minHour, minMinute] = this.min ? this.min.split(':') : [0, 0]
      const [maxHour, maxMinute] = this.max ? this.max.split(':') : [23, 59]
      const minTime = minHour * 60 + minMinute * 1
      const maxTime = maxHour * 60 + maxMinute * 1

      return val => {
        const time = 60 * this.inputHour + val
        return time >= minTime &&
          time <= maxTime &&
          isHourAllowed &&
          (!this.allowedMinutes || this.allowedMinutes(val))
      }
    },
    isAmPm () {
      return this.format === 'ampm'
    }
  },

  watch: {
    value: 'setInputData'
  },

  methods: {
    emitValue () {
      if (this.inputHour != null && this.inputMinute != null) {
        this.$emit('input', `${pad(this.inputHour)}:${pad(this.inputMinute)}`)
      }
    },
    setPeriod (period) {
      this.period = period
      if (this.inputHour != null) {
        const newHour = this.inputHour + (period === 'am' ? -12 : 12)
        this.inputHour = this.firstAllowed('hour', newHour)
        this.emitValue()
      }
    },
    setInputData (value) {
      if (value == null) {
        this.inputHour = null
        this.inputMinute = null
        return
      }

      if (value instanceof Date) {
        this.inputHour = value.getHours()
        this.inputMinute = value.getMinutes()
      } else {
        const [, hour, minute, , period] = value.trim().toLowerCase().match(/^(\d+):(\d+)(:\d+)?([ap]m)?$/, '') || []

        this.inputHour = period ? this.convert12to24(parseInt(hour, 10), period) : parseInt(hour, 10)
        this.inputMinute = parseInt(minute, 10)
      }

      this.period = this.inputHour < 12 ? 'am' : 'pm'
    },
    convert24to12 (hour) {
      return hour ? ((hour - 1) % 12 + 1) : 12
    },
    convert12to24 (hour, period) {
      return hour % 12 + (period === 'pm' ? 12 : 0)
    },
    onInput (value) {
      if (this.selectingHour) {
        this.inputHour = this.isAmPm ? this.convert12to24(value, this.period) : value
      } else {
        this.inputMinute = value
      }
      this.emitValue()
    },
    onChange () {
      if (!this.selectingHour) {
        this.$emit('change', this.value)
      }

      this.selectingHour = !this.selectingHour
    },
    firstAllowed (type, value) {
      const allowedFn = type === 'hour' ? this.isAllowedHourCb : this.isAllowedMinuteCb
      if (!allowedFn) return value

      // TODO: clean up
      const range = type === 'minute'
        ? rangeMinutes
        : (this.isAmPm
          ? (value < 12
            ? rangeHours12am
            : rangeHours12pm)
          : rangeHours24)
      const first = range.find(v => allowedFn((v + value) % range.length + range[0]))
      return ((first || 0) + value) % range.length + range[0]
    },
    genClock () {
      return this.$createElement(VTimePickerClock, {
        props: {
          allowedValues: this.selectingHour ? this.isAllowedHourCb : this.isAllowedMinuteCb,
          color: this.color,
          dark: this.dark,
          double: this.selectingHour && !this.isAmPm,
          format: this.selectingHour ? (this.isAmPm ? this.convert24to12 : val => val) : val => pad(val, 2),
          light: this.light,
          max: this.selectingHour ? (this.isAmPm && this.period === 'am' ? 11 : 23) : 59,
          min: this.selectingHour && this.isAmPm && this.period === 'pm' ? 12 : 0,
          scrollable: this.scrollable,
          size: this.width - ((!this.fullWidth && this.landscape) ? 80 : 20),
          step: this.selectingHour ? 1 : 5,
          value: this.selectingHour ? this.inputHour : this.inputMinute
        },
        on: {
          input: this.onInput,
          change: this.onChange
        },
        ref: 'clock'
      })
    },
    genPickerBody () {
      return this.$createElement('div', {
        staticClass: 'v-time-picker-clock__container',
        style: {
          width: `${this.width}px`,
          height: `${this.width - ((!this.fullWidth && this.landscape) ? 60 : 0)}px`
        },
        key: this.selectingHour
      }, [this.genClock()])
    },
    genPickerTitle () {
      return this.$createElement(VTimePickerTitle, {
        props: {
          ampm: this.isAmPm,
          hour: this.inputHour,
          minute: this.inputMinute,
          period: this.period,
          selectingHour: this.selectingHour
        },
        on: {
          'update:selectingHour': value => (this.selectingHour = value),
          'update:period': this.setPeriod
        },
        ref: 'title',
        slot: 'title'
      })
    }
  },

  mounted () {
    this.setInputData(this.value)
  },

  render () {
    return this.genPicker('v-picker--time')
  }
}


import Vue from 'vue'
import Touch from '@/directives/touch'
import { test, touch } from '@/test'

test('touch.js', ({ mount }) => {
  function create(value) {
    return mount(Vue.component('test', {
      directives: { Touch },
      render: h => h('div', {
        directives: [{
          name: 'touch',
          value
        }]
      })
    }))
  }

  it('should call directive handlers', async () => {
    const down = jest.fn()
    touch(create({ down })).start(0, 0).end(0, 20)
    expect(down).toBeCalled()

    const up = jest.fn()
    touch(create({ up })).start(0, 0).end(0, -20)
    expect(up).toBeCalled()

    const left = jest.fn()
    touch(create({ left })).start(0, 0).end(-20, 0)
    expect(left).toBeCalled()

    const right = jest.fn()
    touch(create({ right })).start(0, 0).end(20, 0)
    expect(right).toBeCalled()

    const start = jest.fn()
    touch(create({ start })).start(0, 0)
    expect(start).toBeCalled()

    const move = jest.fn()
    touch(create({ move })).move(0, 0)
    expect(move).toBeCalled()

    const end = jest.fn()
    touch(create({ end })).end(0, 0)
    expect(end).toBeCalled()
  })

  it('should call directive handlers if not straight down/up/right/left', async () => {
    const nope = jest.fn()
    const down = jest.fn()
    touch(create({ down, right: nope })).start(0, 0).end(5, 20)
    expect(nope).not.toBeCalled()
    expect(down).toBeCalled()
  })

  it('should not call directive handlers if distance is too small ', async () => {
    const down = jest.fn()
    touch(create({ down })).start(0, 0).end(0, 10)
    expect(down).not.toBeCalled()

    const up = jest.fn()
    touch(create({ up })).start(0, 0).end(0, -10)
    expect(up).not.toBeCalled()

    const left = jest.fn()
    touch(create({ left })).start(0, 0).end(-10, 0)
    expect(left).not.toBeCalled()

    const right = jest.fn()
    touch(create({ right })).start(0, 0).end(10, 0)
    expect(right).not.toBeCalled()
  })

  it('should unbind', async () => {
    const start = jest.fn()
    const wrapper = create({ start })

    Touch.unbind(wrapper.element, { value: {} }, { context: wrapper.vm })

    touch(wrapper).start(0, 0)
    expect(start.mock.calls).toHaveLength(0)
  })
})


import VList from '@/components/VList'
import { test } from '@/test'

// TODO: Test actual behaviour instead of classes
test('VList.js', ({ mount }) => {
  it('should render component and match snapshot', () => {
    const wrapper = mount(VList)

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

  it('should render a dense component and match snapshot', () => {
    const wrapper = mount(VList, {
      propsData: {
        dense: true
      }
    })

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

  it('should render a subheader component and match snapshot', () => {
    const wrapper = mount(VList, {
      propsData: {
        subheader: true
      }
    })

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

  it('should render a threeLine component and match snapshot', () => {
    const wrapper = mount(VList, {
      propsData: {
        threeLine: true
      }
    })

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

  it('should render a twoLine component and match snapshot', () => {
    const wrapper = mount(VList, {
      propsData: {
        twoLine: true
      }
    })

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


import { test } from '@/test'
import VAutocomplete from '@/components/VAutocomplete'

test('VAutocomplete.js', ({ mount, shallow }) => {
  const app = document.createElement('div')
  app.setAttribute('data-app', true)
  document.body.appendChild(app)

  it('should allow changing of browser autocomplete', async () => {
    const wrapper = shallow(VAutocomplete, {
      propsData: {
        browserAutocomplete: 'on'
      }
    })

    const input = wrapper.first('input')

    expect(input.element.getAttribute('autocomplete')).toBe('on')

    wrapper.setProps({ browserAutocomplete: 'off' })

    await wrapper.vm.$nextTick()

    expect(input.element.getAttribute('autocomplete')).toBe('off')
  })

  it('should have explicit tabindex passed through when autocomplete', () => {
    const wrapper = shallow(VAutocomplete, {
      propsData: {
        tabindex: 10
      }
    })

    expect(wrapper.vm.$refs.input.tabIndex).toBe(10)
    expect(wrapper.vm.$el.tabIndex).toBe(-1)
  })

  it('should emit search input changes', async () => {
    const wrapper = shallow(VAutocomplete, {
      propsData: {
      }
    })

    const input = wrapper.first('input')

    const update = jest.fn()
    wrapper.vm.$on('update:searchInput', update)

    input.element.value = 'test'
    input.trigger('input')

    await wrapper.vm.$nextTick()

    expect(update).toBeCalledWith('test')
  })

  it('should filter autocomplete search results', async () => {
    const wrapper = shallow(VAutocomplete, {
      propsData: { items: ['foo', 'bar'] }
    })

    wrapper.setData({ internalSearch: 'foo' })

    expect(wrapper.vm.filteredItems).toHaveLength(1)
    expect(wrapper.vm.filteredItems[0]).toBe('foo')
  })

  it('should filter numeric primitives', () => {
    const wrapper = shallow(VAutocomplete, {
      propsData: {
        items: [1, 2]
      }
    })

    wrapper.setData({ internalSearch: 1 })

    expect(wrapper.vm.filteredItems).toHaveLength(1)
    expect(wrapper.vm.filteredItems[0]).toBe(1)
  })

  it('should activate when search changes and not active', async () => {
    const wrapper = shallow(VAutocomplete, {
      propsData: {
        items: [1, 2, 3, 4],
        multiple: true
      }
    })

    wrapper.vm.isMenuActive = true
    await wrapper.vm.$nextTick()
    wrapper.setData({ internalSearch: 2 })
    await wrapper.vm.$nextTick()

    expect(wrapper.vm.isMenuActive).toBe(true)
  })

  it('should set searchValue to null when deactivated', async () => {
    const wrapper = shallow(VAutocomplete, {
      propsData: {
        items: [1, 2, 3, 4],
        multiple: true
      }
    })

    await wrapper.vm.$nextTick()

    const input = wrapper.first('input')

    input.trigger('focus')
    input.element.value = 2
    input.trigger('input')

    expect(wrapper.vm.internalSearch).toBe('2')

    wrapper.setProps({
      multiple: false,
      value: 1
    })

    await wrapper.vm.$nextTick()

    expect(wrapper.vm.internalSearch).toBe(1)

    input.trigger('focus')
    input.element.value = 3
    input.trigger('input')
    input.trigger('blur')

    expect(wrapper.vm.internalSearch).toBe('3')
  })

  it('should render role=combobox correctly when autocomplete', async () => {
    const wrapper = shallow(VAutocomplete)

    expect(wrapper.vm.$el.getAttribute('role')).toBeFalsy()

    const input = wrapper.first('input')
    expect(input.element.getAttribute('role')).toBe('combobox')
  })

  it('should not duplicate items after items update when caching is turned on', async () => {
    const wrapper = shallow(VAutocomplete, {
      propsData: {
        cacheItems: true,
        returnObject: true,
        itemText: 'text',
        itemValue: 'id',
        items: [],
      }
    })

    wrapper.setProps({ items: [{ id: 1, text: 'A' }] })
    expect(wrapper.vm.computedItems).toHaveLength(1)
    wrapper.setProps({ items: [{ id: 1, text: 'A' }] })
    expect(wrapper.vm.computedItems).toHaveLength(1)
  })

  it('should cache items', async () => {
    const wrapper = shallow(VAutocomplete, {
      propsData: { cacheItems: true }
    })

    wrapper.setProps({ items: ['bar', 'baz'] })
    expect(wrapper.vm.computedItems).toHaveLength(2)

    wrapper.setProps({ items: ['foo'] })
    expect(wrapper.vm.computedItems).toHaveLength(3)

    wrapper.setProps({ items: ['bar'] })
    expect(wrapper.vm.computedItems).toHaveLength(3)
  })

  it('should cache items passed via prop', async () => {
    const wrapper = shallow(VAutocomplete, {
      propsData: {
        cacheItems: true,
        items: [1, 2, 3, 4]
      }
    })

    expect(wrapper.vm.computedItems).toHaveLength(4)

    wrapper.setProps({ items: [5] })

    expect(wrapper.vm.computedItems).toHaveLength(5)
  })

  it('should show input when focused and autocomplete', async () => {
    const wrapper = shallow(VAutocomplete)

    const input = wrapper.first('input')

    expect(input.hasStyle('display', 'none'))

    wrapper.trigger('focus')

    expect(input.hasStyle('display', 'block'))
  })

  it('should not filter text with no items', async () => {
    const wrapper = shallow(VAutocomplete, {
      propsData: {
        items: ['foo', 'bar']
      }
    })

    await wrapper.vm.$nextTick()

    wrapper.setProps({ searchInput: 'asdf' })

    // Wait for watcher
    await wrapper.vm.$nextTick()

    const tile = wrapper.first('.v-list__tile__title')

    expect(tile.text()).toBe('No data available')
  })

  it('should not display menu when tab focused', async () => {
    const wrapper = shallow(VAutocomplete, {
      propsData: {
        items: [1 ,2],
        value: 1
      }
    })

    const input = wrapper.first('input')
    input.trigger('focus')
    await wrapper.vm.$nextTick()
    expect(wrapper.vm.isMenuActive).toBe(false)

    wrapper.setProps({
      items: [
        { text: 'Foo', value: 1 },
        { text: 'Bar', value: 2 }
      ]
    })

    input.trigger('blur')
    await wrapper.vm.$nextTick()

    input.trigger('focus')
    await wrapper.vm.$nextTick()

    expect(wrapper.vm.isMenuActive).toBe(false)
  })

  it('should emit custom value on blur', async () => {
    const wrapper = shallow(VAutocomplete, {
      propsData: { combobox: true }
    })

    const input = wrapper.first('input')

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

    input.trigger('focus')
    await wrapper.vm.$nextTick()

    input.element.value = 'foo'
    input.trigger('input')

    input.trigger('blur')
    expect(change).toHaveBeenCalledWith('foo')

    input.trigger('keydown.esc')
    expect(wrapper.vm.isMenuActive).toBe(false)

    input.element.value = ''
    input.trigger('input')

    await wrapper.vm.$nextTick()
    expect(wrapper.vm.isMenuActive).toBe(false)
  })

  it('should change selected index', async () => {
    const wrapper = shallow(VAutocomplete, {
      attachToDocument: true,
      propsData: {
        items: ['foo', 'bar', 'fizz'],
        multiple: true,
        value: ['foo', 'bar', 'fizz']
      }
    })

    await wrapper.vm.$nextTick()

    expect(wrapper.vm.selectedIndex).toBe(-1)
    expect(wrapper.vm.selectedItems.length).toBe(3)

    // Right arrow
    wrapper.vm.changeSelectedIndex(39)
    expect(wrapper.vm.selectedIndex).toBe(0)

    wrapper.vm.changeSelectedIndex(39)
    expect(wrapper.vm.selectedIndex).toBe(1)

    wrapper.vm.changeSelectedIndex(39)
    expect(wrapper.vm.selectedIndex).toBe(2)

    // Left arrow
    wrapper.vm.changeSelectedIndex(37)
    expect(wrapper.vm.selectedIndex).toBe(1)

    wrapper.vm.changeSelectedIndex(37)
    expect(wrapper.vm.selectedIndex).toBe(0)

    wrapper.vm.changeSelectedIndex(37)
    expect(wrapper.vm.selectedIndex).toBe(-1)

    wrapper.vm.changeSelectedIndex(37)
    expect(wrapper.vm.selectedIndex).toBe(2)

    wrapper.vm.changeSelectedIndex(37)
    expect(wrapper.vm.selectedIndex).toBe(1)

    // Delete key
    wrapper.vm.changeSelectedIndex(8)
    await wrapper.vm.$nextTick()
    expect(wrapper.vm.selectedIndex).toBe(1)

    wrapper.vm.changeSelectedIndex(37)
    expect(wrapper.vm.selectedIndex).toBe(0)

    wrapper.vm.changeSelectedIndex(8)
    await wrapper.vm.$nextTick()
    expect(wrapper.vm.selectedIndex).toBe(0)

    // Should not change index if search is dirty
    wrapper.setProps({ searchInput: 'foo' })
    wrapper.vm.changeSelectedIndex(8)

    expect(wrapper.vm.selectedIndex).toBe(0)
    expect(wrapper.vm.selectedItems.length).toBe(1)

    wrapper.setProps({ searchInput: undefined })

    // Should not proceed if keyCode doesn't match
    wrapper.vm.changeSelectedIndex(99)
    await wrapper.vm.$nextTick()

    expect(wrapper.vm.selectedIndex).toBe(0)
    expect(wrapper.vm.selectedItems.length).toBe(1)

    wrapper.vm.changeSelectedIndex(8)
    await wrapper.vm.$nextTick()

    expect(wrapper.vm.selectedItems.length).toBe(0)
    expect(wrapper.vm.selectedIndex).toBe(-1)

    // Should not change/error if called with no selection
    wrapper.vm.changeSelectedIndex(8)
    await wrapper.vm.$nextTick()

    expect(wrapper.vm.selectedIndex).toBe(-1)

    wrapper.setProps({ value: ['foo', 'bar', 'fizz'] })

    await wrapper.vm.$nextTick()

    expect(wrapper.vm.selectedItems.length).toBe(3)

    wrapper.vm.selectedIndex = 2

    // Simulating removing items when an index already selected
    wrapper.setProps({ value: ['foo', 'bar'] })

    await wrapper.vm.$nextTick()

    expect(wrapper.vm.selectedIndex).toBe(2)

    // Backspace
    wrapper.vm.changeSelectedIndex(46)
    expect(wrapper.vm.selectedIndex).toBe(-1)
  })

  it('should conditionally show the menu', async () => {
    const wrapper = mount(VAutocomplete, {
      attachToDocument: true,
      propsData: {
        items: ['foo', 'bar', 'fizz']
      }
    })

    const slot = wrapper.first('.v-input__slot')
    const input = wrapper.first('input')

    // Focus input should only focus
    input.trigger('focus')

    expect(wrapper.vm.isFocused).toBe(true)
    expect(wrapper.vm.menuCanShow).toBe(true)
    expect(wrapper.vm.isMenuActive).toBe(false)

    // Clicking input should open menu
    slot.trigger('click')

    expect(wrapper.vm.isMenuActive).toBe(true)
    expect(wrapper.vm.menuCanShow).toBe(true)

    wrapper.setProps({ searchInput: 'foo' })

    expect(wrapper.vm.isMenuActive).toBe(true)
    expect(wrapper.vm.menuCanShow).toBe(true)

    // Should close menu but keep focus
    input.trigger('keydown.esc')

    expect(wrapper.vm.isFocused).toBe(true)
    expect(wrapper.vm.isMenuActive).toBe(false)
    expect(wrapper.vm.menuCanShow).toBe(true)

    // Should not show menu
    wrapper.setProps({
      combobox: true,
      searchInput: 'foobar'
    })

    expect(wrapper.vm.menuCanShow).toBe(false)

    slot.trigger('click')

    expect(wrapper.vm.isMenuActive).toBe(false)

    // TODO: Add expects for tags when impl
  })

  it('should have the correct selected item', async () => {
    const wrapper = shallow(VAutocomplete, {
      propsData: {
        items: ['foo', 'bar', 'fizz'],
        multiple: true,
        value: ['foo']
      }
    })

    expect(wrapper.vm.selectedItem).toBe(null)

    wrapper.setProps({
      multiple: false,
      value: 'foo'
    })

    expect(wrapper.vm.selectedItem).toBe('foo')
  })

  it('should reset lazySearch', async () => {
    const wrapper = shallow(VAutocomplete, {
      propsData: {
        chips: true,
        items: ['foo', 'bar', 'fizz'],
        searchInput: 'foo'
      }
    })

    expect(wrapper.vm.lazySearch).toBe('foo')
    expect(wrapper.vm.hasSlot).toBe(true)

    wrapper.setData({ isMenuActive: true })
    await wrapper.vm.$nextTick()
    wrapper.setData({ isMenuActive: false })
    await wrapper.vm.$nextTick()

    expect(wrapper.vm.lazySearch).toBe(null)
  })

  it('should call methods on blur', () => {
    const updateTags = jest.fn()
    const updateCombobox = jest.fn()
    const updateAutocomplete = jest.fn()
    const wrapper = shallow(VAutocomplete, {
      methods: {
        updateAutocomplete,
        updateCombobox,
        updateTags
      }
    })

    wrapper.vm.onEnterDown()

    expect(updateAutocomplete).toHaveBeenCalledTimes(1)

    wrapper.setProps({ combobox: true })

    wrapper.vm.onEnterDown()

    expect(updateCombobox).toHaveBeenCalledTimes(1)

    wrapper.setProps({
      combobox: false,
      tags: true
    })

    wrapper.vm.onEnterDown()

    expect(updateTags).toHaveBeenCalledTimes(1)
  })

  it('should select input text on focus', async () => {
    const wrapper = mount(VAutocomplete)
    const select = jest.fn()
    wrapper.vm.$refs.input.select = select

    const input = wrapper.first('input')
    input.trigger('focus')

    await wrapper.vm.$nextTick()

    expect(wrapper.vm.isFocused).toBe(true)
    expect(select).toHaveBeenCalledTimes(1)

    input.trigger('keydown.tab')

    await wrapper.vm.$nextTick()

    expect(wrapper.vm.isFocused).toBe(false)
    expect(select).toHaveBeenCalledTimes(1)
  })

  it('should not respond to click', () => {
    const onFocus = jest.fn()
    const wrapper = mount(VAutocomplete, {
      propsData: { disabled: true },
      methods: { onFocus }
    })
    const slot = wrapper.first('.v-input__slot')

    slot.trigger('click')

    expect(onFocus).not.toBeCalled()

    wrapper.setProps({ disabled: false, readonly: true })

    slot.trigger('click')

    expect(onFocus).not.toBeCalled()

    wrapper.setProps({ readonly: false })

    slot.trigger('click')

    expect(onFocus).toBeCalled()
  })

  it('should react to tabs', async () => {
    const updateTags = jest.fn()
    const wrapper = mount(VAutocomplete, {
      propsData: {
        items: ['fizz', 'buzz'],
        tags: true
      },
      methods: {
        updateTags
      }
    })

    const input = wrapper.first('input')
    const menu = wrapper.first('.v-menu')
    const tile = wrapper.first('.v-list__tile')

    input.trigger('focus')
    input.element.value = 'foo'
    input.trigger('input')
    input.trigger('keydown.tab')

    expect(wrapper.vm.getMenuIndex()).toBe(-1)
    expect(updateTags).toBeCalled()

    input.trigger('focus')
    input.element.value = 'fizz'
    input.trigger('input')
    menu.trigger('keydown.down')

    // Allow dom to update class for
    // selected tile
    await wrapper.vm.$nextTick()

    expect(wrapper.vm.isMenuActive).toBe(true)
    expect(wrapper.vm.getMenuIndex()).toBe(0)

    input.trigger('keydown.tab')

    // We overwrite update tags so above
    // is does not persist
    expect(wrapper.vm.internalValue).toEqual(['fizz'])
    expect(updateTags).toHaveBeenCalledTimes(2)
  })

  it('should react to keydown', () => {
    const activateMenu = jest.fn()
    const changeSelectedIndex = jest.fn()
    const onEscDown = jest.fn()
    const onTabDown = jest.fn()
    const wrapper = mount(VAutocomplete, {
      methods: {
        activateMenu,
        changeSelectedIndex,
        onEscDown,
        onTabDown
      }
    })

    const input = wrapper.first('input')

    expect(wrapper.vm.isMenuActive).toBe(false)

    input.trigger('keydown.enter')
    input.trigger('keydown.space')
    input.trigger('keydown.up')
    input.trigger('keydown.down')

    expect(activateMenu).toHaveBeenCalledTimes(4)

    input.trigger('keydown.esc')

    expect(onEscDown).toHaveBeenCalledTimes(1)

    input.trigger('keydown.tab')

    expect(onTabDown).toHaveBeenCalledTimes(1)

    // Skip menu activation
    wrapper.setData({ isMenuActive: true })

    input.element.value = 'foo'
    input.trigger('input')

    wrapper.setProps({ hideSelections: true })

    expect(wrapper.vm.genSelections()).toEqual([])
  })

  // https://github.com/vuetifyjs/vuetify/issues/3793
  it('should reset menu index after selection', async () => {
    const wrapper = shallow(VAutocomplete, {
      propsData: {
        items: ['foo', 'bar'],
        value: 'foo'
      }
    })

    expect(wrapper.vm.isMenuActive).toBe(false)
    const slot = wrapper.first('.v-input__slot')
    const item = wrapper.first('.v-list__tile')
    slot.trigger('click')

    expect(wrapper.vm.isMenuActive).toBe(true)

    expect(wrapper.vm.getMenuIndex()).toBe(-1)
  })

  it('should not remove a disabled item', () => {
    const wrapper = mount(VAutocomplete, {
      propsData: {
        chips: true,
        multiple: true,
        items: [
          { text: 'foo', value: 'foo', disabled: true },
          { text: 'bar', value: 'bar' },
        ],
        value: ['foo', 'bar']
      }
    })

    const chips = wrapper.find('.v-chip')
    const input = wrapper.first('input')

    expect(chips[0].element.classList.contains('v-chip--disabled')).toBe(true)

    input.trigger('focus')
    input.trigger('keydown.left')

    expect(wrapper.vm.selectedIndex).toBe(1)

    input.trigger('keydown.delete')

    expect(wrapper.vm.internalValue).toEqual(['foo'])

    input.trigger('keydown.delete')

    expect(wrapper.vm.internalValue).toEqual(['foo'])
  })

  it('should not filter results', async () => {
    const wrapper = mount(VAutocomplete, {
      propsData: {
        items: ['foo', 'bar']
      }
    })

    const input = wrapper.first('input')
    input.element.value = 'foo'
    input.trigger('input')

    expect(wrapper.vm.filteredItems.length).toBe(1)

    wrapper.setProps({ noFilter: true })

    await wrapper.vm.$nextTick()

    expect(wrapper.vm.filteredItems.length).toBe(2)
  })

  it('should hide menu when no data', async () => {
    const wrapper = mount(VAutocomplete)

    const input = wrapper.first('input')
    input.trigger('focus')
    input.element.value = 'foo'
    input.trigger('input')

    expect(wrapper.vm.menuCanShow).toBe(true)

    wrapper.setProps({ hideNoData: true })

    await wrapper.vm.$nextTick()

    expect(wrapper.vm.menuCanShow).toBe(false)

    wrapper.setProps({ hideNoData: false })

    await wrapper.vm.$nextTick()

    expect(wrapper.vm.menuCanShow).toBe(true)

    // If we are hiding selected
    // filtered will have a positive length
    // but the hidden items will not show
    // check to make sure when all values are
    // selected to close the menu
    wrapper.setProps({
      hideNoData: true,
      hideSelected: true,
      items: [1,2,3,4],
      multiple: true,
      value: [1,2,3]
    })

    await wrapper.vm.$nextTick()

    expect(wrapper.vm.menuCanShow).toBe(true)

    wrapper.setProps({ value: [1,2,3,4] })

    await wrapper.vm.$nextTick()

    expect(wrapper.vm.menuCanShow).toBe(false)
  })

  // https://github.com/vuetifyjs/vuetify/issues/2834
  it('should not update search if selectedIndex is > -1', () => {
    const wrapper = mount(VAutocomplete)

    const input = wrapper.first('input')

    input.trigger('focus')
    input.element.value = 'foo'
    input.trigger('input')

    expect(wrapper.vm.internalSearch).toBe('foo')

    wrapper.setData({
      lazySearch: '',
      selectedIndex: 0
    })

    expect(wrapper.vm.internalSearch).toBe('')

    input.element.value = 'bar'
    input.trigger('input')

    expect(wrapper.vm.internalSearch).toBe('')
  })

  it('should clear search input on clear callback', async () => {
    const wrapper = mount(VAutocomplete, {
      propsData: {
        clearable: true
      }
    })

    const icon = wrapper.first('.v-input__append-inner .v-icon')
    const input = wrapper.first('input')

    input.element.value = 'foobar'
    input.trigger('input')

    expect(wrapper.vm.internalSearch).toBe('foobar')

    icon.trigger('click')

    expect(wrapper.vm.internalSearch).toBe(null)
  })

  it('should propagate content class', () => {
    const wrapper = mount(VAutocomplete, {
      propsData: { contentClass: 'foobar' }
    })

    const content = wrapper.first('.v-autocomplete__content')

    expect(content.element.classList.contains('foobar')).toBe(true)
  })
})


const allowedFn = type === 'hour' ? this.isAllowedHourCb : this.isAllowedMinuteCb


@import '../bootstrap'

.v-dialog
  elevation(24)
  border-radius: $card-border-radius
  margin: 24px
  overflow-y: auto
  pointer-events: auto
  transition: .3s $transition.fast-in-fast-out
  width: 100%
  z-index: inherit

  &__content
    align-items: center
    display: flex
    height: 100%
    justify-content: center
    left: 0
    pointer-events: none
    position: fixed
    top: 0
    transition: .2s $transition.fast-in-fast-out
    width: 100%
    z-index: 6
    outline: none

  &:not(.v-dialog--fullscreen)
    max-height: 90%

  &__container
    display: inline-block
    vertical-align: middle

  @keyframes animate-dialog
    0%
      transform: scale(1)

    50%
      transform: scale(1.03)

    100%
      transform: scale(1)

  &--animated
    animation-duration: .15s
    animation-name: animate-dialog
    animation-timing-function: $transition.fast-in-fast-out

  &--fullscreen
    margin 0
    height 100%
    position fixed
    overflow-y: auto
    top: 0
    left 0

    > .v-card
      min-height 100%
      min-width 100%
      margin 0 !important
      padding 0 !important

  &--scrollable
    display: flex

    > .v-card
      display: flex
      flex: 1 1 auto
      flex-direction: column

      > .v-card__title
      > .v-card__actions
        flex: 1 0 auto

      > .v-card__text
        overflow-y: auto
        backface-visibility: hidden


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

/* Theme */
v-slider($material)
  .v-slider__track,
  .v-slider__track-fill
    background: $material.slider.inactive

  .v-slider__track__container
    &:after
      border: 1px solid $material.text.primary

  .v-slider__ticks
    border-color: $material.text.primary
    border-style: solid
    color: $material.text.secondary

  &:not(.v-input--is-dirty)
    .v-slider__thumb-label
      background: $material.slider.inactive

    .v-slider__thumb
      border: 3px solid $material.slider.inactive

    &.v-input--slider--is-active
      .v-slider__thumb
        border: 3px solid $material.slider.active

    .v-slider__ticks:first-child
      border-color: transparent

  &.v-input--is-disabled
    .v-slider__thumb
      border: 5px solid $material.slider.disabled

  &.v-input--is-disabled.v-input--is-dirty
    .v-slider__thumb
      background: $material.slider.disabled
      border: 0px solid transparent

  &.v-input--slider--is-active
    .v-slider__track
      background: $material.slider.active

v-slider-rtl()
  .v-label
    margin-left: 16px
    margin-right: 0

theme(v-slider, "v-input--slider")
rtl(v-slider-rtl, "v-input--slider")

/** Input Group */
.v-input--slider
  &:not(.v-input--slider--thumb-label)
    margin-top: 0

  &.v-input--is-focused
    .v-slider__thumb-container--is-active:not(.v-slider__thumb-container--show-label)
      &:before
        opacity: .2
        transform: scale(1)

    .v-slider__track
      transition: none

    &.v-input--slider--ticks
      .v-slider
        &__track__container:after,
        .v-slider__tick
          opacity: 1

  &.v-input--slider--ticks
    .v-slider__ticks.v-slider__ticks--always-show
      opacity: 1

    &-labels
      .v-input__slot
        margin-bottom: 16px

  &.v-input--is-readonly
    pointer-events: none

  &.v-input--is-disabled
    .v-slider__thumb
      transform: translateY(-50%) scale(.45)

  .v-input__slot > *:first-child:not(:only-child)
    margin-right: 16px

/** Slider */
.v-slider
  cursor: default
  display: flex
  align-items: center
  position: relative
  height: 32px
  flex: 1
  outline: none
  user-select: none

  input
    cursor: default
    opacity: 0
    padding: 0
    width: 100%
    font-size: 0
    min-height: 32px

/** Thumb/Track/Ticks */
.v-slider
  &__track__container
    height: 2px
    left: 0
    overflow: hidden
    pointer-events: none
    position: absolute
    top: 50%
    transform: translateY(-50%)
    width: 100%

    &:after
      content: ''
      position: absolute
      right: 0
      top: 0
      height: 2px
      transition: .3s $transition.swing
      width: 2px
      opacity: 0

  &__track,
  &__thumb,
  &__ticks
    position: absolute
    top: 0

  &__track
    height: 2px
    left: 0
    transition: .3s $transition.swing
    transform-origin: right
    overflow: hidden
    width: 100%

    &-fill
      position: absolute
      left: 0
      height: 2px
      transform-origin: left
      width: 100%
      transition: .3s $transition.swing

  &__ticks-container
    position: absolute
    left: 0
    height: 2px
    width: 100%
    top: 50%
    transform: translate(0, -50%)

  &__ticks
    opacity: 0
    transition: .3s $transition.swing

    > span
      position: absolute
      top: 8px
      transform: translateX(-50%)
      white-space: nowrap
      user-select: none

    &:first-child > span
      transform: translateX(0)

    &:last-child > span
      transform: translateX(-100%)

  &__thumb-container
    position: absolute
    top: 50%
    transition: .3s $transition.swing

    &:before
      content: ''
      color: inherit
      border-radius: 50%
      background: currentColor
      height: 32px
      left: -16px
      opacity: 0
      overflow: hidden
      pointer-events: none
      position: absolute
      top: -16px
      transform: scale(0.2)
      transition: $ripple-animation-transition
      width: 32px
      will-change: transform, opacity

  &__thumb
    width: 24px
    height: 24px
    left: -12px
    top: 50%
    border-radius: 50%
    background: transparent
    transition: .3s $transition.swing
    transform: translateY(-50%) scale(.6)
    user-select: none

/** Thumb Label */
.v-slider
  &--is-active
    .v-slider__thumb-container--is-active
      .v-slider__thumb
        transform: translateY(-50%) scale(1)

      &.v-slider__thumb-container--show-label
        .v-slider__thumb
          transform: translateY(-50%) scale(0)

    .v-slider__ticks-container
      .v-slider__ticks
        opacity: 1

  &__thumb-label__container
    position: absolute
    left: 0
    top: 0
    transition: .3s $transition.fast-in-fast-out

  &__thumb-label
    display: flex
    align-items: center
    justify-content: center
    font-size: 12px
    color: #fff
    width: 32px
    height: 32px
    border-radius: 50% 50% 0
    position: absolute
    left: 0
    bottom: 100%
    // Needed for IE11 which does not
    // process translateY(calc(-20% - 12px))
    // or translate(-50%, calc(-20% - 12px))
    transform: translateY(-20%) translateY(-12px) translateX(-50%) rotate(45deg)
    user-select: none
    transition: .3s $transition.fast-in-fast-out

    > *
      transform: rotate(-45deg)

  &__track,
  &__track-fill
    position: absolute


import '../../stylus/components/_grid.styl'

import Grid from './grid'

export default Grid('container')


it('should change selectingHour when clicked in title', () => {

keep keep keep keep replace keep keep keep keep keep


@import './app'
@import './components/*'


import VTooltip from '@/components/VTooltip'
import { test } from '@/test'

test('VTooltip.js', ({ mount, compileToFunctions }) => {
  it('should render component and match snapshot', async () => {
    const wrapper = mount(VTooltip, {
      propsData: {
        openDelay: 0
      },
      slots: {
        activator: [compileToFunctions('activator')],
        default: [compileToFunctions('content')]
      }
    })

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

    wrapper.setProps({
      value: true
    })
    await wrapper.vm.$nextTick()
    expect(wrapper.html()).toMatchSnapshot()

    expect('Unable to locate target [data-app]').toHaveBeenTipped()
  })

  it('should render component with custom lazy and match snapshot', () => {
    const wrapper = mount(VTooltip, {
      propsData: {
        lazy: true
      },
      slots: {
        activator: [compileToFunctions('activator')],
        default: [compileToFunctions('content')]
      }
    })

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

  it('should render component with value=true and match snapshot', async () => {
    const wrapper = mount(VTooltip, {
      propsData: {
        value: true
      },
      slots: {
        activator: [compileToFunctions('activator')],
        default: [compileToFunctions('content')]
      }
    })

    expect(wrapper.data().isActive).toBe(true)
    expect(wrapper.html()).toMatchSnapshot()
    expect('Unable to locate target [data-app]').toHaveBeenTipped()
  })

  it('should render component with zIndex prop and match snapshot', async () => {
    const wrapper = mount(VTooltip, {
      propsData: {
        zIndex: 42
      }
    })

    expect(wrapper.html()).toMatchSnapshot()
    expect('Unable to locate target [data-app]').toHaveBeenTipped()
  })

  it('should display tooltip after mouseenter and hide after mouseleave', async () => {
    jest.useFakeTimers()
    const wrapper = mount(VTooltip, {
      propsData: {
        openDelay: 123,
        closeDelay: 321
      },
      slots: {
        activator: [compileToFunctions('activator')],
        default: [compileToFunctions('content')]
      }
    })

    const activator = wrapper.find('.v-tooltip__content + span')[0]
    const cb = jest.fn()
    wrapper.instance().$on('input', cb)

    activator.trigger('mouseenter')
    jest.runAllTimers()
    await wrapper.vm.$nextTick()
    expect(setTimeout.mock.calls).toHaveLength(1)
    expect(setTimeout.mock.calls[0][1]).toBe(123)
    expect(cb).toBeCalledWith(true)

    activator.trigger('mouseleave')
    jest.runAllTimers()
    await wrapper.vm.$nextTick()
    expect(setTimeout.mock.calls).toHaveLength(2)
    expect(setTimeout.mock.calls[1][1]).toBe(321)
    expect(cb).toBeCalledWith(false)

    expect('Unable to locate target [data-app]').toHaveBeenTipped()
  })
})


import VExpansionPanel from './VExpansionPanel'
import VExpansionPanelContent from './VExpansionPanelContent'

export { VExpansionPanel, VExpansionPanelContent }

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

export default VExpansionPanel


API.v1.addRoute('federation.events.dispatch', { authRequired: false }, {

return API.v1.failure('Federation not enabled');

keep keep keep keep keep replace keep keep keep

API.v1.addRoute('federation.events.dispatch', { authRequired: false, rateLimiterOptions: { numRequestsAllowed: 30, intervalTimeInMS: 1000 } }, {

import { callbacks } from '../../../callbacks/server';

const callbackDefinitions = [];

export function registerCallback(callbackDefition) {
	callbackDefinitions.push(callbackDefition);
}

export function enableCallbacks() {
	for (const definition of callbackDefinitions) {
		callbacks.add(definition.hook, definition.callback, callbacks.priority.LOW, definition.id);
	}
}

export function disableCallbacks() {
	for (const definition of callbackDefinitions) {
		callbacks.remove(definition.hook, definition.id);
	}
}


/**
 * return readable time format from seconds
 * @param  {Double} sec seconds
 * @return {String}     Readable string format
 */
export const secondsToHHMMSS = (sec) => {
	sec = parseFloat(sec);

	let hours = Math.floor(sec / 3600);
	let minutes = Math.floor((sec - (hours * 3600)) / 60);
	let seconds = Math.round(sec - (hours * 3600) - (minutes * 60));

	if (hours < 10) { hours = `0${ hours }`; }
	if (minutes < 10) { minutes = `0${ minutes }`; }
	if (seconds < 10) { seconds = `0${ seconds }`; }

	if (hours > 0) {
		return `${ hours }:${ minutes }:${ seconds }`;
	}
	if (minutes > 0) {
		return `00:${ minutes }:${ seconds }`;
	}
	return `00:00:${ seconds }`;
};


const { execSync } = require('child_process');

console.log('Running npm-postinstall.js');

execSync('cp node_modules/katex/dist/katex.min.css app/katex/');

execSync('mkdir -p public/fonts/');
execSync('cp node_modules/katex/dist/fonts/* public/fonts/');

execSync('cp node_modules/pdfjs-dist/build/pdf.worker.min.js public/');


import { FederationRoomEvents, Rooms } from '../../../models/server';
import { logger } from '../lib/logger';
import { hasExternalDomain } from '../functions/helpers';
import { getFederationDomain } from '../lib/getFederationDomain';
import { dispatchEvent } from '../handler';

async function afterDeleteMessage(message) {
	const room = Rooms.findOneById(message.rid, { fields: { federation: 1 } });

	// If there are not federated users on this room, ignore it
	if (!hasExternalDomain(room)) { return message; }

	logger.client.debug(() => `afterDeleteMessage => message=${ JSON.stringify(message, null, 2) } room=${ JSON.stringify(room, null, 2) }`);

	// Create the delete message event
	const event = await FederationRoomEvents.createDeleteMessageEvent(getFederationDomain(), room._id, message._id);

	// Dispatch event (async)
	dispatchEvent(room.federation.domains, event);

	return message;
}

export const definition = {
	hook: 'afterDeleteMessage',
	callback: (message) => Promise.await(afterDeleteMessage(message)),
	id: 'federation-after-delete-message',
};


keep keep keep replace replace replace replace keep keep keep

import qs from 'querystring';

import { disabled } from '../functions/errors';
import { logger } from '../lib/logger';
import { isFederationEnabled } from '../lib/isFederationEnabled';
import { federationRequestToPeer } from '../lib/http';

export function federationSearchUsers(query) {
	if (!isFederationEnabled()) {
		throw disabled('client.searchUsers');
	}

	logger.client.debug(() => `searchUsers => query=${ query }`);

	const [username, peerDomain] = query.split('@');

	const uri = `/api/v1/federation.users.search?${ qs.stringify({ username, domain: peerDomain }) }`;

	const { data: { users } } = federationRequestToPeer('GET', peerDomain, uri);

	return users;
}

export function getUserByUsername(query) {
	if (!isFederationEnabled()) {
		throw disabled('client.searchUsers');
	}

	logger.client.debug(() => `getUserByUsername => query=${ query }`);

	const [username, peerDomain] = query.split('@');

	const uri = `/api/v1/federation.users.getByUsername?${ qs.stringify({ username }) }`;

	const { data: { user } } = federationRequestToPeer('GET', peerDomain, uri);

	return user;
}

export function requestEventsFromLatest(domain, fromDomain, contextType, contextQuery, latestEventIds) {
	if (!isFederationEnabled()) {
		throw disabled('client.requestEventsFromLatest');
	}

	logger.client.debug(() => `requestEventsFromLatest => domain=${ domain } contextType=${ contextType } contextQuery=${ JSON.stringify(contextQuery, null, 2) } latestEventIds=${ latestEventIds.join(', ') }`);

	const uri = '/api/v1/federation.events.requestFromLatest';

	federationRequestToPeer('POST', domain, uri, { fromDomain, contextType, contextQuery, latestEventIds });
}

export function dispatchEvents(domains, events) {
	if (!isFederationEnabled()) {
		throw disabled('client.dispatchEvents');
	}

	domains = [...new Set(domains)];

	logger.client.debug(() => `dispatchEvents => domains=${ domains.join(', ') } events=${ events.map((e) => JSON.stringify(e, null, 2)) }`);

	const uri = '/api/v1/federation.events.dispatch';

	for (const domain of domains) {
		federationRequestToPeer('POST', domain, uri, { events }, { ignoreErrors: true });
	}
}

export function dispatchEvent(domains, event) {
	// Ensure the domain list is distinct to avoid excessive events
	const distinctDomains = [...new Set(domains)].filter((domain) => domain === event.origin);

	dispatchEvents(distinctDomains, [event]);
}

export function getUpload(domain, fileId) {
	const { data: { upload, buffer } } = federationRequestToPeer('GET', domain, `/api/v1/federation.uploads?${ qs.stringify({ upload_id: fileId }) }`);

	return { upload, buffer: Buffer.from(buffer) };
}


import { Meteor } from 'meteor/meteor';
import { Tracker } from 'meteor/tracker';

import { OTR } from './rocketchat.otr';
import { settings } from '../../settings';
import { TabBar } from '../../ui-utils';

Meteor.startup(function() {
	Tracker.autorun(function() {
		if (settings.get('OTR_Enable') && window.crypto) {
			OTR.crypto = window.crypto.subtle || window.crypto.webkitSubtle;
			OTR.enabled.set(true);
			TabBar.addButton({
				groups: ['direct'],
				id: 'otr',
				i18nTitle: 'OTR',
				icon: 'key',
				template: 'otrFlexTab',
				order: 13,
			});
		} else {
			OTR.enabled.set(false);
			TabBar.removeButton('otr');
		}
	});
});


import { Migrations } from '../../../app/migrations';
import { Permissions, Roles } from '../../../app/models';

Migrations.add({
	version: 108,
	up() {
		const roles = Roles.find({
			_id: { $ne: 'guest' },
			scope: 'Users',
		}).fetch().map((role) => role._id);
		Permissions.createOrUpdate('leave-c', roles);
		Permissions.createOrUpdate('leave-d', roles);
	},
});


import { Meteor } from 'meteor/meteor';

import { TabBar } from '../../../app/ui-utils/client';
import { hasAllPermission } from '../../../app/authorization/client';

Meteor.startup(() => {
	TabBar.addButton({
		groups: ['channel', 'group', 'direct'],
		id: 'export-messages',
		anonymous: true,
		i18nTitle: 'Export_Messages',
		icon: 'mail',
		template: 'ExportMessages',
		full: true,
		order: 12,
		condition: () => hasAllPermission('mail-messages'),
	});
});


import { callbacks } from '../../../callbacks/server';

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


import { Meteor } from 'meteor/meteor';

Meteor.methods({
	addUserToRoom(data) {
		return Meteor.call('addUsersToRoom', {
			rid: data.rid,
			users: [data.username],
		});
	},
});


import { Migrations } from '../../../app/migrations';
import { Settings } from '../../../app/models';

Migrations.add({
	version: 90,
	up() {
		Settings.remove({ _id: 'Piwik', type: 'group' });

		const settings = Settings.find({ $or: [{ _id: 'PiwikAnalytics_url', value: { $ne: null } }, { _id: 'PiwikAnalytics_siteId', value: { $ne: null } }] }).fetch();

		if (settings && settings.length === 2) {
			Settings.upsert({ _id: 'PiwikAnalytics_enabled' }, { $set: { value: true } });
		}
	},
});


//! moment.js locale configuration
//! locale : Galician [gl]
//! author : Juan G. Hurtado : https://github.com/juanghurtado

;(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 gl = moment.defineLocale('gl', {
        months : 'Xaneiro_Febreiro_Marzo_Abril_Maio_Xuño_Xullo_Agosto_Setembro_Outubro_Novembro_Decembro'.split('_'),
        monthsShort : 'Xan._Feb._Mar._Abr._Mai._Xuñ._Xul._Ago._Set._Out._Nov._Dec.'.split('_'),
        monthsParseExact: true,
        weekdays : 'Domingo_Luns_Martes_Mércores_Xoves_Venres_Sábado'.split('_'),
        weekdaysShort : 'Dom._Lun._Mar._Mér._Xov._Ven._Sáb.'.split('_'),
        weekdaysMin : 'Do_Lu_Ma_Mé_Xo_Ve_Sá'.split('_'),
        weekdaysParseExact : true,
        longDateFormat : {
            LT : 'H:mm',
            LTS : 'H:mm:ss',
            L : 'DD/MM/YYYY',
            LL : 'D MMMM YYYY',
            LLL : 'D MMMM YYYY H:mm',
            LLLL : 'dddd D MMMM YYYY H:mm'
        },
        calendar : {
            sameDay : function () {
                return '[hoxe ' + ((this.hours() !== 1) ? 'ás' : 'á') + '] LT';
            },
            nextDay : function () {
                return '[mañá ' + ((this.hours() !== 1) ? 'ás' : 'á') + '] LT';
            },
            nextWeek : function () {
                return 'dddd [' + ((this.hours() !== 1) ? 'ás' : 'a') + '] LT';
            },
            lastDay : function () {
                return '[onte ' + ((this.hours() !== 1) ? 'á' : 'a') + '] LT';
            },
            lastWeek : function () {
                return '[o] dddd [pasado ' + ((this.hours() !== 1) ? 'ás' : 'a') + '] LT';
            },
            sameElse : 'L'
        },
        relativeTime : {
            future : function (str) {
                if (str === 'uns segundos') {
                    return 'nuns segundos';
                }
                return 'en ' + str;
            },
            past : 'hai %s',
            s : 'uns segundos',
            m : 'un minuto',
            mm : '%d minutos',
            h : 'unha hora',
            hh : '%d horas',
            d : 'un día',
            dd : '%d días',
            M : 'un mes',
            MM : '%d meses',
            y : 'un ano',
            yy : '%d anos'
        },
        ordinalParse : /\d{1,2}º/,
        ordinal : '%dº',
        week : {
            dow : 1, // Monday is the first day of the week.
            doy : 7  // The week that contains Jan 1st is the first week of the year.
        }
    });

    return gl;

}));


export function registerCallback(callbackDefition) {

for (const definition of callbackDefinitions) {

keep keep keep replace replace keep keep keep keep

callbacks.add(definition.hook, definition.callback, callbacks.priority.LOW, definition.id);

export function registerCallback(callbackDefinition) {


<template name="addWebdavAccount">
	<form id="add-webdav" class="content-background-color color-primary-font-color">

						<input name="name" id="serverName" type="text" class="rc-input__element" autocapitalize="off" autocorrect="off" placeholder="{{_ 'Name_optional' }}"
						 autofocus>

						<input name="serverURL" id="serverURL" type="text" class="rc-input__element" autocapitalize="off" autocorrect="off" placeholder="{{_ 'Webdav_Server_URL' }}"
						 autofocus>

						<input name="username" id="username" type="text" class="rc-input__element" autocapitalize="off" autocorrect="off" placeholder="{{_ 'Username' }}" autofocus>

						<input name="pass" id="pass" type="password" class="rc-input__element" autocapitalize="off" autocorrect="off" placeholder="{{_ 'Password' }}" autofocus>

			<button class="rc-button rc-button--primary">{{btnAddNewServer}}</button>

	</form>
</template>


//! moment.js locale configuration
//! locale : Japanese [jv]
//! author : Rony Lantip : https://github.com/lantip
//! reference: http://jv.wikipedia.org/wiki/Basa_Jawa

;(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 jv = moment.defineLocale('jv', {
        months : 'Januari_Februari_Maret_April_Mei_Juni_Juli_Agustus_September_Oktober_Nopember_Desember'.split('_'),
        monthsShort : 'Jan_Feb_Mar_Apr_Mei_Jun_Jul_Ags_Sep_Okt_Nop_Des'.split('_'),
        weekdays : 'Minggu_Senen_Seloso_Rebu_Kemis_Jemuwah_Septu'.split('_'),
        weekdaysShort : 'Min_Sen_Sel_Reb_Kem_Jem_Sep'.split('_'),
        weekdaysMin : 'Mg_Sn_Sl_Rb_Km_Jm_Sp'.split('_'),
        longDateFormat : {
            LT : 'HH.mm',
            LTS : 'HH.mm.ss',
            L : 'DD/MM/YYYY',
            LL : 'D MMMM YYYY',
            LLL : 'D MMMM YYYY [pukul] HH.mm',
            LLLL : 'dddd, D MMMM YYYY [pukul] HH.mm'
        },
        meridiemParse: /enjing|siyang|sonten|ndalu/,
        meridiemHour : function (hour, meridiem) {
            if (hour === 12) {
                hour = 0;
            }
            if (meridiem === 'enjing') {
                return hour;
            } else if (meridiem === 'siyang') {
                return hour >= 11 ? hour : hour + 12;
            } else if (meridiem === 'sonten' || meridiem === 'ndalu') {
                return hour + 12;
            }
        },
        meridiem : function (hours, minutes, isLower) {
            if (hours < 11) {
                return 'enjing';
            } else if (hours < 15) {
                return 'siyang';
            } else if (hours < 19) {
                return 'sonten';
            } else {
                return 'ndalu';
            }
        },
        calendar : {
            sameDay : '[Dinten puniko pukul] LT',
            nextDay : '[Mbenjang pukul] LT',
            nextWeek : 'dddd [pukul] LT',
            lastDay : '[Kala wingi pukul] LT',
            lastWeek : 'dddd [kepengker pukul] LT',
            sameElse : 'L'
        },
        relativeTime : {
            future : 'wonten ing %s',
            past : '%s ingkang kepengker',
            s : 'sawetawis detik',
            m : 'setunggal menit',
            mm : '%d menit',
            h : 'setunggal jam',
            hh : '%d jam',
            d : 'sedinten',
            dd : '%d dinten',
            M : 'sewulan',
            MM : '%d wulan',
            y : 'setaun',
            yy : '%d taun'
        },
        week : {
            dow : 1, // Monday is the first day of the week.
            doy : 7  // The week that contains Jan 1st is the first week of the year.
        }
    });

    return jv;

}));


@keyframes fadeIn {
	0% {
		visibility: hidden;

		opacity: 0;
	}

	1% {
		visibility: visible;

		opacity: 0;
	}

	100% {
		visibility: visible;

		opacity: 1;
	}
}

@keyframes fadeOut {
	0% {
		visibility: visible;

		opacity: 1;
	}

	99% {
		visibility: visible;

		opacity: 0;
	}

	100% {
		visibility: hidden;

		opacity: 0;
	}
}

@keyframes highlight {
	0% {
		background: #ffff99;
	}

	100% {
		background: none;
	}
}

@keyframes modalEnter {
	0% {
		visibility: hidden;

		opacity: 0;
	}

	1% {
		visibility: visible;

		transform: translateY(-150px);

		opacity: 0;
	}

	100% {
		visibility: visible;

		transform: translateY(0);

		opacity: 1;
	}
}

@keyframes modalExit {
	0% {
		visibility: visible;

		opacity: 1;
	}

	99% {
		visibility: visible;

		transform: translateY(150px);

		opacity: 0;
	}

	100% {
		visibility: hidden;

		opacity: 0;
	}
}

@keyframes spin {
	from {
		transform: rotate(0deg);
	}

	to {
		transform: rotate(360deg);
	}
}

@keyframes spinh {
	from {
		transform: rotateY(0deg);
	}

	to {
		transform: rotateY(360deg);
	}
}

.dropdown-in {
	animation: dropdown-in 0.3s;
}

@keyframes dropdown-in {
	0% {
		display: none;

		opacity: 0;
	}

	1% {
		display: block;

		transform: scale(0);

		opacity: 0;
	}

	100% {
		transform: scale(1);

		opacity: 1;
	}
}

.pulse {
	animation: pulse 1s infinite;
}

@keyframes pulse {
	from {
		transform: scale3d(0.9, 0.9, 0.9);
	}

	50% {
		transform: scale3d(1.1, 1.1, 1.1);
	}

	to {
		transform: scale3d(0.9, 0.9, 0.9);
	}
}

.fadeInDown {
	animation-name: fadeInDown;
	animation-duration: 0.3s;
}

@keyframes fadeInDown {
	from {
		transform: translate3d(0, -20px, 0);

		opacity: 0;
	}

	to {
		transform: none;

		opacity: 1;
	}
}


callbacks.add(definition.hook, definition.callback, callbacks.priority.LOW, definition.id);


import React from 'react';

import Page from '../../components/basic/Page';
import VerticalBar from '../../components/basic/VerticalBar';
import { useTranslation } from '../../contexts/TranslationContext';
import { useRouteParameter, useRoute } from '../../contexts/RouterContext';
import { EditRoomContextBar } from './EditRoom';
import RoomsTable from './RoomsTable';

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

	const context = useRouteParameter('context');
	const id = useRouteParameter('id');

	const roomsRoute = useRoute('admin-rooms');

	const handleVerticalBarCloseButtonClick = () => {
		roomsRoute.push({});
	};

	return <Page flexDirection='row'>
		<Page>
			<Page.Header title={t('Rooms')} />
			<Page.Content>
				<RoomsTable />
			</Page.Content>
		</Page>
		{context && <VerticalBar className='contextual-bar'>
			<VerticalBar.Header>
				{t('Room_Info')}
				<VerticalBar.Close onClick={handleVerticalBarCloseButtonClick} />
			</VerticalBar.Header>

			<EditRoomContextBar rid={id} />
		</VerticalBar>}
	</Page>;
}

export default RoomsPage;


import React, { useCallback, useState, useMemo, useEffect } from 'react';
import { Box, Button, ButtonGroup, Margins, TextInput, Field, Icon, Skeleton, Throbber, InputBox, Modal } from '@rocket.chat/fuselage';

import { useTranslation } from '../../contexts/TranslationContext';
import { useFileInput } from '../../hooks/useFileInput';
import { useEndpointDataExperimental, ENDPOINT_STATES } from '../../hooks/useEndpointDataExperimental';
import { useEndpointUpload } from '../../hooks/useEndpointUpload';
import { useSetModal } from '../../contexts/ModalContext';
import { useEndpointAction } from '../../hooks/useEndpointAction';
import VerticalBar from '../../components/basic/VerticalBar';

const DeleteWarningModal = ({ onDelete, onCancel, ...props }) => {
	const t = useTranslation();
	return <Modal {...props}>
		<Modal.Header>
			<Icon color='danger' name='modal-warning' size={20}/>
			<Modal.Title>{t('Are_you_sure')}</Modal.Title>
			<Modal.Close onClick={onCancel}/>
		</Modal.Header>
		<Modal.Content fontScale='p1'>
			{t('Custom_Emoji_Delete_Warning')}
		</Modal.Content>
		<Modal.Footer>
			<ButtonGroup align='end'>
				<Button ghost onClick={onCancel}>{t('Cancel')}</Button>
				<Button primary danger onClick={onDelete}>{t('Delete')}</Button>
			</ButtonGroup>
		</Modal.Footer>
	</Modal>;
};

const SuccessModal = ({ onClose, ...props }) => {
	const t = useTranslation();
	return <Modal {...props}>
		<Modal.Header>
			<Icon color='success' name='checkmark-circled' size={20}/>
			<Modal.Title>{t('Deleted')}</Modal.Title>
			<Modal.Close onClick={onClose}/>
		</Modal.Header>
		<Modal.Content fontScale='p1'>
			{t('Custom_Emoji_Has_Been_Deleted')}
		</Modal.Content>
		<Modal.Footer>
			<ButtonGroup align='end'>
				<Button primary onClick={onClose}>{t('Ok')}</Button>
			</ButtonGroup>
		</Modal.Footer>
	</Modal>;
};

export function EditCustomEmojiWithData({ _id, cache, onChange, ...props }) {
	const t = useTranslation();
	const query = useMemo(() => ({
		query: JSON.stringify({ _id }),
	// TODO: remove cache. Is necessary for data invalidation
	}), [_id, cache]);

	const { data = { emojis: {} }, state, error } = useEndpointDataExperimental('emoji-custom.list', query);

	if (state === ENDPOINT_STATES.LOADING) {
		return <Box pb='x20'>
			<Skeleton mbs='x8'/>
			<InputBox.Skeleton w='full'/>
			<Skeleton mbs='x8'/>
			<InputBox.Skeleton w='full'/>
			<ButtonGroup stretch w='full' mbs='x8'>
				<Button disabled><Throbber inheritColor/></Button>
				<Button primary disabled><Throbber inheritColor/></Button>
			</ButtonGroup>
			<ButtonGroup stretch w='full' mbs='x8'>
				<Button primary danger disabled><Throbber inheritColor/></Button>
			</ButtonGroup>
		</Box>;
	}

	if (error || !data || !data.emojis || data.emojis.update.length < 1) {
		return <Box fontScale='h1' pb='x20'>{t('Custom_User_Status_Error_Invalid_User_Status')}</Box>;
	}

	return <EditCustomEmoji data={data.emojis.update[0]} onChange={onChange} {...props}/>;
}

export function EditCustomEmoji({ close, onChange, data, ...props }) {
	const t = useTranslation();

	const { _id, name: previousName, aliases: previousAliases, extension: previousExtension } = data || {};
	const previousEmoji = data || {};

	const [name, setName] = useState(previousName);
	const [aliases, setAliases] = useState(previousAliases.join(', '));
	const [emojiFile, setEmojiFile] = useState();
	const setModal = useSetModal();
	const [newEmojiPreview, setNewEmojiPreview] = useState(`/emoji-custom/${ encodeURIComponent(previousName) }.${ previousExtension }`);

	useEffect(() => {
		setName(previousName || '');
		setAliases((previousAliases && previousAliases.join(', ')) || '');
	}, [previousName, previousAliases, previousEmoji, _id]);

	const setEmojiPreview = useCallback(async (file) => {
		setEmojiFile(file);
		setNewEmojiPreview(URL.createObjectURL(file));
	}, [setEmojiFile]);

	const hasUnsavedChanges = useMemo(() => previousName !== name || aliases !== previousAliases.join(', ') || !!emojiFile, [previousName, name, aliases, previousAliases, emojiFile]);

	const saveAction = useEndpointUpload('emoji-custom.update', {}, t('Custom_Emoji_Updated_Successfully'));

	const handleSave = useCallback(async () => {
		const formData = new FormData();
		formData.append('emoji', emojiFile);
		formData.append('_id', _id);
		formData.append('name', name);
		formData.append('aliases', aliases);
		const result = await saveAction(formData);
		if (result.success) {
			onChange();
		}
	}, [emojiFile, _id, name, aliases, saveAction, onChange]);

	const deleteAction = useEndpointAction('POST', 'emoji-custom.delete', useMemo(() => ({ emojiId: _id }), [_id]));

	const onDeleteConfirm = useCallback(async () => {
		const result = await deleteAction();
		if (result.success) {
			setModal(() => <SuccessModal onClose={() => { setModal(undefined); close(); onChange(); }}/>);
		}
	}, [close, deleteAction, onChange]);

	const openConfirmDelete = useCallback(() => setModal(() => <DeleteWarningModal onDelete={onDeleteConfirm} onCancel={() => setModal(undefined)}/>), [onDeleteConfirm, setModal]);

	const handleAliasesChange = useCallback((e) => setAliases(e.currentTarget.value), [setAliases]);

	const [clickUpload] = useFileInput(setEmojiPreview, 'emoji');

	return <VerticalBar.ScrollableContent {...props}>
		<Field>
			<Field.Label>{t('Name')}</Field.Label>
			<Field.Row>
				<TextInput value={name} onChange={(e) => setName(e.currentTarget.value)} placeholder={t('Name')} />
			</Field.Row>
		</Field>
		<Field>
			<Field.Label>{t('Aliases')}</Field.Label>
			<Field.Row>
				<TextInput value={aliases} onChange={handleAliasesChange} placeholder={t('Aliases')} />
			</Field.Row>
		</Field>
		<Field>
			<Field.Label alignSelf='stretch' display='flex' justifyContent='space-between' alignItems='center'>
				{t('Custom_Emoji')}
				<Button square onClick={clickUpload}><Icon name='upload' size='x20'/></Button>
			</Field.Label>
			{ newEmojiPreview && <Box display='flex' flexDirection='row' mbs='none' justifyContent='center'>
				<Margins inline='x4'>
					<Box is='img' style={{ objectFit: 'contain' }} w='x120' h='x120' src={newEmojiPreview}/>
				</Margins>
			</Box> }
		</Field>
		<Field>
			<Field.Row>
				<ButtonGroup stretch w='full'>
					<Button onClick={close}>{t('Cancel')}</Button>
					<Button primary onClick={handleSave} disabled={!hasUnsavedChanges}>{t('Save')}</Button>
				</ButtonGroup>
			</Field.Row>
		</Field>
		<Field>
			<Field.Row>
				<ButtonGroup stretch w='full'>
					<Button primary danger onClick={openConfirmDelete}><Icon name='trash' mie='x4'/>{t('Delete')}</Button>
				</ButtonGroup>
			</Field.Row>
		</Field>
	</VerticalBar.ScrollableContent>;
}


import { Meteor } from 'meteor/meteor';
import { DDPRateLimiter } from 'meteor/ddp-rate-limiter';
import { RateLimiter } from 'meteor/rate-limit';
import _ from 'underscore';

export const RateLimiterClass = new class {
	limitFunction(fn, numRequests, timeInterval, matchers) {
		if (process.env.TEST_MODE === 'true') {
			return fn;
		}
		const rateLimiter = new RateLimiter();
		rateLimiter.addRule(matchers, numRequests, timeInterval);
		return function(...args) {
			const match = {};
			_.each(matchers, function(matcher, key) {
				match[key] = args[key];
			});
			rateLimiter.increment(match);
			const rateLimitResult = rateLimiter.check(match);
			if (rateLimitResult.allowed) {
				return fn.apply(null, args);
			}
			throw new Meteor.Error('error-too-many-requests', `Error, too many requests. Please slow down. You must wait ${ Math.ceil(rateLimitResult.timeToReset / 1000) } seconds before trying again.`, {
				timeToReset: rateLimitResult.timeToReset,
				seconds: Math.ceil(rateLimitResult.timeToReset / 1000),
			});
		};
	}

	limitMethod(methodName, numRequests, timeInterval, matchers) {
		if (process.env.TEST_MODE === 'true') {
			return;
		}
		const match = {
			type: 'method',
			name: methodName,
		};
		_.each(matchers, function(matcher, key) {
			match[key] = matchers[key];
		});
		return DDPRateLimiter.addRule(match, numRequests, timeInterval);
	}
}();


height: 0px
max-height: 0px
border: solid
border-width: thin 0 0 0
&--inset:not(.v-divider--vertical)
margin-left: 72px
max-width: calc(100% - 72px)

keep keep keep add keep keep keep keep keep

import { Vue } from 'vue/types/vue'
import { VNode, VNodeDirective, FunctionalComponentOptions } from 'vue/types'

export function createSimpleFunctional (
  c: string,
  el = 'div',
  name?: string
): FunctionalComponentOptions {
  return {
    name: name || c.replace(/__/g, '-'),

    functional: true,

    render (h, { data, children }): VNode {
      data.staticClass = (`${c} ${data.staticClass || ''}`).trim()

      return h(el, data, children)
    }
  }
}

export function createSimpleTransition (
  name: string,
  origin = 'top center 0',
  mode?: string
): FunctionalComponentOptions {
  return {
    name,

    functional: true,

    props: {
      group: {
        type: Boolean,
        default: false
      },
      mode: {
        type: String,
        default: mode
      },
      origin: {
        type: String,
        default: origin
      }
    },

    render (h, context): VNode {
      const tag = `transition${context.props.group ? '-group' : ''}`
      context.data = context.data || {}
      context.data.props = {
        name,
        mode: context.props.mode
      }
      context.data.on = context.data.on || {}
      if (!Object.isExtensible(context.data.on)) {
        context.data.on = { ...context.data.on }
      }

      context.data.on.beforeEnter = (el: HTMLElement) => {
        el.style.transformOrigin = context.props.origin
        el.style.webkitTransformOrigin = context.props.origin
      }

      if (!context.props.mode) {
        const group = context.props.group

        context.data.on.beforeLeave = (el: HTMLElement) => {
          // When using group, this helps Vue FLIP
          // look correct when transitioning
          // https://vuejs.org/v2/guide/transitions.html#List-Move-Transitions
          if (group) el.style.display = 'none'
          else el.style.position = 'absolute'
        }
      }

      return h(tag, context.data, context.children)
    }
  }
}

export function createJavaScriptTransition (
  name: string,
  functions: Record<string, () => any>,
  mode = 'in-out'
): FunctionalComponentOptions {
  return {
    name,

    functional: true,

    props: {
      mode: {
        type: String,
        default: mode
      }
    },

    render (h, context): VNode {
      const data = {
        props: {
          ...context.props,
          name
        },
        on: functions
      }

      return h('transition', data, context.children)
    }
  }
}

export type BindingConfig = Pick<VNodeDirective, 'arg' | 'modifiers' | 'value'>
export function directiveConfig (binding: BindingConfig, defaults = {}): VNodeDirective {
  return {
    ...defaults,
    ...binding.modifiers,
    value: binding.arg,
    ...(binding.value || {})
  }
}

export function addOnceEventListener (el: EventTarget, event: string, cb: () => void): void {
  var once = () => {
    cb()
    el.removeEventListener(event, once, false)
  }

  el.addEventListener(event, once, false)
}

export function getNestedValue (obj: any, path: (string | number)[], fallback?: any): any {
  const last = path.length - 1

  if (last < 0) return obj === undefined ? fallback : obj

  for (let i = 0; i < last; i++) {
    if (obj == null) {
      return fallback
    }
    obj = obj[path[i]]
  }

  if (obj == null) return fallback

  return obj[path[last]] === undefined ? fallback : obj[path[last]]
}

export function deepEqual (a: any, b: any): boolean {
  if (a === b) return true

  if (a instanceof Date && b instanceof Date) {
    // If the values are Date, they were convert to timestamp with getTime and compare it
    if (a.getTime() !== b.getTime()) return false
  }

  if (a !== Object(a) || b !== Object(b)) {
    // If the values aren't objects, they were already checked for equality
    return false
  }

  const props = Object.keys(a)

  if (props.length !== Object.keys(b).length) {
    // Different number of props, don't bother to check
    return false
  }

  return props.every(p => deepEqual(a[p], b[p]))
}

export function getObjectValueByPath (obj: object, path: string, fallback?: any): any {
  // credit: http://stackoverflow.com/questions/6491463/accessing-nested-javascript-objects-with-string-key#comment55278413_6491621
  if (!path || path.constructor !== String) return fallback
  path = path.replace(/\[(\w+)\]/g, '.$1') // convert indexes to properties
  path = path.replace(/^\./, '') // strip a leading dot
  return getNestedValue(obj, path.split('.'), fallback)
}

export function getPropertyFromItem (
  item: object,
  property: string | (string | number)[] | ((item: object, fallback?: any) => any),
  fallback?: any
): any {
  if (property == null) return item === undefined ? fallback : item

  if (item !== Object(item)) return fallback === undefined ? item : fallback

  if (typeof property === 'string') return getObjectValueByPath(item, property, fallback)

  if (Array.isArray(property)) return getNestedValue(item, property, fallback)

  if (typeof property !== 'function') return fallback

  const value = property(item, fallback)

  return typeof value === 'undefined' ? fallback : value
}

export function createRange (length: number): number[] {
  return Array.from({ length }, (v, k) => k)
}

export function getZIndex (el?: Element | null): number {
  if (!el || el.nodeType !== Node.ELEMENT_NODE) return 0

  const index = +window.getComputedStyle(el).getPropertyValue('z-index')

  if (isNaN(index)) return getZIndex(el.parentNode as Element)
  return index
}

const tagsToReplace = {
  '&': '&',
  '<': '<',
  '>': '>'
} as any

export function escapeHTML (str: string): string {
  return str.replace(/[&<>]/g, tag => tagsToReplace[tag] || tag)
}

export function filterObjectOnKeys<T, K extends keyof T> (obj: T, keys: K[]): { [N in K]: T[N] } {
  const filtered = {} as { [N in K]: T[N] }

  for (let i = 0; i < keys.length; i++) {
    const key = keys[i]
    if (typeof obj[key] !== 'undefined') {
      filtered[key] = obj[key]
    }
  }

  return filtered
}

export function filterChildren (array: VNode[] = [], tag: string): VNode[] {
  return array.filter(child => {
    return child.componentOptions &&
      child.componentOptions.Ctor.options.name === tag
  })
}

export function convertToUnit (str: string | number | null | undefined, unit = 'px'): string | undefined {
  if (str == null || str === '') {
    return undefined
  } else if (isNaN(+str!)) {
    return String(str)
  } else {
    return `${Number(str)}${unit}`
  }
}

export function kebabCase (str: string): string {
  return (str || '').replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase()
}

export function isObject (obj: any): obj is object {
  return obj !== null && typeof obj === 'object'
}

// KeyboardEvent.keyCode aliases
export const keyCodes = Object.freeze({
  enter: 13,
  tab: 9,
  delete: 46,
  esc: 27,
  space: 32,
  up: 38,
  down: 40,
  left: 37,
  right: 39,
  end: 35,
  home: 36,
  del: 46,
  backspace: 8,
  insert: 45,
  pageup: 33,
  pagedown: 34
})

const ICONS_PREFIX = '$vuetify.icons.'

// This remaps internal names like '$vuetify.icons.cancel' to the current name
// for that icon.
export function remapInternalIcon (vm: Vue, iconName: string): string {
  if (!iconName.startsWith(ICONS_PREFIX)) {
    return iconName
  }

  // Now look up icon indirection name, e.g. '$vuetify.icons.cancel'
  return getObjectValueByPath(vm, iconName, iconName)
}

export function keys<O> (o: O) {
  return Object.keys(o) as (keyof O)[]
}

/**
 * Camelize a hyphen-delimited string.
 */
const camelizeRE = /-(\w)/g
export const camelize = (str: string): string => {
  return str.replace(camelizeRE, (_, c) => c ? c.toUpperCase() : '')
}


/* eslint-disable max-len, import/export, no-use-before-define */
import Vue, { VueConstructor } from 'vue'

export default function mixins<T extends VueConstructor[]> (...args: T): ExtractVue<T> extends infer V ? V extends Vue ? VueConstructor<V> : never : never
export default function mixins<T extends Vue> (...args: VueConstructor[]): VueConstructor<T>
export default function mixins (...args: VueConstructor[]): VueConstructor {
  return Vue.extend({ mixins: args })
}

/**
 * Returns the instance type from a VueConstructor
 * Useful for adding types when using mixins().extend()
 */
export type ExtractVue<T extends VueConstructor | VueConstructor[]> = T extends (infer U)[]
  ? UnionToIntersection<
    U extends VueConstructor<infer V> ? V : never
  >
  : T extends VueConstructor<infer V> ? V : never

type UnionToIntersection<U> =
  (U extends any ? (k: U) => void : never) extends ((k: infer I) => void) ? I : never


# Contributor Covenant Code of Conduct

## Our Pledge

In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.

## Our Standards

Examples of behavior that contributes to creating a positive environment include:

* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy towards other community members

Examples of unacceptable behavior by participants include:

* The use of sexualized language or imagery and unwelcome sexual attention or advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic address, without explicit permission
* Other conduct which could reasonably be considered inappropriate in a professional setting

## Our Responsibilities

Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.

Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.

## Scope

This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.

## Enforcement

Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at john.j.leider@gmail.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.

Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.

## Attribution

This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version]

[homepage]: http://contributor-covenant.org
[version]: http://contributor-covenant.org/version/1/4/