!!! 5
%html
  %head
    %title Stypi Editor Normalize Test
    %link{:rel => 'stylesheet', :type => 'text/css', :href => '../../../lib/mocha.css'}

%body
  #mocha
  #editor-container

  %script{:type => 'text/javascript', :src => '../../../lib/jquery.js'}
  %script{:type => 'text/javascript', :src => '../../../lib/underscore.js'}
  %script{:type => 'text/javascript', :src => '../../../lib/eventemitter2.js'}
  %script{:type => 'text/javascript', :src => '../../../lib/rangy/rangy-core.js'}
  %script{:type => 'text/javascript', :src => '../../../lib/diff_match_patch.js'}
  %script{:type => 'text/javascript', :src => '../../../lib/jetsync.js'}
  %script{:type => 'text/javascript', :src => '../../../lib/linked_list.js'}
  %script{:type => 'text/javascript', :src => '../../src/constants.js'}
  %script{:type => 'text/javascript', :src => '../../src/utils.js'}
  %script{:type => 'text/javascript', :src => '../../src/leaf.js'}
  %script{:type => 'text/javascript', :src => '../../src/line.js'}
  %script{:type => 'text/javascript', :src => '../../src/document.js'}
  %script{:type => 'text/javascript', :src => '../../src/position.js'}
  %script{:type => 'text/javascript', :src => '../../src/range.js'}
  %script{:type => 'text/javascript', :src => '../../src/keyboard.js'}
  %script{:type => 'text/javascript', :src => '../../src/editor.js'}
  %script{:type => 'text/javascript', :src => '../../../lib/mocha.js'}
  %script{:type => 'text/javascript', :src => '../../../lib/chai.js'}
  :javascript
    mocha.setup('bdd');
    expect = chai.expect
  %script{:type => 'text/javascript', :src => 'scripts/normalize.js'}
  :javascript
    mocha.run();


import { Meteor } from 'meteor/meteor';
import { Session } from 'meteor/session';

import { ChatRoom, Subscriptions } from '../../../models';
import { openRoom } from '../../../ui-utils';
import { getUserPreference, RoomTypeConfig, RoomTypeRouteConfig, RoomSettingsEnum, UiTextContext } from '../../../utils';
import { hasPermission, hasAtLeastOnePermission } from '../../../authorization';
import { settings } from '../../../settings';
import { getUserAvatarURL } from '../../../utils/lib/getUserAvatarURL';

export class DirectMessageRoomRoute extends RoomTypeRouteConfig {
	constructor() {
		super({
			name: 'direct',
			path: '/direct/:username',
		});
	}

	action(params) {
		return openRoom('d', params.username);
	}

	link(sub) {
		return { username: sub.name };
	}
}

export class DirectMessageRoomType extends RoomTypeConfig {
	constructor() {
		super({
			identifier: 'd',
			order: 50,
			icon: 'at',
			label: 'Direct_Messages',
			route: new DirectMessageRoomRoute(),
		});
	}

	findRoom(identifier) {
		if (!hasPermission('view-d-room')) {
			return null;
		}

		const query = {
			t: 'd',
			name: identifier,
		};

		const subscription = Subscriptions.findOne(query);
		if (subscription && subscription.rid) {
			return ChatRoom.findOne(subscription.rid);
		}
	}

	roomName(roomData) {
		// this function can receive different types of data
		// if it doesn't have fname and name properties, should be a Room object
		// so, need to find the related subscription
		const subscription = roomData && (roomData.fname || roomData.name)
			? roomData
			: Subscriptions.findOne({ rid: roomData._id });

		if (subscription === undefined) {
			return console.log('roomData', roomData);
		}

		if (settings.get('UI_Use_Real_Name') && subscription.fname) {
			return subscription.fname;
		}

		return subscription.name;
	}

	secondaryRoomName(roomData) {
		if (settings.get('UI_Use_Real_Name')) {
			const subscription = Subscriptions.findOne({ rid: roomData._id }, { fields: { name: 1 } });
			return subscription && subscription.name;
		}
	}

	condition() {
		const groupByType = getUserPreference(Meteor.userId(), 'sidebarGroupByType');
		return groupByType && hasAtLeastOnePermission(['view-d-room', 'view-joined-room']);
	}

	getUserStatus(roomId) {
		const subscription = Subscriptions.findOne({ rid: roomId });
		if (subscription == null) {
			return;
		}

		return Session.get(`user_${ subscription.name }_status`);
	}

	getUserStatusText(roomId) {
		const subscription = Subscriptions.findOne({ rid: roomId });
		if (subscription == null) {
			return;
		}

		return Session.get(`user_${ subscription.name }_status_text`);
	}

	allowRoomSettingChange(room, setting) {
		switch (setting) {
			case RoomSettingsEnum.NAME:
			case RoomSettingsEnum.SYSTEM_MESSAGES:
			case RoomSettingsEnum.DESCRIPTION:
			case RoomSettingsEnum.READ_ONLY:
			case RoomSettingsEnum.REACT_WHEN_READ_ONLY:
			case RoomSettingsEnum.ARCHIVE_OR_UNARCHIVE:
			case RoomSettingsEnum.JOIN_CODE:
				return false;
			case RoomSettingsEnum.E2E:
				return settings.get('E2E_Enable') === true;
			default:
				return true;
		}
	}

	enableMembersListProfile() {
		return true;
	}

	userDetailShowAll(/* room */) {
		return true;
	}

	getUiText(context) {
		switch (context) {
			case UiTextContext.HIDE_WARNING:
				return 'Hide_Private_Warning';
			case UiTextContext.LEAVE_WARNING:
				return 'Leave_Private_Warning';
			default:
				return '';
		}
	}

	/**
	 * Returns details to use on notifications
	 *
	 * @param {object} room
	 * @param {object} user
	 * @param {string} notificationMessage
	 * @return {object} Notification details
	 */
	getNotificationDetails(room, user, notificationMessage) {
		if (!Meteor.isServer) {
			return {};
		}

		const title = settings.get('UI_Use_Real_Name') ? user.name : `@${ user.username }`;
		const text = notificationMessage;

		return { title, text };
	}

	getAvatarPath(roomData) {
		return getUserAvatarURL(roomData.name || this.roomName(roomData));
	}
}


import { Meteor } from 'meteor/meteor';

import { Users, Rooms } from '../../../../models';

export default function handleOnLogin(login) {
	if (login.user === null) {
		return this.log('Invalid handleOnLogin call');
	}
	if (!login.user.username) {
		return this.log('Invalid handleOnLogin call (Missing username)');
	}
	if (this.loggedInUsers.indexOf(login.user._id) !== -1) {
		return this.log('Duplicate handleOnLogin call');
	}

	this.loggedInUsers.push(login.user._id);

	Meteor.users.update({ _id: login.user._id }, {
		$set: {
			'profile.irc.fromIRC': false,
			'profile.irc.username': `${ login.user.username }-rkt`,
			'profile.irc.nick': `${ login.user.username }-rkt`,
			'profile.irc.hostname': 'rocket.chat',
		},
	});

	const user = Users.findOne({
		_id: login.user._id,
	});

	this.sendCommand('registerUser', user);

	const rooms = Rooms.findBySubscriptionUserId(user._id).fetch();

	rooms.forEach((room) => this.sendCommand('joinedChannel', { room, user }));
}


import moment from 'moment';

import { handleError } from '../../../utils';

/**
 *	Check if given daterange matches any of pre-defined options
 * @param {String} value
 * @param {Date} from
 * @param {Date} to
 *
 * @returns {String} new value
 */
export const checkDaterangeValue = (value, from, to) => {
	if (moment().startOf('day').isSame(from) && moment().startOf('day').isSame(to)) {
		return 'today';
	}
	if (moment().startOf('day').subtract(1, 'days').isSame(from) && moment().startOf('day').subtract(1, 'days').isSame(to)) {
		return 'yesterday';
	}
	if (moment().startOf('week').isSame(from) && moment().endOf('week').isSame(to)) {
		return 'this-week';
	}
	if (moment().subtract(1, 'weeks').startOf('week').isSame(from) && moment().subtract(1, 'weeks').endOf('week').isSame(to)) {
		return 'prev-week';
	}
	if (moment().startOf('month').isSame(from) && moment().endOf('month').isSame(to)) {
		return 'this-month';
	}
	if (moment().subtract(1, 'months').startOf('month').isSame(from) && moment().subtract(1, 'months').endOf('month').isSame(to)) {
		return 'prev-month';
	}
	return value;
};

/**
 * Update daterange provided
 * @param {String} value
 * @param {Date} from
 * @param {Date} to
 */
export const setDateRange = (value, from, to) => {
	if (moment(from).isAfter(moment())) {
		return handleError({ details: { errorTitle: 'Invalid_dates' }, error: 'Start_date_incorrect' });
	}

	if (value && from && to) {
		value = checkDaterangeValue(value, from, to);

		return {
			value,
			from: moment(from).format('MMM D YYYY'),
			to: moment(to).format('MMM D YYYY'),
		};
	}
	return {
		value: 'this-week',
		from: moment().startOf('week').format('MMM D YYYY'),
		to: moment().endOf('week').format('MMM D YYYY'),
	};
};

/**
 * Inc/Dec ReactiVar Daterange by one unit.
 * @param {Object} currentDaterange
 * @param {Int} order
 */
export const updateDateRange = (currentDaterange, order) => {
	// const currentDaterange = daterange.get();

	switch (currentDaterange.value) {
		case 'today':
		case 'yesterday':
		case 'day':
			if (order === 1) {
				return setDateRange('day',
					moment(currentDaterange.from, 'MMM D YYYY').add(1, 'days').startOf('day'),
					moment(currentDaterange.to, 'MMM D YYYY').add(1, 'days').startOf('day'));
			}
			return setDateRange('day',
				moment(currentDaterange.from, 'MMM D YYYY').subtract(1, 'days').startOf('day'),
				moment(currentDaterange.to, 'MMM D YYYY').subtract(1, 'days').startOf('day'));

			// break;
		case 'this-week':
		case 'prev-week':
		case 'week':
			if (order === 1) {
				return setDateRange('week',
					moment(currentDaterange.from, 'MMM D YYYY').add(1, 'weeks').startOf('week'),
					moment(currentDaterange.to, 'MMM D YYYY').add(1, 'weeks').endOf('week'));
			}
			return setDateRange('week',
				moment(currentDaterange.from, 'MMM D YYYY').subtract(1, 'weeks').startOf('week'),
				moment(currentDaterange.to, 'MMM D YYYY').subtract(1, 'weeks').endOf('week'));

			// break;
		case 'this-month':
		case 'prev-month':
		case 'month':
			if (order === 1) {
				return setDateRange('month',
					moment(currentDaterange.from, 'MMM D YYYY').add(1, 'months').startOf('month'),
					moment(currentDaterange.to, 'MMM D YYYY').add(1, 'months').endOf('month'));
			}
			return setDateRange('month',
				moment(currentDaterange.from, 'MMM D YYYY').subtract(1, 'months').startOf('month'),
				moment(currentDaterange.to, 'MMM D YYYY').subtract(1, 'months').endOf('month'));

			// break;
		case 'custom':
			handleError({ details: { errorTitle: 'Navigation_didnot_work' }, error: 'You_have_selected_custom_dates' });
	}
};


import { Meteor } from 'meteor/meteor';

let logger;

if (Meteor.isServer) {
	const { Logger } = require('../../logger/server/server');
	logger = new Logger('TemplateVarHandler', {});
}

export const templateVarHandler = function(variable, object) {
	const templateRegex = /#{([\w\-]+)}/gi;
	let match = templateRegex.exec(variable);
	let tmpVariable = variable;

	if (match == null) {
		if (!object.hasOwnProperty(variable)) {
			logger && logger.debug(`user does not have attribute: ${ variable }`);
			return;
		}
		return object[variable];
	}
	logger && logger.debug('template found. replacing values');
	while (match != null) {
		const tmplVar = match[0];
		const tmplAttrName = match[1];

		if (!object.hasOwnProperty(tmplAttrName)) {
			logger && logger.debug(`user does not have attribute: ${ tmplAttrName }`);
			return;
		}

		const attrVal = object[tmplAttrName];
		logger && logger.debug(`replacing template var: ${ tmplVar } with value: ${ attrVal }`);
		tmpVariable = tmpVariable.replace(tmplVar, attrVal);
		match = templateRegex.exec(variable);
	}
	return tmpVariable;
};


// otherwise gets discussion's avatar via _id

import { Meteor } from 'meteor/meteor';

import { openRoom } from '../../../ui-utils';
import { ChatRoom, ChatSubscription } from '../../../models';
import { settings } from '../../../settings';
import { hasAtLeastOnePermission } from '../../../authorization';
import { getUserPreference, RoomTypeConfig, RoomTypeRouteConfig, RoomSettingsEnum, UiTextContext } from '../../../utils';
import { getAvatarURL } from '../../../utils/lib/getAvatarURL';

export class PublicRoomRoute extends RoomTypeRouteConfig {
	constructor() {
		super({
			name: 'channel',
			path: '/channel/:name',
		});
	}

	action(params) {
		return openRoom('c', params.name);
	}
}

export class PublicRoomType extends RoomTypeConfig {
	constructor() {
		super({
			identifier: 'c',
			order: 30,
			icon: 'hashtag',
			label: 'Channels',
			route: new PublicRoomRoute(),
		});
	}

	getIcon(roomData) {
		if (roomData.prid) {
			return 'discussion';
		}
		return this.icon;
	}

	findRoom(identifier) {
		const query = {
			t: 'c',
			name: identifier,
		};
		return ChatRoom.findOne(query);
	}

	roomName(roomData) {
		if (roomData.prid) {
			return roomData.fname;
		}
		if (settings.get('UI_Allow_room_names_with_special_chars')) {
			return roomData.fname || roomData.name;
		}
		return roomData.name;
	}

	condition() {
		const groupByType = getUserPreference(Meteor.userId(), 'sidebarGroupByType');
		return groupByType && (hasAtLeastOnePermission(['view-c-room', 'view-joined-room']) || settings.get('Accounts_AllowAnonymousRead') === true);
	}

	showJoinLink(roomId) {
		return !!ChatRoom.findOne({ _id: roomId, t: 'c' });
	}

	includeInRoomSearch() {
		return true;
	}

	isGroupChat() {
		return true;
	}

	canAddUser(room) {
		return hasAtLeastOnePermission(['add-user-to-any-c-room', 'add-user-to-joined-room'], room._id);
	}

	canSendMessage(roomId) {
		const room = ChatRoom.findOne({ _id: roomId, t: 'c' }, { fields: { prid: 1 } });
		if (room.prid) {
			return true;
		}

		// TODO: remove duplicated code
		return ChatSubscription.find({
			rid: roomId,
		}).count() > 0;
	}

	enableMembersListProfile() {
		return true;
	}

	allowRoomSettingChange(room, setting) {
		switch (setting) {
			case RoomSettingsEnum.BROADCAST:
				return room.broadcast;
			case RoomSettingsEnum.READ_ONLY:
				return !room.broadcast;
			case RoomSettingsEnum.REACT_WHEN_READ_ONLY:
				return !room.broadcast && room.ro;
			case RoomSettingsEnum.E2E:
				return false;
			case RoomSettingsEnum.SYSTEM_MESSAGES:
			default:
				return true;
		}
	}

	getUiText(context) {
		switch (context) {
			case UiTextContext.HIDE_WARNING:
				return 'Hide_Room_Warning';
			case UiTextContext.LEAVE_WARNING:
				return 'Leave_Room_Warning';
			default:
				return '';
		}
	}

	getAvatarPath(roomData) {
		// TODO: change to always get avatar from _id when rooms have avatars

		return getAvatarURL({ username: `@${ this.roomName(roomData) }` });
	}
}


.SetupWizard__Step {
	display: none;

	flex-direction: column;

	max-width: 350px;
	margin: 2rem auto;

	transition: opacity 1s;

	opacity: 1;

	&--active {
		display: flex;
	}

	&--working {
		opacity: 0.5;
	}
}

@media (width <= 760px) {
	.SetupWizard__Step {
		margin: 0 auto;
	}
}


.message-color {
	display: inline-block;

	font-weight: 100;
}

.message-color-sample {
	position: relative;
	top: 2px;

	display: inline-block;

	width: 14px;
	height: 14px;
	margin-right: 3px;
	margin-left: 2px;

	border: 1px solid rgba(0, 0, 0, 0.2);
	border-radius: 3px;
}


import { Meteor } from 'meteor/meteor';

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

Meteor.startup(function() {
	settings.addGroup('IRC_Federation', function() {
		this.add('IRC_Enabled', false, {
			type: 'boolean',
			i18nLabel: 'Enabled',
			i18nDescription: 'IRC_Enabled',
			alert: 'IRC_Enabled_Alert',
		});

		this.add('IRC_Protocol', 'RFC2813', {
			type: 'select',
			i18nLabel: 'Protocol',
			i18nDescription: 'IRC_Protocol',
			values: [
				{
					key: 'RFC2813',
					i18nLabel: 'RFC2813',
				},
			],
		});

		this.add('IRC_Host', 'localhost', {
			type: 'string',
			i18nLabel: 'Host',
			i18nDescription: 'IRC_Host',
		});

		this.add('IRC_Port', 6667, {
			type: 'int',
			i18nLabel: 'Port',
			i18nDescription: 'IRC_Port',
		});

		this.add('IRC_Name', 'irc.rocket.chat', {
			type: 'string',
			i18nLabel: 'Name',
			i18nDescription: 'IRC_Name',
		});

		this.add('IRC_Description', 'Rocket.Chat IRC Bridge', {
			type: 'string',
			i18nLabel: 'Description',
			i18nDescription: 'IRC_Description',
		});

		this.add('IRC_Local_Password', 'password', {
			type: 'string',
			i18nLabel: 'Local_Password',
			i18nDescription: 'IRC_Local_Password',
		});

		this.add('IRC_Peer_Password', 'password', {
			type: 'string',
			i18nLabel: 'Peer_Password',
			i18nDescription: 'IRC_Peer_Password',
		});

		this.add('IRC_Reset_Connection', 'resetIrcConnection', {
			type: 'action',
			actionText: 'Reset_Connection',
			i18nLabel: 'Reset_Connection',
		});
	});
});


return hasAtLeastOnePermission(['add-user-to-any-c-room', 'add-user-to-joined-room'], room._id);

import { Meteor } from 'meteor/meteor';
import { FlowRouter } from 'meteor/kadira:flow-router';

import { RoomTypeConfig } from './RoomTypeConfig';
import { roomExit } from './roomExit';

export class RoomTypesCommon {
	constructor() {
		this.roomTypes = {};
		this.roomTypesOrder = [];
		this.mainOrder = 1;
	}

	/**
	 * Adds a room type to the application.
	 *
	 * @param {RoomTypeConfig} roomConfig
	 * @returns {void}
	 */
	add(roomConfig) {
		if (!(roomConfig instanceof RoomTypeConfig)) {
			throw new Error('Invalid Room Configuration object, it must extend "RoomTypeConfig"');
		}

		if (this.roomTypes[roomConfig.identifier]) {
			return false;
		}

		if (!roomConfig.order) {
			roomConfig.order = this.mainOrder + 10;
			this.mainOrder += 10;
		}

		this.roomTypesOrder.push({
			identifier: roomConfig.identifier,
			order: roomConfig.order,
		});

		this.roomTypes[roomConfig.identifier] = roomConfig;

		if (roomConfig.route && roomConfig.route.path && roomConfig.route.name && roomConfig.route.action) {
			const routeConfig = {
				name: roomConfig.route.name,
				action: roomConfig.route.action,
			};

			if (Meteor.isClient) {
				routeConfig.triggersExit = [roomExit];
			}

			return FlowRouter.route(roomConfig.route.path, routeConfig);
		}
	}

	hasCustomLink(roomType) {
		return this.roomTypes[roomType] && this.roomTypes[roomType].route && this.roomTypes[roomType].route.link != null;
	}

	/**
	 * @param {string} roomType room type (e.g.: c (for channels), d (for direct channels))
	 * @param {object} subData the user's subscription data
	 */
	getRouteLink(roomType, subData) {
		const routeData = this.getRouteData(roomType, subData);
		if (!routeData) {
			return false;
		}

		return FlowRouter.path(this.roomTypes[roomType].route.name, routeData);
	}

	/**
	 * @param {string} roomType room type (e.g.: c (for channels), d (for direct channels))
	 * @param {RoomTypeConfig} roomConfig room's type configuration
	 */
	getConfig(roomType) {
		return this.roomTypes[roomType];
	}

	/**
	 * @param {string} roomType room type (e.g.: c (for channels), d (for direct channels))
	 * @param {object} subData the user's subscription data
	 */
	getURL(roomType, subData) {
		const routeData = this.getRouteData(roomType, subData);
		if (!routeData) {
			return false;
		}

		return FlowRouter.url(this.roomTypes[roomType].route.name, routeData);
	}

	getRouteData(roomType, subData) {
		if (!this.roomTypes[roomType]) {
			return false;
		}

		let routeData = {};
		if (this.roomTypes[roomType] && this.roomTypes[roomType].route && this.roomTypes[roomType].route.link) {
			routeData = this.roomTypes[roomType].route.link(subData);
		} else if (subData && subData.name) {
			routeData = {
				name: subData.name,
			};
		}

		return routeData;
	}
}


import { Tracker } from 'meteor/tracker';

import { settings } from '../../settings';
import { hasAllPermission } from '../../authorization';
import { AccountBox, TabBar, MessageTypes } from '../../ui-utils';

Tracker.autorun((c) => {
	// import livechat tabbar templates right away if livechat enabled
	if (!settings.get('Livechat_enabled')) {
		return;
	}
	import('./views/regular');
	c.stop();
});

AccountBox.addItem({
	name: 'Livechat',
	icon: 'livechat',
	href: 'livechat-current-chats',
	sideNav: 'livechatFlex',
	condition: () => settings.get('Livechat_enabled') && hasAllPermission('view-livechat-manager'),
});

TabBar.addButton({
	groups: ['live'],
	id: 'visitor-info',
	i18nTitle: 'Visitor_Info',
	icon: 'info-circled',
	template: 'visitorInfo',
	order: 0,
});

TabBar.addButton({
	groups: ['live'],
	id: 'visitor-history',
	i18nTitle: 'Past_Chats',
	icon: 'chat',
	template: 'visitorHistory',
	order: 11,
});

TabBar.addGroup('message-search', ['live']);
TabBar.addGroup('starred-messages', ['live']);
TabBar.addGroup('uploaded-files-list', ['live']);
TabBar.addGroup('push-notifications', ['live']);
TabBar.addGroup('video', ['live']);

TabBar.addButton({
	groups: ['live'],
	id: 'external-search',
	i18nTitle: 'Knowledge_Base',
	icon: 'book',
	template: 'externalSearch',
	order: 10,
});

MessageTypes.registerType({
	id: 'livechat-close',
	system: true,
	message: 'Conversation_closed',
	data(message) {
		return {
			comment: message.msg,
		};
	},
});


import { Blaze } from 'meteor/blaze';
import { Template } from 'meteor/templating';
import _ from 'underscore';

import AutoComplete from './autocomplete-client';

//  Events on template instances, sent to the autocomplete class
const acEvents = {
	'keydown'(e, t) {
		t.ac.onKeyDown(e);
	},
	'keyup'(e, t) {
		t.ac.onKeyUp(e);
	},
	'focus'(e, t) {
		t.ac.onFocus(e);
	},
	'blur'(e, t) {
		t.ac.onBlur(e);
	},
};

Template.inputAutocomplete.events(acEvents);

Template.textareaAutocomplete.events(acEvents);

const attributes = function() {
	return _.omit(this, 'settings'); // Render all but the settings parameter
};

const autocompleteHelpers = {
	attributes,
	autocompleteContainer: new Template('AutocompleteContainer', function() {
		const ac = new AutoComplete(Blaze.getData().settings);
		// Set the autocomplete object on the parent template instance
		this.parentView.templateInstance().ac = ac;

		// Set nodes on render in the autocomplete class
		this.onViewReady(function() {
			ac.element = this.parentView.firstNode();
			ac.$element = $(ac.element);
		});
		return Blaze.With(ac, function() { //eslint-disable-line
			return Template._autocompleteContainer;
		});
	}),
};

Template.inputAutocomplete.helpers(autocompleteHelpers);

Template.textareaAutocomplete.helpers(autocompleteHelpers);

Template._autocompleteContainer.rendered = function() {
	this.data.tmplInst = this;
};

Template._autocompleteContainer.destroyed = function() {
	// Meteor._debug "autocomplete destroyed"
	this.data.teardown();
};

/*
  List rendering helpers
 */

Template._autocompleteContainer.events({
	// t.data is the AutoComplete instance; `this` is the data item
	'click .-autocomplete-item, click [data-autocomplete]'(e, t) {
		t.data.onItemClick(this, e);
	},
	'mouseenter .-autocomplete-item'(e, t) {
		t.data.onItemHover(this, e);
	},
});

Template._autocompleteContainer.helpers({
	empty() {
		return this.filteredList().count() === 0;
	},
	noMatchTemplate() {
		return this.matchedRule().noMatchTemplate || Template._noMatch;
	},
});
export { acEvents, attributes, autocompleteHelpers };


import { Meteor } from 'meteor/meteor';

import { Rooms, Subscriptions, Messages } from '../../../models';
import { callbacks } from '../../../callbacks';

export const addUserToRoom = function(rid, user, inviter, silenced) {
	const now = new Date();
	const room = Rooms.findOneById(rid);

	// Check if user is already in room
	const subscription = Subscriptions.findOneByRoomIdAndUserId(rid, user._id);
	if (subscription) {
		return;
	}

	if (room.t === 'c' || room.t === 'p') {
		// Add a new event, with an optional inviter
		callbacks.run('beforeAddedToRoom', { user, inviter }, room);

		// Keep the current event
		callbacks.run('beforeJoinRoom', user, room);
	}

	Subscriptions.createWithRoomAndUser(room, user, {
		ts: now,
		open: true,
		alert: true,
		unread: 1,
		userMentions: 1,
		groupMentions: 0,
	});

	if (!silenced) {
		if (inviter) {
			Messages.createUserAddedWithRoomIdAndUser(rid, user, {
				ts: now,
				u: {
					_id: inviter._id,
					username: inviter.username,
				},
			});
		} else if (room.prid) {
			Messages.createUserJoinWithRoomIdAndUserDiscussion(rid, user, { ts: now });
		} else {
			Messages.createUserJoinWithRoomIdAndUser(rid, user, { ts: now });
		}
	}

	if (room.t === 'c' || room.t === 'p') {
		Meteor.defer(function() {
			// Add a new event, with an optional inviter
			callbacks.run('afterAddedToRoom', { user, inviter }, room);

			// Keep the current event
			callbacks.run('afterJoinRoom', user, room);
		});
	}

	return true;
};


return Object.keys(this.roomTypes).filter((key) => this.roomTypes[key].includeInDashboard && this.roomTypes[key].includeInDashboard());


import { Meteor } from 'meteor/meteor';

import { updateAvatarOfUsername } from '../../app/ui-utils';
import { Notifications } from '../../app/notifications';

Meteor.startup(function() {
	Notifications.onLogged('updateAvatar', function(data) {
		updateAvatarOfUsername(data.username);
	});
});


<?xml version="1.0" encoding="utf-8"?>
<browserconfig>
	<msapplication>
		<tile>
			<square150x150logo src="assets/tile_70.png"/>
			<square150x150logo src="assets/tile_144.png"/>
			<square150x150logo src="assets/tile_150.png"/>
			<square310x310logo src="assets/tile_310_square.png"/>
			<wide310x150logo src="assets/tile_310_wide.png"/>
			<TileColor>#db2323</TileColor>
		</tile>
	</msapplication>
</browserconfig>


import { Meteor } from 'meteor/meteor';

export const rolesStreamer = new Meteor.Streamer('roles');
rolesStreamer.allowWrite('none');
rolesStreamer.allowRead('logged');


@use '../../styles/tools'
@use '../../styles/settings'
@use './variables' as *
@use './mixins' as *

// Theme
.v-table
  @include tools.theme($table-theme...)

  transition-duration: $table-transition-duration
  transition-property: $table-transition-property
  transition-timing-function: $table-transition-timing-function

  .v-table-divider
    border-right: $table-border

  .v-table__wrapper
    > table
      > thead
        > tr
          > th
            border-bottom: $table-border
            color: rgba(var(--v-theme-on-surface), var(--v-medium-emphasis-opacity))

      > tbody
        > tr
          &:not(:last-child)
            > td,
            > th
              border-bottom: $table-border

      > tfoot
        > tr
            > td,
            > th
              border-top: $table-border

  &.v-table--hover
    > .v-table__wrapper
      > table
        > tbody
          > tr
            &:hover td
              background: $table-hover-color

  &.v-table--fixed-header
    > .v-table__wrapper
      > table
        > thead
          > tr
            > th
              background: $table-background
              box-shadow: inset 0 -1px 0 $table-border-color
              z-index: 1

  &.v-table--fixed-footer
    > tfoot
      > tr
        > th,
        > td
          background: $table-background
          box-shadow: inset 0 1px 0 $table-border-color

// Block
.v-table
  --v-table-header-height: #{$table-header-height}

  border-radius: inherit
  // Do not inherit line-height
  line-height: $table-line-height
  max-width: 100%
  display: flex
  flex-direction: column

  > .v-table__wrapper
    > table
      width: 100%
      border-spacing: 0

      > tbody,
      > thead,
      > tfoot
        > tr
          > td,
          > th
            padding: $table-column-padding
            transition-duration: $table-transition-duration
            transition-property: $table-transition-property
            transition-timing-function: $table-transition-timing-function

          > th
            font-weight: $table-header-font-weight
            user-select: none
            text-align: start

  @at-root
    @include tools.density('v-table', $table-density) using ($modifier)
      > .v-table__wrapper
        > table
          > tbody,
          > thead,
          > tfoot
            > tr
              > th
                height: calc(var(--v-table-header-height) + $modifier)

              > td
                height: calc($table-row-height + $modifier)

// Elements
.v-table__wrapper
  border-radius: inherit
  overflow: auto
  flex: 1 1 auto

// Modifiers
.v-table--has-top
  > .v-table__wrapper
    > table
      > tbody
        > tr
          &:first-child
            &:hover
              > td
                &:first-child
                  border-top-left-radius: 0

                &:last-child
                  border-top-right-radius: 0

.v-table--has-bottom
  > .v-table__wrapper
    > table
      > tbody
        > tr
          &:last-child
            &:hover
              > td
                &:first-child
                  border-bottom-left-radius: 0

                &:last-child
                  border-bottom-right-radius: 0

.v-table--fixed-height
  > .v-table__wrapper
    overflow-y: auto

.v-table--fixed-header
  > .v-table__wrapper
    > table
      > thead
        position: sticky
        top: 0
        z-index: 1
        > tr
          > th
            border-bottom: 0px !important

.v-table--fixed-footer
  > .v-table__wrapper
    > table
      > tfoot
        > tr
          position: sticky
          bottom: 0
          z-index: 1
          > td,
          > th
              border-top: 0px !important


<template>
  <v-btn
    :aria-label="t('see-more-projects')"
    :size="size"
    :to="rpath('/resources/made-with-vuetify/')"
    color="primary"
    variant="outlined"
    @click="onClick"
  >
    <span
      class="text-capitalize font-weight-regular"
      v-text="t('see-more-projects')"
    />
  </v-btn>
</template>

<script setup>
  // Composables
  import { useGtag } from 'vue-gtag-next'
  import { useI18n } from 'vue-i18n'
  import { useRoute } from 'vue-router'

  // Utilities
  import { rpath } from '@/util/routes'

  defineProps({
    size: {
      type: String,
      default: 'large',
    },
  })

  const { event } = useGtag()
  const { name } = useRoute()
  const { t } = useI18n()

  function onClick () {
    event('click', {
      event_category: 'button',
      event_label: 'sponsors',
      value: name,
    })
  }
</script>


<template>
  <v-data-table
    :headers="headers"
    :items="desserts"
    item-value="name"
  >
    <template v-slot:item="{ item }">

        {{ item.name }}
        {{ item.calories }}
        {{ item.fat }}
        {{ item.carbs }}
        {{ item.protein }}
        {{ item.iron }}

    </template>
  </v-data-table>
</template>


border-right: 1px solid rgba(var(--v-border-color), var(--v-border-opacity))


// @ts-nocheck
/* eslint-disable */

import { CalendarEventParsed, CalendarEventVisual, CalendarTimestamp } from 'vuetify/types'
import { getTimestampIdentifier } from '../util/timestamp'

const MILLIS_IN_DAY = 86400000

export type GetRange = (event: CalendarEventParsed) => [number, number]

export function getVisuals (events: CalendarEventParsed[], minStart = 0): CalendarEventVisual[] {
  const visuals = events.map(event => ({
    event,
    columnCount: 0,
    column: 0,
    left: 0,
    width: 100,
  }))

  visuals.sort((a, b) => {
    return (Math.max(minStart, a.event.startTimestampIdentifier) - Math.max(minStart, b.event.startTimestampIdentifier)) ||
           (b.event.endTimestampIdentifier - a.event.endTimestampIdentifier)
  })

  return visuals
}

export interface ColumnGroup {
  start: number
  end: number
  visuals: CalendarEventVisual[]
}

export function hasOverlap (s0: number, e0: number, s1: number, e1: number, exclude = true): boolean {
  return exclude ? !(s0 >= e1 || e0 <= s1) : !(s0 > e1 || e0 < s1)
}

export function setColumnCount (groups: ColumnGroup[]) {
  groups.forEach(group => {
    group.visuals.forEach(groupVisual => {
      groupVisual.columnCount = groups.length
    })
  })
}

export function getRange (event: CalendarEventParsed): [number, number] {
  return [event.startTimestampIdentifier, event.endTimestampIdentifier]
}

export function getDayRange (event: CalendarEventParsed): [number, number] {
  return [event.startIdentifier, event.endIdentifier]
}

export function getNormalizedRange (event: CalendarEventParsed, dayStart: number): [number, number] {
  return [Math.max(dayStart, event.startTimestampIdentifier), Math.min(dayStart + MILLIS_IN_DAY, event.endTimestampIdentifier)]
}

export function getOpenGroup (groups: ColumnGroup[], start: number, end: number, timed: boolean) {
  for (let i = 0; i < groups.length; i++) {
    const group = groups[i]
    let intersected = false

    if (hasOverlap(start, end, group.start, group.end, timed)) {
      for (let k = 0; k < group.visuals.length; k++) {
        const groupVisual = group.visuals[k]
        const [groupStart, groupEnd] = timed ? getRange(groupVisual.event) : getDayRange(groupVisual.event)

        if (hasOverlap(start, end, groupStart, groupEnd, timed)) {
          intersected = true
          break
        }
      }
    }

    if (!intersected) {
      return i
    }
  }

  return -1
}

export function getOverlapGroupHandler (firstWeekday: number) {
  const handler = {
    groups: [] as ColumnGroup[],
    min: -1,
    max: -1,
    reset: () => {
      handler.groups = []
      handler.min = handler.max = -1
    },
    getVisuals: (day: CalendarTimestamp, dayEvents: CalendarEventParsed[], timed: boolean, reset = false) => {
      if (day.weekday === firstWeekday || reset) {
        handler.reset()
      }

      const dayStart = getTimestampIdentifier(day)
      const visuals = getVisuals(dayEvents, dayStart)

      visuals.forEach(visual => {
        const [start, end] = timed ? getRange(visual.event) : getDayRange(visual.event)

        if (handler.groups.length > 0 && !hasOverlap(start, end, handler.min, handler.max, timed)) {
          setColumnCount(handler.groups)
          handler.reset()
        }

        let targetGroup = getOpenGroup(handler.groups, start, end, timed)

        if (targetGroup === -1) {
          targetGroup = handler.groups.length

          handler.groups.push({ start, end, visuals: [] })
        }

        const target = handler.groups[targetGroup]
        target.visuals.push(visual)
        target.start = Math.min(target.start, start)
        target.end = Math.max(target.end, end)

        visual.column = targetGroup

        if (handler.min === -1) {
          handler.min = start
          handler.max = end
        } else {
          handler.min = Math.min(handler.min, start)
          handler.max = Math.max(handler.max, end)
        }
      })

      setColumnCount(handler.groups)

      if (timed) {
        handler.reset()
      }

      return visuals
    },
  }

  return handler
}


Displays linear progress bar. Can either be a String which specifies which color is applied to the progress bar (any material color or theme color - **primary**, **secondary**, **success**, **info**, **warning**, **error**) or a Boolean which uses the component **color** (set by color prop - if it's supported by the component) or the primary color.


// Styles
import './VSlider.sass'

// Components
import { VSliderThumb } from './VSliderThumb'
import { VSliderTrack } from './VSliderTrack'
import { makeVInputProps, VInput } from '@/components/VInput/VInput'
import { VLabel } from '@/components/VLabel'

// Composables
import { makeSliderProps, useSlider, useSteps } from './slider'
import { makeFocusProps, useFocus } from '@/composables/focus'
import { useRtl } from '@/composables/locale'
import { useProxiedModel } from '@/composables/proxiedModel'

// Utilities
import { computed, ref } from 'vue'
import { genericComponent, propsFactory, useRender } from '@/util'

// Types
import type { VSliderThumbSlots } from './VSliderThumb'
import type { VSliderTrackSlots } from './VSliderTrack'
import type { VInputSlot, VInputSlots } from '@/components/VInput/VInput'

export type VSliderSlots = VInputSlots & VSliderThumbSlots & VSliderTrackSlots & {
  label: VInputSlot
}

export const makeVSliderProps = propsFactory({
  ...makeFocusProps(),
  ...makeSliderProps(),
  ...makeVInputProps(),

  modelValue: {
    type: [Number, String],
    default: 0,
  },
}, 'VSlider')

export const VSlider = genericComponent<VSliderSlots>()({
  name: 'VSlider',

  props: makeVSliderProps(),

  emits: {
    'update:focused': (value: boolean) => true,
    'update:modelValue': (v: number) => true,
    start: (value: number) => true,
    end: (value: number) => true,
  },

  setup (props, { slots, emit }) {
    const thumbContainerRef = ref()
    const { rtlClasses } = useRtl()

    const steps = useSteps(props)

    const model = useProxiedModel(
      props,
      'modelValue',
      undefined,
      value => {
        return steps.roundValue(value == null ? steps.min.value : value)
      },
    )

    const {
      min,
      max,
      mousePressed,
      roundValue,
      onSliderMousedown,
      onSliderTouchstart,
      trackContainerRef,
      position,
      hasLabels,
      readonly,
    } = useSlider({
      props,
      steps,
      onSliderStart: () => {
        emit('start', model.value)
      },
      onSliderEnd: ({ value }) => {
        const roundedValue = roundValue(value)
        model.value = roundedValue
        emit('end', roundedValue)
      },
      onSliderMove: ({ value }) => model.value = roundValue(value),
      getActiveThumb: () => thumbContainerRef.value?.$el,
    })

    const { isFocused, focus, blur } = useFocus(props)
    const trackStop = computed(() => position(model.value))

    useRender(() => {
      const inputProps = VInput.filterProps(props)
      const hasPrepend = !!(props.label || slots.label || slots.prepend)

      return (
        <VInput
          class={[
            'v-slider',
            {
              'v-slider--has-labels': !!slots['tick-label'] || hasLabels.value,
              'v-slider--focused': isFocused.value,
              'v-slider--pressed': mousePressed.value,
              'v-slider--disabled': props.disabled,
            },
            rtlClasses.value,
            props.class,
          ]}
          style={ props.style }
          { ...inputProps }
          focused={ isFocused.value }
        >
          {{
            ...slots,
            prepend: hasPrepend ? slotProps => (
              <>
                { slots.label?.(slotProps) ?? (
                  props.label
                    ? (
                      <VLabel
                        id={ slotProps.id.value }
                        class="v-slider__label"
                        text={ props.label }
                      />
                    ) : undefined
                )}

                { slots.prepend?.(slotProps) }
              </>
            ) : undefined,
            default: ({ id, messagesId }) => (
              <div
                class="v-slider__container"
                onMousedown={ !readonly.value ? onSliderMousedown : undefined }
                onTouchstartPassive={ !readonly.value ? onSliderTouchstart : undefined }
              >
                <input
                  id={ id.value }
                  name={ props.name || id.value }
                  disabled={ !!props.disabled }
                  readonly={ !!props.readonly }
                  tabindex="-1"
                  value={ model.value }
                />

                <VSliderTrack
                  ref={ trackContainerRef }
                  start={ 0 }
                  stop={ trackStop.value }
                >
                  {{ 'tick-label': slots['tick-label'] }}
                </VSliderTrack>

                <VSliderThumb
                  ref={ thumbContainerRef }
                  aria-describedby={ messagesId.value }
                  focused={ isFocused.value }
                  min={ min.value }
                  max={ max.value }
                  modelValue={ model.value }
                  onUpdate:modelValue={ v => (model.value = v) }
                  position={ trackStop.value }
                  elevation={ props.elevation }
                  onFocus={ focus }
                  onBlur={ blur }
                  ripple={ props.ripple }
                >
                  {{ 'thumb-label': slots['thumb-label'] }}
                </VSliderThumb>
              </div>
            ),
          }}
        </VInput>
      )
    })

    return {}
  },
})

export type VSlider = InstanceType<typeof VSlider>


.v-data-table.v-table--fixed-header > .v-table__wrapper > table > thead > tr > th.v-data-table-column--fixed

// Components
import { VDataTableColumn } from './VDataTableColumn'
import { VCheckboxBtn } from '@/components/VCheckbox'
import { VIcon } from '@/components/VIcon'

// Composables
import { useHeaders } from './composables/headers'
import { useSelection } from './composables/select'
import { useSort } from './composables/sort'
import { useBackgroundColor } from '@/composables/color'
import { IconValue } from '@/composables/icons'
import { LoaderSlot, makeLoaderProps, useLoader } from '@/composables/loader'

// Utilities
import { computed } from 'vue'
import { convertToUnit, genericComponent, propsFactory, useRender } from '@/util'

// Types
import type { CSSProperties, UnwrapRef } from 'vue'
import type { provideSelection } from './composables/select'
import type { provideSort } from './composables/sort'
import type { InternalDataTableHeader } from './types'
import type { LoaderSlotProps } from '@/composables/loader'

export type HeadersSlotProps = {
  headers: InternalDataTableHeader[][]
  columns: InternalDataTableHeader[]
  sortBy: UnwrapRef<ReturnType<typeof provideSort>['sortBy']>
  someSelected: UnwrapRef<ReturnType<typeof provideSelection>['someSelected']>
  allSelected: UnwrapRef<ReturnType<typeof provideSelection>['allSelected']>
  toggleSort: ReturnType<typeof provideSort>['toggleSort']
  selectAll: ReturnType<typeof provideSelection>['selectAll']
  getSortIcon: (column: InternalDataTableHeader) => IconValue
  isSorted: ReturnType<typeof provideSort>['isSorted']
}

type VDataTableHeaderCellColumnSlotProps = {
  column: InternalDataTableHeader
  selectAll: ReturnType<typeof provideSelection>['selectAll']
  isSorted: ReturnType<typeof provideSort>['isSorted']
  toggleSort: ReturnType<typeof provideSort>['toggleSort']
  sortBy: UnwrapRef<ReturnType<typeof provideSort>['sortBy']>
  someSelected: UnwrapRef<ReturnType<typeof provideSelection>['someSelected']>
  allSelected: UnwrapRef<ReturnType<typeof provideSelection>['allSelected']>
  getSortIcon: (column: InternalDataTableHeader) => IconValue
}

export type VDataTableHeadersSlots = {
  headers: HeadersSlotProps
  loader: LoaderSlotProps
  'column.data-table-select': VDataTableHeaderCellColumnSlotProps
  'column.data-table-expand': VDataTableHeaderCellColumnSlotProps
} & { [key: `column.${string}`]: VDataTableHeaderCellColumnSlotProps }

export const makeVDataTableHeadersProps = propsFactory({
  color: String,
  sticky: Boolean,
  multiSort: Boolean,
  sortAscIcon: {
    type: IconValue,
    default: '$sortAsc',
  },
  sortDescIcon: {
    type: IconValue,
    default: '$sortDesc',
  },

  ...makeLoaderProps(),
}, 'VDataTableHeaders')

export const VDataTableHeaders = genericComponent<VDataTableHeadersSlots>()({
  name: 'VDataTableHeaders',

  props: makeVDataTableHeadersProps(),

  setup (props, { slots }) {
    const { toggleSort, sortBy, isSorted } = useSort()
    const { someSelected, allSelected, selectAll, showSelectAll } = useSelection()
    const { columns, headers } = useHeaders()
    const { loaderClasses } = useLoader(props)

    function getFixedStyles (column: InternalDataTableHeader, y: number): CSSProperties | undefined {
      if (!props.sticky && !column.fixed) return undefined

      return {
        position: 'sticky',
        left: column.fixed ? convertToUnit(column.fixedOffset) : undefined,
        top: props.sticky ? `calc(var(--v-table-header-height) * ${y})` : undefined,
      }
    }

    function getSortIcon (column: InternalDataTableHeader) {
      const item = sortBy.value.find(item => item.key === column.key)

      if (!item) return props.sortAscIcon

      return item.order === 'asc' ? props.sortAscIcon : props.sortDescIcon
    }

    const { backgroundColorClasses, backgroundColorStyles } = useBackgroundColor(props, 'color')

    const slotProps = computed(() => ({
      headers: headers.value,
      columns: columns.value,
      toggleSort,
      isSorted,
      sortBy: sortBy.value,
      someSelected: someSelected.value,
      allSelected: allSelected.value,
      selectAll,
      getSortIcon,
    } satisfies HeadersSlotProps))

    const VDataTableHeaderCell = ({ column, x, y }: { column: InternalDataTableHeader, x: number, y: number }) => {
      const noPadding = column.key === 'data-table-select' || column.key === 'data-table-expand'

      return (
        <VDataTableColumn
          tag="th"
          align={ column.align }
          class={[
            'v-data-table__th',
            {
              'v-data-table__th--sortable': column.sortable,
              'v-data-table__th--sorted': isSorted(column),
              'v-data-table__th--fixed': column.fixed,
            },
            loaderClasses.value,
          ]}
          style={{
            width: convertToUnit(column.width),
            minWidth: convertToUnit(column.width),
            ...getFixedStyles(column, y),
          }}
          colspan={ column.colspan }
          rowspan={ column.rowspan }
          onClick={ column.sortable ? () => toggleSort(column) : undefined }
          lastFixed={ column.lastFixed }
          noPadding={ noPadding }
          { ...column.headerProps }
        >
          {{
            default: () => {
              const columnSlotName = `column.${column.key}` as const
              const columnSlotProps: VDataTableHeaderCellColumnSlotProps = {
                column,
                selectAll,
                isSorted,
                toggleSort,
                sortBy: sortBy.value,
                someSelected: someSelected.value,
                allSelected: allSelected.value,
                getSortIcon,
              }

              if (slots[columnSlotName]) return slots[columnSlotName]!(columnSlotProps)

              if (column.key === 'data-table-select') {
                return slots['column.data-table-select']?.(columnSlotProps) ?? (showSelectAll && (
                  <VCheckboxBtn
                    modelValue={ allSelected.value }
                    indeterminate={ someSelected.value && !allSelected.value }
                    onUpdate:modelValue={ selectAll }
                  />
                ))
              }

              return (

                  { column.title }
                  { column.sortable && (
                    <VIcon
                      key="icon"
                      class="v-data-table-header__sort-icon"
                      icon={ getSortIcon(column) }
                    />
                  )}
                  { props.multiSort && isSorted(column) && (

                      { sortBy.value.findIndex(x => x.key === column.key) + 1 }

                  )}

              )
            },
          }}
        </VDataTableColumn>
      )
    }

    useRender(() => {
      return (
        <>
          { slots.headers
            ? slots.headers(slotProps.value)
            : headers.value.map((row, y) => (

                { row.map((column, x) => (
                  <VDataTableHeaderCell column={ column } x={ x } y={ y } />
                ))}

            ))}

          { props.loading && (

                <LoaderSlot
                  name="v-data-table-progress"
                  active
                  color={ typeof props.loading === 'boolean' ? undefined : props.loading }
                  indeterminate
                  v-slots={{ default: slots.loader }}
                />

          )}
        </>
      )
    })
  },
})


<template>
  <usage-example
    v-model="model"
    :code="code"
    :options="options"
    :name="name"
  >
    <div>
      <v-expansion-panels v-bind="props">
        <v-expansion-panel
          title="Title"
          text="Lorem ipsum dolor sit amet consectetur adipisicing elit. Commodi, ratione debitis quis est labore voluptatibus! Eaque cupiditate minima"
        >
        </v-expansion-panel>
      </v-expansion-panels>
    </div>
  </usage-example>
</template>

<script setup>
  // Utilities
  import { computed, ref } from 'vue'
  import { propsToString } from '@/util/helpers'

  const name = 'v-expansion-panels'
  const model = ref('default')
  const options = []
  const props = computed(() => {
    return {}
  })

  const slots = computed(() => {
    return `
  <v-expansion-panel
    title="Title"
    text="Lorem ipsum dolor sit amet consectetur adipisicing elit. Commodi, ratione debitis quis est labore voluptatibus! Eaque cupiditate minima"
  >
  </v-expansion-panel>
`
  })

  const code = computed(() => {
    return `<${name}${propsToString(props.value)}>${slots.value}</${name}>`
  })
</script>


// Types
import type { IfAny } from '@vue/shared' // eslint-disable-line vue/prefer-import-from-vue
import type { ComponentObjectPropsOptions, Prop, PropType } from 'vue'

/**
 * Creates a factory function for props definitions.
 * This is used to define props in a composable then override
 * default values in an implementing component.
 *
 * @example Simplified signature
 * (props: Props) => (defaults?: Record<keyof props, any>) => Props
 *
 * @example Usage
 * const makeProps = propsFactory({
 *   foo: String,
 * })
 *
 * defineComponent({
 *   props: {
 *     ...makeProps({
 *       foo: 'a',
 *     }),
 *   },
 *   setup (props) {
 *     // would be "string | undefined", now "string" because a default has been provided
 *     props.foo
 *   },
 * }
 */

export function propsFactory<
  PropsOptions extends ComponentObjectPropsOptions
> (props: PropsOptions, source: string) {
  return <Defaults extends PartialKeys<PropsOptions> = {}>(
    defaults?: Defaults
  ): AppendDefault<PropsOptions, Defaults> => {
    return Object.keys(props).reduce<any>((obj, prop) => {
      const isObjectDefinition = typeof props[prop] === 'object' && props[prop] != null && !Array.isArray(props[prop])
      const definition = isObjectDefinition ? props[prop] : { type: props[prop] }

      if (defaults && prop in defaults) {
        obj[prop] = {
          ...definition,
          default: defaults[prop],
        }
      } else {
        obj[prop] = definition
      }

      if (source && !obj[prop].source) {
        obj[prop].source = source
      }

      return obj
    }, {})
  }
}

type AppendDefault<T extends ComponentObjectPropsOptions, D extends PartialKeys<T>> = {
  [P in keyof T]-?: unknown extends D[P]
    ? T[P]
    : T[P] extends Record<string, unknown>
      ? Omit<T[P], 'type' | 'default'> & {
        type: PropType<MergeDefault<T[P], D[P]>>
        default: MergeDefault<T[P], D[P]>
      }
      : {
        type: PropType<MergeDefault<T[P], D[P]>>
        default: MergeDefault<T[P], D[P]>
      }
}

type MergeDefault<T, D> = unknown extends D ? InferPropType<T> : (NonNullable<InferPropType<T>> | D)

/**
 * Like `Partial<T>` but doesn't care what the value is
 */
type PartialKeys<T> = { [P in keyof T]?: unknown }

// Copied from Vue
type InferPropType<T> = [T] extends [null]
  ? any // null & true would fail to infer
  : [T] extends [{ type: null | true }]
    // As TS issue https://github.com/Microsoft/TypeScript/issues/14829
    // somehow `ObjectConstructor` when inferred from { (): T } becomes `any`
    // `BooleanConstructor` when inferred from PropConstructor(with PropMethod) becomes `Boolean`
    ? any
    : [T] extends [ObjectConstructor | { type: ObjectConstructor }]
      ? Record<string, any>
      : [T] extends [BooleanConstructor | { type: BooleanConstructor }]
        ? boolean
        : [T] extends [DateConstructor | { type: DateConstructor }]
          ? Date
          : [T] extends [(infer U)[] | { type: (infer U)[] }]
            ? U extends DateConstructor
              ? Date | InferPropType<U>
              : InferPropType<U>
            : [T] extends [Prop<infer V, infer D>]
              ? unknown extends V
                ? IfAny<V, V, D>
                : V
              : T


onClick={ column.sortable ? () => toggleSort(column) : undefined }

@use '../../styles/settings'
@use '../../styles/tools'
@use '../../components/VTable/variables' as *
@use './variables' as *

.v-data-table
  width: 100%

.v-data-table__table
  width: 100%
  border-collapse: separate
  border-spacing: 0

.v-data-table__tr
  &--focus
    border: 1px dotted black

  &--clickable
    cursor: pointer

.v-data-table
  .v-table__wrapper
    > table
      > thead,
        tbody
        > tr
          > td,
            th

            &.v-data-table-column--align-end
              text-align: end

              .v-data-table-header__content
                flex-direction: row-reverse

            &.v-data-table-column--align-center
              text-align: center

              .v-data-table-header__content
                justify-content: center

            &.v-data-table-column--no-padding
              padding: 0 8px

          > th
            align-items: center

          > th.v-data-table__th--fixed
              position: sticky

          > th.v-data-table__th--sortable:hover
              cursor: pointer
              color: rgba(var(--v-theme-on-surface), var(--v-high-emphasis-opacity))

          > th:not(.v-data-table__th--sorted)
              .v-data-table-header__sort-icon
                opacity: 0

              &:hover
                .v-data-table-header__sort-icon
                  opacity: 0.5

.v-data-table-column--fixed
  background: $table-background
  position: sticky
  left: 0

.v-data-table-column--last-fixed
  border-right: 1px solid rgba(var(--v-border-color), var(--v-border-opacity))

.v-data-table-group-header-row
  td
    background: rgba(var(--v-theme-surface))
    color: rgba(var(--v-theme-on-surface))

    > span
      padding-left: 5px

.v-data-table--loading
  .v-data-table__td
    opacity: 0.3

.v-data-table-group-header-row__column
  padding-left: calc(var(--v-data-table-group-header-row-depth) * 16px) !important

.v-data-table-header__content
  display: flex
  align-items: center

.v-data-table-header__sort-badge
  display: inline-flex
  justify-content: center
  align-items: center
  font-size: 0.875rem
  padding: 4px
  border-radius: 50%
  background: $data-table-header-sort-badge-color
  min-width: $data-table-header-sort-badge-size
  min-height: $data-table-header-sort-badge-size
  width: $data-table-header-sort-badge-size
  height: $data-table-header-sort-badge-size

.v-data-table-progress
  > th
    border: none !important
    height: auto !important
    padding: 0 !important

.v-data-table-rows-loading,
.v-data-table-rows-no-data
  text-align: center


// Styles
import './VSelectionControl.sass'

// Components
import { VIcon } from '@/components/VIcon'
import { VLabel } from '@/components/VLabel'
import { makeSelectionControlGroupProps, VSelectionControlGroupSymbol } from '@/components/VSelectionControlGroup/VSelectionControlGroup'

// Composables
import { useBackgroundColor, useTextColor } from '@/composables/color'
import { makeComponentProps } from '@/composables/component'
import { useDensity } from '@/composables/density'
import { useProxiedModel } from '@/composables/proxiedModel'

// Directives
import { Ripple } from '@/directives/ripple'

// Utilities
import { computed, inject, nextTick, ref, shallowRef } from 'vue'
import {
  filterInputAttrs,
  genericComponent,
  getUid,
  matchesSelector,
  propsFactory,
  useRender,
  wrapInArray,
} from '@/util'

// Types
import type { CSSProperties, ExtractPropTypes, Ref, VNode, WritableComputedRef } from 'vue'
import type { IconValue } from '@/composables/icons'
import type { GenericProps } from '@/util'

export type SelectionControlSlot = {
  model: WritableComputedRef<any>
  textColorClasses: Ref<string[]>
  textColorStyles: Ref<CSSProperties>
  backgroundColorClasses: Ref<string[]>
  backgroundColorStyles: Ref<CSSProperties>
  inputNode: VNode
  icon: IconValue | undefined
  props: {
    onBlur: (e: Event) => void
    onFocus: (e: FocusEvent) => void
    id: string
  }
}

export type VSelectionControlSlots = {
  default: {
    backgroundColorClasses: Ref<string[]>
    backgroundColorStyles: Ref<CSSProperties>
  }
  label: { label: string | undefined, props: Record<string, unknown> }
  input: SelectionControlSlot
}

export const makeVSelectionControlProps = propsFactory({
  label: String,
  baseColor: String,
  trueValue: null,
  falseValue: null,
  value: null,

  ...makeComponentProps(),
  ...makeSelectionControlGroupProps(),
}, 'VSelectionControl')

export function useSelectionControl (
  props: ExtractPropTypes<ReturnType<typeof makeVSelectionControlProps>> & {
    'onUpdate:modelValue': ((val: any) => void) | undefined
  }
) {
  const group = inject(VSelectionControlGroupSymbol, undefined)
  const { densityClasses } = useDensity(props)
  const modelValue = useProxiedModel(props, 'modelValue')
  const trueValue = computed(() => (
    props.trueValue !== undefined ? props.trueValue
    : props.value !== undefined ? props.value
    : true
  ))
  const falseValue = computed(() => props.falseValue !== undefined ? props.falseValue : false)
  const isMultiple = computed(() => (
    !!props.multiple ||
    (props.multiple == null && Array.isArray(modelValue.value))
  ))
  const model = computed({
    get () {
      const val = group ? group.modelValue.value : modelValue.value

      return isMultiple.value
        ? val.some((v: any) => props.valueComparator(v, trueValue.value))
        : props.valueComparator(val, trueValue.value)
    },
    set (val: boolean) {
      if (props.readonly) return

      const currentValue = val ? trueValue.value : falseValue.value

      let newVal = currentValue

      if (isMultiple.value) {
        newVal = val
          ? [...wrapInArray(modelValue.value), currentValue]
          : wrapInArray(modelValue.value).filter((item: any) => !props.valueComparator(item, trueValue.value))
      }

      if (group) {
        group.modelValue.value = newVal
      } else {
        modelValue.value = newVal
      }
    },
  })
  const { textColorClasses, textColorStyles } = useTextColor(computed(() => {
    if (props.error || props.disabled) return undefined

    return model.value ? props.color : props.baseColor
  }))
  const { backgroundColorClasses, backgroundColorStyles } = useBackgroundColor(computed(() => {
    return (
      model.value &&
      !props.error &&
      !props.disabled
    ) ? props.color : undefined
  }))
  const icon = computed(() => model.value ? props.trueIcon : props.falseIcon)

  return {
    group,
    densityClasses,
    trueValue,
    falseValue,
    model,
    textColorClasses,
    textColorStyles,
    backgroundColorClasses,
    backgroundColorStyles,
    icon,
  }
}

export const VSelectionControl = genericComponent<new <T>(
  props: {
    modelValue?: T
    'onUpdate:modelValue'?: (val: T) => any
  },
  slots: VSelectionControlSlots,
) => GenericProps<typeof props, typeof slots>>()({
  name: 'VSelectionControl',

  directives: { Ripple },

  inheritAttrs: false,

  props: makeVSelectionControlProps(),

  emits: {
    'update:modelValue': (val: any) => true,
  },

  setup (props, { attrs, slots }) {
    const {
      group,
      densityClasses,
      icon,
      model,
      textColorClasses,
      textColorStyles,
      backgroundColorClasses,
      backgroundColorStyles,
      trueValue,
    } = useSelectionControl(props)
    const uid = getUid()
    const id = computed(() => props.id || `input-${uid}`)
    const isFocused = shallowRef(false)
    const isFocusVisible = shallowRef(false)
    const input = ref<HTMLInputElement>()

    group?.onForceUpdate(() => {
      if (input.value) {
        input.value.checked = model.value
      }
    })

    function onFocus (e: FocusEvent) {
      isFocused.value = true
      if (matchesSelector(e.target as HTMLElement, ':focus-visible') !== false) {
        isFocusVisible.value = true
      }
    }

    function onBlur () {
      isFocused.value = false
      isFocusVisible.value = false
    }

    function onInput (e: Event) {
      if (props.readonly && group) {
        nextTick(() => group.forceUpdate())
      }
      model.value = (e.target as HTMLInputElement).checked
    }

    useRender(() => {
      const label = slots.label
        ? slots.label({
          label: props.label,
          props: { for: id.value },
        })
        : props.label
      const [rootAttrs, inputAttrs] = filterInputAttrs(attrs)

      const inputNode = (
        <input
          ref={ input }
          checked={ model.value }
          disabled={ !!(props.readonly || props.disabled) }
          id={ id.value }
          onBlur={ onBlur }
          onFocus={ onFocus }
          onInput={ onInput }
          aria-disabled={ !!(props.readonly || props.disabled) }
          type={ props.type }
          value={ trueValue.value }
          name={ props.name }
          aria-checked={ props.type === 'checkbox' ? model.value : undefined }
          { ...inputAttrs }
        />
      )

      return (
        <div
          class={[
            'v-selection-control',
            {
              'v-selection-control--dirty': model.value,
              'v-selection-control--disabled': props.disabled,
              'v-selection-control--error': props.error,
              'v-selection-control--focused': isFocused.value,
              'v-selection-control--focus-visible': isFocusVisible.value,
              'v-selection-control--inline': props.inline,
            },
            densityClasses.value,
            props.class,
          ]}
          { ...rootAttrs }
          style={ props.style }
        >
          <div
            class={[
              'v-selection-control__wrapper',
              textColorClasses.value,
            ]}
            style={ textColorStyles.value }
          >
            { slots.default?.({
              backgroundColorClasses,
              backgroundColorStyles,
            })}

            <div
              class={[
                'v-selection-control__input',
              ]}
              v-ripple={ props.ripple && [
                !props.disabled && !props.readonly,
                null,
                ['center', 'circle'],
              ]}
            >
              { slots.input?.({
                model,
                textColorClasses,
                textColorStyles,
                backgroundColorClasses,
                backgroundColorStyles,
                inputNode,
                icon: icon.value,
                props: {
                  onFocus,
                  onBlur,
                  id: id.value,
                },
              } satisfies SelectionControlSlot) ?? (
                <>
                  { icon.value && <VIcon key="icon" icon={ icon.value } /> }

                  { inputNode }
                </>
              )}
            </div>
          </div>

          { label && (
            <VLabel for={ id.value } clickable onClick={ (e: Event) => e.stopPropagation() }>
              { label }
            </VLabel>
          )}
        </div>
      )
    })

    return {
      isFocused,
      input,
    }
  },
})

export type VSelectionControl = InstanceType<typeof VSelectionControl>


<template>
  <usage-example
    v-model="model"
    :code="code"
    :name="name"
    :options="options"
  >
    <div>
      <v-virtual-scroll v-bind="props" :items="items">
        <template v-slot:default="{ item }">
          Item {{ item }}
        </template>
      </v-virtual-scroll>
    </div>

    <!-- <template v-slot:configuration>
    </template> -->
  </usage-example>
</template>

<script setup>
  // Utilities
  import { computed, ref } from 'vue'
  import { propsToString } from '@/util/helpers'

  const name = 'v-virtual-scroll'
  const model = ref('default')
  const items = Array.from({ length: 1000 }, (k, v) => v + 1)
  const options = []
  const props = computed(() => {
    return {
      height: 300,
      items: Array.from({ length: 1000 }, (k, v) => v + 1).slice(0, 30),
    }
  })

  const slots = computed(() => {
    return `
  <template v-slot:default="{ item }">
    Item {{ item }}
  </template>
`
  })

  const code = computed(() => {
    return `<${name}${propsToString(props.value)}>${slots.value}</${name}>`
  })
</script>


export { VSkeletonLoader } from './VSkeletonLoader'


---
layout: user
meta:
  nav: Dashboard
  title: User Options
  description: User Options
  keywords: user options
---

<script setup>
  import Options from '@/components/app/settings/Options.vue'
</script>

### Options

Adjust options that modify your Documentation experience

----

<options />


// Utilities
import { createSimpleFunctional } from '@/util'

export const VCardSubtitle = createSimpleFunctional('v-card-subtitle')

export type VCardSubtitle = InstanceType<typeof VCardSubtitle>


export type EasingFunction = (t: number) => number

// linear
export const linear = (t: number) => t
// accelerating from zero velocity
export const easeInQuad = (t: number) => t ** 2
// decelerating to zero velocity
export const easeOutQuad = (t: number) => t * (2 - t)
// acceleration until halfway, then deceleration
export const easeInOutQuad = (t: number) => (t < 0.5 ? 2 * t ** 2 : -1 + (4 - 2 * t) * t)
// accelerating from zero velocity
export const easeInCubic = (t: number) => t ** 3
// decelerating to zero velocity
export const easeOutCubic = (t: number) => --t ** 3 + 1
// acceleration until halfway, then deceleration
export const easeInOutCubic = (t: number) => t < 0.5 ? 4 * t ** 3 : (t - 1) * (2 * t - 2) * (2 * t - 2) + 1
// accelerating from zero velocity
export const easeInQuart = (t: number) => t ** 4
// decelerating to zero velocity
export const easeOutQuart = (t: number) => 1 - --t ** 4
// acceleration until halfway, then deceleration
export const easeInOutQuart = (t: number) => (t < 0.5 ? 8 * t * t * t * t : 1 - 8 * --t * t * t * t)
// accelerating from zero velocity
export const easeInQuint = (t: number) => t ** 5
// decelerating to zero velocity
export const easeOutQuint = (t: number) => 1 + --t ** 5
// acceleration until halfway, then deceleration
export const easeInOutQuint = (t: number) => t < 0.5 ? 16 * t ** 5 : 1 + 16 * --t ** 5


<template>
  <v-row justify="center">
    <v-col
      cols="12"
      sm="7"
      md="6"
      lg="5"
    >
      <v-sheet
        elevation="10"
        rounded="xl"
      >
        <v-sheet
          class="pa-3 bg-primary text-right"
          rounded="t-xl"
        >
          <v-btn icon>
            <v-icon>mdi-content-save-cog-outline</v-icon>
          </v-btn>

          <v-btn
            class="ms-2"
            icon
          >
            <v-icon>mdi-check-bold</v-icon>
          </v-btn>
        </v-sheet>

        <div class="pa-4">
          <v-chip-group
            selected-class="text-primary"
            column
          >
            <v-chip
              v-for="tag in tags"
              :key="tag"
            >
              {{ tag }}
            </v-chip>
          </v-chip-group>
        </div>
      </v-sheet>
    </v-col>
  </v-row>
</template>

<script setup>
  const tags = [
    'Work',
    'Home Improvement',
    'Vacation',
    'Food',
    'Drawers',
    'Shopping',
    'Art',
    'Tech',
    'Creative Writing',
  ]
</script>

<script>
  export default {
    data: () => ({
      tags: [
        'Work',
        'Home Improvement',
        'Vacation',
        'Food',
        'Drawers',
        'Shopping',
        'Art',
        'Tech',
        'Creative Writing',
      ],
    }),
  }
</script>


<template>

    Lorem ipsum

</template>


export { VAlert } from './VAlert'
export { VAlertTitle } from './VAlertTitle'


// Utilities
import { CircularBuffer } from '@/util'

const HORIZON = 100 // ms
const HISTORY = 20 // number of samples to keep

export interface Sample {
  t: number
  d: number
}

/** @see https://android.googlesource.com/platform/frameworks/native/+/master/libs/input/VelocityTracker.cpp */
function kineticEnergyToVelocity (work: number) {
  const sqrt2 = 1.41421356237
  return (work < 0 ? -1.0 : 1.0) * Math.sqrt(Math.abs(work)) * sqrt2
}

/**
 * Returns pointer velocity in px/s
 */
export function calculateImpulseVelocity (samples: Sample[]) {
  // The input should be in reversed time order (most recent sample at index i=0)
  if (samples.length < 2) {
    // if 0 or 1 points, velocity is zero
    return 0
  }
  // if (samples[1].t > samples[0].t) {
  //   // Algorithm will still work, but not perfectly
  //   consoleWarn('Samples provided to calculateImpulseVelocity in the wrong order')
  // }
  if (samples.length === 2) {
    // if 2 points, basic linear calculation
    if (samples[1].t === samples[0].t) {
      // consoleWarn(`Events have identical time stamps t=${samples[0].t}, setting velocity = 0`)
      return 0
    }
    return (samples[1].d - samples[0].d) / (samples[1].t - samples[0].t)
  }
  // Guaranteed to have at least 3 points here
  // start with the oldest sample and go forward in time
  let work = 0
  for (let i = samples.length - 1; i > 0; i--) {
    if (samples[i].t === samples[i - 1].t) {
      // consoleWarn(`Events have identical time stamps t=${samples[i].t}, skipping sample`)
      continue
    }
    const vprev = kineticEnergyToVelocity(work) // v[i-1]
    const vcurr = (samples[i].d - samples[i - 1].d) / (samples[i].t - samples[i - 1].t) // v[i]
    work += (vcurr - vprev) * Math.abs(vcurr)
    if (i === samples.length - 1) {
      work *= 0.5
    }
  }
  return kineticEnergyToVelocity(work) * 1000
}

export function useVelocity () {
  const touches: Record<number, CircularBuffer<[number, Touch]> | undefined> = {}

  function addMovement (e: TouchEvent) {
    Array.from(e.changedTouches).forEach(touch => {
      const samples = touches[touch.identifier] ?? (touches[touch.identifier] = new CircularBuffer(HISTORY))
      samples.push([e.timeStamp, touch])
    })
  }

  function endTouch (e: TouchEvent) {
    Array.from(e.changedTouches).forEach(touch => {
      delete touches[touch.identifier]
    })
  }

  function getVelocity (id: number) {
    const samples = touches[id]?.values().reverse()

    if (!samples) {
      throw new Error(`No samples for touch id ${id}`)
    }

    const newest = samples[0]
    const x: Sample[] = []
    const y: Sample[] = []
    for (const val of samples) {
      if (newest[0] - val[0] > HORIZON) break

      x.push({ t: val[0], d: val[1].clientX })
      y.push({ t: val[0], d: val[1].clientY })
    }

    return {
      x: calculateImpulseVelocity(x),
      y: calculateImpulseVelocity(y),
      get direction () {
        const { x, y } = this
        const [absX, absY] = [Math.abs(x), Math.abs(y)]

        return absX > absY && x >= 0 ? 'right'
          : absX > absY && x <= 0 ? 'left'
          : absY > absX && y >= 0 ? 'down'
          : absY > absX && y <= 0 ? 'up'
          : oops()
      },
    }
  }

  return { addMovement, endTouch, getVelocity }
}

function oops (): never {
  throw new Error()
}


---
meta:
  title: Frequently asked questions
  description: Stuck on a problem? Check out the most frequently asked questions by the Vuetify community.
  keywords: frequently asked questions, faq
related:
  - /introduction/why-vuetify/
  - /getting-started/contributing/
  - /getting-started/installation/
---

# Frequently asked questions

Stuck on a particular problem? Check some of these common gotchas before creating a ticket. If you still cannot find what you are looking for, you can submit an [issue](https://issues.vuetifyjs.com/) on GitHub or ask in our [community](https://community.vuetifyjs.com/).

<promoted slug="discord-subscriber-help" />

## Questions

The following responses are a collection of common questions asked by the Vuetify community.

* **What is Vuetify?** { #what-is-vuetify }

  Vuetify is a Vue.js framework that helps to create beautiful and responsive user interfaces. It includes a wide variety of customizable and reusable components for building modern applications.

* **Does Vuetify provide support?** { #does-vuetify-provide-support }

  Vuetify is a free to use Open Source project released under the [MIT](http://opensource.org/licenses/MIT) license. There are multiple ways to receive support for Vuetify:

  * Join our [Discord Community](https://community.vuetifyjs.com/) - (Free/Paid)
  * Ask a question on [GitHub Discussions](https://discussions.vuetifyjs.com/) - (Free)
  * Get [Direct Support](/introduction/enterprise-support/) from Vuetify - (Paid)

* **What is the difference between Vuetify and Vue?** { #what-is-the-difference-between-vuetify-and-vue }

  Vuetify is a framework that is built on top of Vue.js. It is a collection of components that can be used to build applications. Vue.js is a JavaScript framework that is used to build user interfaces.

* **What versions of Vue.js are compatible with Vuetify?** { #what-versions-of-vuejs-are-compatible-with-vuetify }

  Vuetify is compatible with Vue.js 3.0.0 and above.

* **Can I use Vuetify with other CSS frameworks?** { #can-i-use-vuetify-with-other-css-frameworks }

  Yes, you can use Vuetify with other CSS frameworks, but it is typically not recommended. If you are integrating Vuetify into an existing application that is using another CSS framework, you may want to disable the default color and utility generation. See the [SASS Variables](/features/sass-variables/) page for more information.

* **Can I customize the styling of Vuetify components?** { #can-i-customize-the-styling-of-vuetify-components}

  Yes, you can customize the styling of Vuetify components using the [Global configuration](/features/global-configuration/). Vuetify also provides [SASS variables](/features/sass-variables/) that can be overridden to change the look and feel of the components.

* **Where can I get help with Vuetify?** { #where-can-i-get-help-with-vuetify }

  If you need help with an issue, please use one of our help channels:

  * [Vuetify Enterprise Support](/introduction/enterprise-support/)
  * [Discord Community](https://community.vuetifyjs.com/)
  * [GitHub Discussions](https://discussions.vuetifyjs.com/)

* **Can I contribute to Vuetify?** { #can-i-contribute-to-vuetify }

  Yes, we welcome all contributions. Please see our [contributing guide](/getting-started/contributing/) for more information.

* **Can I use Vuetify with server-side rendering?** { #can-i-use-vuetify-with-server-side-rendering }

  Yes, Vuetify supports server-side rendering. Set the `ssr` property to `true` in your `vuetify` configuration object.

  ```js { resource="src/plugins/vuetify.js" }
  import { createVuetify } from 'vuetify'

  export default createVuetify({
    ssr: true,
  })
  ```

* **Is there still support for IE11 in Vuetify 3?** { #is-there-still-support-for-ie11-in-vuetify-3 }

  No. If you need to support IE11, use [Vuetify 2](https://v2.vuetifyjs.com/).

* **Is there support for Nuxt 3?** { #is-there-support-for-nuxt-3 }

  Yes, Vuetify 3 is compatible with Nuxt 3 but does not currently have a community Nuxt module.

* **What is Vuetify Labs?** { #what-is-vuetify-labs }

  Vuetify Labs is a collection of components that are still in development. They are not considered stable and may change at any time. They are not included in the default Vuetify installation and must be imported individually. See the [Labs](/labs/introduction/) page for more information.

* **Does Vuetify have Nightly Builds?** { #does-vuetify-have-nightly-builds }

  Yes, Vuetify has nightly builds. See the [Nightly Builds](/getting-started/installation/#nightly-builds) page for more information.

* **I found a bug, what should I do?** { #i-found-a-bug-what-should-i-do }

  Please create a new [issue](https://issues.vuetifyjs.com/) with our Issue Generator. Please make sure to check for existing issues before creating a new one.

* **Why did Vuetify 3 change `value` to `model-value`?**

  The `value` prop was changed in Vue 3 to support a new `v-model` syntax. See the official Vue docs for more information on [Component v-model](https://vuejs.org/guide/components/v-model.html).

<promoted type="theme" />


const filterReg = new RegExp(escapeRegExp(text), 'i');

const cursor = LivechatDepartment.find({

$or: [{ ancestors: { $in: [unitId] } }, { ancestors: { $exists: false } }],

keep keep keep replace keep keep keep keep

$or: [{ ancestors: { $in: [[unitId], null, []] } }, { ancestors: { $exists: false } }],

import {
	Field,
	TextInput,
	Button,
	Box,
	PaginatedMultiSelectFiltered,
	Select,
	Margins,
} from '@rocket.chat/fuselage';
import { useMutableCallback } from '@rocket.chat/fuselage-hooks';
import React, { useMemo, useState } from 'react';

import VerticalBar from '../../../../client/components/VerticalBar';
import { useRoute } from '../../../../client/contexts/RouterContext';
import { useMethod } from '../../../../client/contexts/ServerContext';
import { useToastMessageDispatch } from '../../../../client/contexts/ToastMessagesContext';
import { useTranslation } from '../../../../client/contexts/TranslationContext';
import { useRecordList } from '../../../../client/hooks/lists/useRecordList';
import { AsyncStatePhase } from '../../../../client/hooks/useAsyncState';
import { useForm } from '../../../../client/hooks/useForm';
import { useDepartmentsList } from '../../../../client/views/hooks/useDepartmentsList';
import { useMonitorsList } from '../../../../client/views/hooks/useMonitorsList';

function UnitEdit({ data, unitId, isNew, unitMonitors, unitDepartments, reload, ...props }) {
	const t = useTranslation();
	const unitsRoute = useRoute('omnichannel-units');
	const [monitorsFilter, setMonitorsFilter] = useState('');
	const [departmentsFilter, setDepartmentsFilter] = useState('');

	const { itemsList: monitorsList, loadMoreItems: loadMoreMonitors } = useMonitorsList(
		useMemo(() => ({ limit: 50, filter: monitorsFilter }), [monitorsFilter]),
	);

	const { phase: monitorsPhase, items: monitorsItems, itemCount: monitorsTotal } = useRecordList(
		monitorsList,
	);

	const { itemsList: departmentsList, loadMoreItems: loadMoreDepartments } = useDepartmentsList(
		useMemo(() => ({ limit: 50, filter: departmentsFilter }), [departmentsFilter]),
	);

	const {
		phase: departmentsPhase,
		items: departmentsItems,
		itemCount: departmentsTotal,
	} = useRecordList(departmentsList);

	const unit = data || {};

	const currUnitMonitors = useMemo(
		() =>
			unitMonitors && unitMonitors.monitors
				? unitMonitors.monitors.map(({ monitorId, username }) => ({
						value: monitorId,
						label: username,
				  }))
				: [],
		[unitMonitors],
	);
	const visibilityOpts = [
		['public', t('Public')],
		['private', t('Private')],
	];

	const currUnitDepartments = useMemo(
		() =>
			unitDepartments && unitDepartments.departments && unitId
				? unitDepartments.departments.map(({ _id, name }) => ({
						value: _id,
						label: name,
				  }))
				: [],
		[unitDepartments, unitId],
	);

	const { values, handlers, hasUnsavedChanges } = useForm({
		name: unit.name,
		visibility: unit.visibility,
		departments: currUnitDepartments,
		monitors: currUnitMonitors,
	});

	const { handleName, handleVisibility, handleDepartments, handleMonitors } = handlers;
	const { name, visibility, departments, monitors } = values;

	const nameError = useMemo(
		() => (!name || name.length === 0 ? t('The_field_is_required', t('name')) : undefined),
		[name, t],
	);
	const visibilityError = useMemo(
		() =>
			!visibility || visibility.length === 0
				? t('The_field_is_required', t('description'))
				: undefined,
		[visibility, t],
	);
	const departmentError = useMemo(
		() =>
			!departments || departments.length === 0
				? t('The_field_is_required', t('departments'))
				: undefined,
		[departments, t],
	);
	const unitMonitorsError = useMemo(
		() =>
			!monitors || monitors.length === 0 ? t('The_field_is_required', t('monitors')) : undefined,
		[monitors, t],
	);

	const saveUnit = useMethod('livechat:saveUnit');

	const dispatchToastMessage = useToastMessageDispatch();

	const handleReset = useMutableCallback(() => {
		reload();
	});

	const canSave = useMemo(
		() => !nameError && !visibilityError && !departmentError && !unitMonitorsError,
		[nameError, visibilityError, departmentError, unitMonitorsError],
	);

	const handleSave = useMutableCallback(async () => {
		const unitData = { name, visibility };
		const departmentsData = departments.map((department) => ({ departmentId: department.value }));
		const monitorsData = monitors.map((monitor) => ({
			monitorId: monitor.value,
			username: monitor.label,
		}));

		if (!canSave) {
			return dispatchToastMessage({ type: 'error', message: t('The_field_is_required') });
		}

		try {
			await saveUnit(unitId, unitData, monitorsData, departmentsData);
			dispatchToastMessage({ type: 'success', message: t('Saved') });
			reload();
			unitsRoute.push({});
		} catch (error) {
			dispatchToastMessage({ type: 'error', message: error });
		}
	});

	return (
		<VerticalBar.ScrollableContent is='form' {...props}>
			<Field>
				<Field.Label>{t('Name')}*</Field.Label>
				<Field.Row>
					<TextInput
						placeholder={t('Name')}
						flexGrow={1}
						value={name}
						onChange={handleName}
						error={hasUnsavedChanges && nameError}
					/>
				</Field.Row>
			</Field>
			<Field>
				<Field.Label>{t('Visibility')}*</Field.Label>
				<Field.Row>
					<Select
						options={visibilityOpts}
						value={visibility}
						error={hasUnsavedChanges && visibilityError}
						placeholder={t('Select_an_option')}
						onChange={handleVisibility}
						flexGrow={1}
					/>
				</Field.Row>
			</Field>
			<Field>
				<Field.Label>{t('Departments')}*</Field.Label>
				<Field.Row>
					<PaginatedMultiSelectFiltered
						filter={departmentsFilter}
						setFilter={setDepartmentsFilter}
						options={departmentsItems}
						value={departments}
						error={hasUnsavedChanges && departmentError}
						maxWidth='100%'
						placeholder={t('Select_an_option')}
						onChange={handleDepartments}
						flexGrow={1}
						endReached={
							departmentsPhase === AsyncStatePhase.LOADING
								? () => {}
								: (start) => loadMoreDepartments(start, Math.min(50, departmentsTotal))
						}
					/>
				</Field.Row>
			</Field>
			<Field>
				<Field.Label>{t('Monitors')}*</Field.Label>
				<Field.Row>
					<PaginatedMultiSelectFiltered
						filter={monitorsFilter}
						setFilter={setMonitorsFilter}
						options={monitorsItems}
						value={monitors}
						error={hasUnsavedChanges && unitMonitorsError}
						maxWidth='100%'
						placeholder={t('Select_an_option')}
						onChange={handleMonitors}
						flexGrow={1}
						endReached={
							monitorsPhase === AsyncStatePhase.LOADING
								? () => {}
								: (start) => loadMoreMonitors(start, Math.min(50, monitorsTotal))
						}
					/>
				</Field.Row>
			</Field>

			<Field.Row>
				<Box display='flex' flexDirection='row' justifyContent='space-between' w='full'>
					<Margins inlineEnd='x4'>
						{!isNew && (
							<Button flexGrow={1} type='reset' disabled={!hasUnsavedChanges} onClick={handleReset}>
								{t('Reset')}
							</Button>
						)}
						<Button
							primary
							mie='none'
							flexGrow={1}
							disabled={!hasUnsavedChanges || !canSave}
							onClick={handleSave}
						>
							{t('Save')}
						</Button>
					</Margins>
				</Box>
			</Field.Row>
		</VerticalBar.ScrollableContent>
	);
}

export default UnitEdit;


import { useDebouncedValue } from '@rocket.chat/fuselage-hooks';
import React, { useCallback, useMemo, useState } from 'react';

import { useUserId, useUserSubscription } from '../../../../contexts/UserContext';
import { useRecordList } from '../../../../hooks/lists/useRecordList';
import { AsyncStatePhase } from '../../../../hooks/useAsyncState';
import { useUserRoom } from '../../hooks/useUserRoom';
import { useTabBarClose } from '../../providers/ToolboxProvider';
import { useDiscussionsList } from './useDiscussionsList';

const subscriptionFields = { tunread: 1, tunreadUser: 1, tunreadGroup: 1 };
const roomFields = { t: 1, name: 1 };

export function withData(Component) {
	const WrappedComponent = ({ rid, ...props }) => {
		const room = useUserRoom(rid, roomFields);
		const subscription = useUserSubscription(rid, subscriptionFields);
		const userId = useUserId();
		const onClose = useTabBarClose();

		const [text, setText] = useState('');
		const debouncedText = useDebouncedValue(text, 400);

		const options = useMemo(
			() => ({
				rid,
				text: debouncedText,
			}),
			[rid, debouncedText],
		);

		const { discussionsList, initialItemCount, loadMoreItems } = useDiscussionsList(
			options,
			userId,
		);
		const { phase, error, items: discussions, itemCount: totalItemCount } = useRecordList(
			discussionsList,
		);

		const handleTextChange = useCallback((e) => {
			setText(e.currentTarget.value);
		}, []);

		return (
			<Component
				{...props}
				onClose={onClose}
				unread={subscription?.tunread}
				unreadUser={subscription?.tunreadUser}
				unreadGroup={subscription?.tunreadGroup}
				userId={userId}
				error={error}
				discussions={discussions}
				total={totalItemCount}
				initial={initialItemCount}
				loading={phase === AsyncStatePhase.LOADING}
				loadMoreItems={loadMoreItems}
				room={room}
				text={text}
				setText={handleTextChange}
			/>
		);
	};

	WrappedComponent.displayName = `withData(${
		Component.displayName ?? Component.name ?? 'Component'
	})`;

	return WrappedComponent;
}


import React, { FC } from 'react';

import { useTranslation } from '../../../contexts/TranslationContext';
import { useBlockRendered } from '../hooks/useBlockRendered';
import Content from './Content';
import Reply from './Reply';

type BroadcastOptions = {
	username: string;
	mid: string;
	replyBroadcast: () => void;
};

const BroadcastMetric: FC<BroadcastOptions> = ({ username, mid, replyBroadcast }) => {
	const t = useTranslation();
	const { className, ref } = useBlockRendered();

	return (
		<Content>
			<div className={className} ref={ref as any} />
			<Reply data-username={username} data-mid={mid} onClick={replyBroadcast}>
				{t('Reply')}
			</Reply>
		</Content>
	);
};

export default BroadcastMetric;


import { escapeRegExp } from '@rocket.chat/string-helpers';

import {
	LivechatDepartment,
} from '../../../../../app/models/server/raw';

export const findAllDepartmentsAvailable = async (unitId, offset, count, text) => {
	const filterReg = new RegExp(escapeRegExp(text), 'i');

	const cursor = LivechatDepartment.find({
		$or: [{ ancestors: { $in: [unitId] } }, { ancestors: { $exists: false } }],
		...text && { name: filterReg },

	}, { limit: count, offset });

	const departments = await cursor.toArray();
	const total = await cursor.count();
	const departmentsFiltered = departments.filter((department) => !department.ancestors?.length);

	return { departments: departmentsFiltered, total };
};

export const findAllDepartmentsByUnit = async (unitId, offset, count) => {
	const cursor = LivechatDepartment.find({
		ancestors: { $in: [unitId] },
	}, { limit: count, offset });

	const total = await cursor.count();
	const departments = await cursor.toArray();

	return { departments, total };
};


import { ImporterInfo } from '../../importer/lib/ImporterInfo';

export class PendingFileImporterInfo extends ImporterInfo {
	constructor() {
		super('pending-files', 'Pending Files', '');
	}
}


export type UnsubscribeMethod = (_id: string, createdAt: string) => void;


import { Template } from 'meteor/templating';
import { ReactiveVar } from 'meteor/reactive-var';
import { FlowRouter } from 'meteor/kadira:flow-router';

import { hasAllPermission } from '../../../../../../app/authorization/client';
import { messageContext } from '../../../../../../app/ui-utils/client/lib/messageContext';
import { call } from '../../utils.js';

import './audit.html';

const loadMessages = async function({ rid, users, startDate, endDate = new Date(), msg, type, visitor, agent }) {
	this.messages = this.messages || new ReactiveVar([]);
	this.loading = this.loading || new ReactiveVar(true);
	try {
		this.loading.set(true);
		const messages = type === 'l' ? await call('auditGetOmnichannelMessages', { rid, users, startDate, endDate, msg, type, visitor, agent }) : await call('auditGetMessages', { rid, users, startDate, endDate, msg, type, visitor, agent });
		this.messagesContext.set({
			...messageContext({ rid }),
			messages,
		});
	} catch (e) {
		this.messagesContext.set({});
	} finally {
		this.loading.set(false);
	}
};

Template.audit.helpers({
	isLoading() {
		return Template.instance().loading.get();
	},
	messageContext() {
		return Template.instance().messagesContext.get();
	},
	hasResults() {
		return Template.instance().hasResults.get();
	},
});

Template.audit.onCreated(async function() {
	this.messagesContext = new ReactiveVar({});
	this.loading = new ReactiveVar(false);
	this.hasResults = new ReactiveVar(false);

	if (!hasAllPermission('can-audit')) {
		return FlowRouter.go('/home');
	}

	this.autorun(() => {
		const messagesContext = this.messagesContext.get();

		this.hasResults.set(messagesContext && messagesContext.messages && messagesContext.messages.length > 0);
	});

	this.loadMessages = loadMessages.bind(this);

	const {
		visitor,
		agent,
		users,
		rid,
	} = this.data;
	if (rid || users.length || agent || visitor) {
		await this.loadMessages(this.data);
	}
});


return { departments: departmentsFiltered, total };

export const findAllDepartmentsByUnit = async (unitId, offset, count) => {


# ECDH Proxy (alpha)

This services aims to be in front of the HTTP and Webscoket services and provide a second layer of encryption based on [ECDH](https://en.wikipedia.org/wiki/Elliptic-curve_Diffie%E2%80%93Hellman) algorithm.

## Configuration

All the configuration for this service is done via environment variables:

- **STATIC_SEED**: The static seed to compose the encryption. **Required**
- **PORT**: The port this service will expose the HTTP server. Default: `4000`
- **PROXY_HOST**: The host this service will proxy the requests to after decoding. Default `localhost`
- **PROXY_PORT**: The port this service will proxy the requests to after decoding. Default `3000`


import { Table, Icon, Button } from '@rocket.chat/fuselage';
import { useMutableCallback } from '@rocket.chat/fuselage-hooks';
import React from 'react';

import DeleteWarningModal from '../../../../client/components/DeleteWarningModal';
import { useSetModal } from '../../../../client/contexts/ModalContext';
import { useRoute } from '../../../../client/contexts/RouterContext';
import { useMethod } from '../../../../client/contexts/ServerContext';
import { useToastMessageDispatch } from '../../../../client/contexts/ToastMessagesContext';
import { useTranslation } from '../../../../client/contexts/TranslationContext';

function RemovePriorityButton({ _id, reload }) {
	const removePriority = useMethod('livechat:removePriority');
	const setModal = useSetModal();
	const dispatchToastMessage = useToastMessageDispatch();
	const t = useTranslation();
	const prioritiesRoute = useRoute('omnichannel-priorities');

	const handleRemoveClick = useMutableCallback(async () => {
		try {
			await removePriority(_id);
		} catch (error) {
			console.log(error);
		}
		reload();
	});

	const handleDelete = useMutableCallback((e) => {
		e.stopPropagation();
		const onDeleteAgent = async () => {
			try {
				await handleRemoveClick();
				dispatchToastMessage({ type: 'success', message: t('Priority_removed') });
				prioritiesRoute.push({});
			} catch (error) {
				dispatchToastMessage({ type: 'error', message: error });
			}
			setModal();
		};

		setModal(<DeleteWarningModal onDelete={onDeleteAgent} onCancel={() => setModal()} />);
	});

	return (
		<Table.Cell fontScale='p1' color='hint' withTruncatedText>
			<Button small ghost title={t('Remove')} onClick={handleDelete}>
				<Icon name='trash' size='x16' />
			</Button>
		</Table.Cell>
	);
}

export default RemovePriorityButton;


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

type FunctionPropertyNames<T> = {
	[K in keyof T]: T[K] extends Function ? K : never;
}[keyof T];

type Prom<T> = {
	[K in FunctionPropertyNames<T>]: ReturnType<T[K]> extends Promise<any> ? T[K] : (...params: Parameters<T[K]>) => Promise<ReturnType<T[K]>>;
}

type PromOrError<T> = {
	[K in FunctionPropertyNames<T>]: ReturnType<T[K]> extends Promise<any> ? (...params: Parameters<T[K]>) => ReturnType<T[K]> | Promise<Error> : (...params: Parameters<T[K]>) => Promise<ReturnType<T[K]> | Error>;
}

function handler<T extends object>(namespace: string, waitService: boolean): ProxyHandler<T> {
	return {
		get: (_target: T, prop: string): any => (...params: any): Promise<any> => api[waitService ? 'waitAndCall' : 'call'](`${ namespace }.${ prop }`, params),
	};
}

export function proxifyWithWait<T>(namespace: string): Prom<T> {
	return new Proxy({}, handler(namespace, true)) as unknown as Prom<T>;
}

export function proxify<T>(namespace: string): PromOrError<T> {
	return new Proxy({}, handler(namespace, false)) as unknown as Prom<T>;
}


const unitsRoute = useRoute('omnichannel-units');

const [monitorsFilter, setMonitorsFilter] = useState('');

const [departmentsFilter, setDepartmentsFilter] = useState('');

const { itemsList: monitorsList, loadMoreItems: loadMoreMonitors } = useMonitorsList(

useMemo(() => ({ limit: 50, filter: monitorsFilter }), [monitorsFilter]),

const { phase: monitorsPhase, items: monitorsItems, itemCount: monitorsTotal } = useRecordList(

keep keep keep keep keep replace keep keep keep keep

useMemo(() => ({ filter: monitorsFilter }), [monitorsFilter]),


import { Meteor } from 'meteor/meteor';
import _ from 'underscore';
import s from 'underscore.string';
import limax from 'limax';

import { hasPermission } from '../../../authorization';
import { EmojiCustom } from '../../../models';
import { RocketChatFileEmojiCustomInstance } from '../startup/emoji-custom';
import { api } from '../../../../server/sdk/api';

Meteor.methods({
	insertOrUpdateEmoji(emojiData) {
		if (!hasPermission(this.userId, 'manage-emoji')) {
			throw new Meteor.Error('not_authorized');
		}

		if (!s.trim(emojiData.name)) {
			throw new Meteor.Error('error-the-field-is-required', 'The field Name is required', { method: 'insertOrUpdateEmoji', field: 'Name' });
		}

		emojiData.name = limax(emojiData.name, { replacement: '_' });
		emojiData.aliases = limax(emojiData.aliases, { replacement: '_' });

		// allow all characters except colon, whitespace, comma, >, <, &, ", ', /, \, (, )
		// more practical than allowing specific sets of characters; also allows foreign languages
		const nameValidation = /[\s,:><&"'\/\\\(\)]/;
		const aliasValidation = /[:><&\|"'\/\\\(\)]/;

		// silently strip colon; this allows for uploading :emojiname: as emojiname
		emojiData.name = emojiData.name.replace(/:/g, '');
		emojiData.aliases = emojiData.aliases.replace(/:/g, '');

		if (nameValidation.test(emojiData.name)) {
			throw new Meteor.Error('error-input-is-not-a-valid-field', `${ emojiData.name } is not a valid name`, { method: 'insertOrUpdateEmoji', input: emojiData.name, field: 'Name' });
		}

		if (emojiData.aliases) {
			if (aliasValidation.test(emojiData.aliases)) {
				throw new Meteor.Error('error-input-is-not-a-valid-field', `${ emojiData.aliases } is not a valid alias set`, { method: 'insertOrUpdateEmoji', input: emojiData.aliases, field: 'Alias_Set' });
			}
			emojiData.aliases = emojiData.aliases.split(/[\s,]/);
			emojiData.aliases = emojiData.aliases.filter(Boolean);
			emojiData.aliases = _.without(emojiData.aliases, emojiData.name);
		} else {
			emojiData.aliases = [];
		}

		emojiData.extension = emojiData.extension === 'svg+xml' ? 'png' : emojiData.extension;

		let matchingResults = [];

		if (emojiData._id) {
			matchingResults = EmojiCustom.findByNameOrAliasExceptID(emojiData.name, emojiData._id).fetch();
			for (const alias of emojiData.aliases) {
				matchingResults = matchingResults.concat(EmojiCustom.findByNameOrAliasExceptID(alias, emojiData._id).fetch());
			}
		} else {
			matchingResults = EmojiCustom.findByNameOrAlias(emojiData.name).fetch();
			for (const alias of emojiData.aliases) {
				matchingResults = matchingResults.concat(EmojiCustom.findByNameOrAlias(alias).fetch());
			}
		}

		if (matchingResults.length > 0) {
			throw new Meteor.Error('Custom_Emoji_Error_Name_Or_Alias_Already_In_Use', 'The custom emoji or one of its aliases is already in use', { method: 'insertOrUpdateEmoji' });
		}

		if (typeof emojiData.extension === 'undefined') {
			throw new Meteor.Error('error-the-field-is-required', 'The custom emoji file is required', { method: 'insertOrUpdateEmoji' });
		}

		if (!emojiData._id) {
			// insert emoji
			const createEmoji = {
				name: emojiData.name,
				aliases: emojiData.aliases,
				extension: emojiData.extension,
			};

			const _id = EmojiCustom.create(createEmoji);

			api.broadcast('emoji.updateCustom', createEmoji);

			return _id;
		}
		// update emoji
		if (emojiData.newFile) {
			RocketChatFileEmojiCustomInstance.deleteFile(encodeURIComponent(`${ emojiData.name }.${ emojiData.extension }`));
			RocketChatFileEmojiCustomInstance.deleteFile(encodeURIComponent(`${ emojiData.name }.${ emojiData.previousExtension }`));
			RocketChatFileEmojiCustomInstance.deleteFile(encodeURIComponent(`${ emojiData.previousName }.${ emojiData.extension }`));
			RocketChatFileEmojiCustomInstance.deleteFile(encodeURIComponent(`${ emojiData.previousName }.${ emojiData.previousExtension }`));

			EmojiCustom.setExtension(emojiData._id, emojiData.extension);
		} else if (emojiData.name !== emojiData.previousName) {
			const rs = RocketChatFileEmojiCustomInstance.getFileWithReadStream(encodeURIComponent(`${ emojiData.previousName }.${ emojiData.previousExtension }`));
			if (rs !== null) {
				RocketChatFileEmojiCustomInstance.deleteFile(encodeURIComponent(`${ emojiData.name }.${ emojiData.extension }`));
				const ws = RocketChatFileEmojiCustomInstance.createWriteStream(encodeURIComponent(`${ emojiData.name }.${ emojiData.previousExtension }`), rs.contentType);
				ws.on('end', Meteor.bindEnvironment(() =>
					RocketChatFileEmojiCustomInstance.deleteFile(encodeURIComponent(`${ emojiData.previousName }.${ emojiData.previousExtension }`)),
				));
				rs.readStream.pipe(ws);
			}
		}

		if (emojiData.name !== emojiData.previousName) {
			EmojiCustom.setName(emojiData._id, emojiData.name);
		}

		if (emojiData.aliases) {
			EmojiCustom.setAliases(emojiData._id, emojiData.aliases);
		} else {
			EmojiCustom.setAliases(emojiData._id, []);
		}

		api.broadcast('emoji.updateCustom', emojiData);

		return true;
	},
});