import type { IUIKitContextualBarInteraction } from '@rocket.chat/apps-engine/definition/uikit';
import { useCurrentRoute } from '@rocket.chat/ui-contexts';
import { useEffect, useState } from 'react';

import { getUserInteractionPayloadByViewId } from '../../../../app/ui-message/client/ActionManager';
import { useRoom } from '../contexts/RoomContext';

type AppsContextualBarData = {
	viewId: string;
	roomId: string;
	payload: IUIKitContextualBarInteraction;
	appId: string;
};

export const useAppsContextualBar = (): AppsContextualBarData | undefined => {
	const [, params] = useCurrentRoute();
	const [payload, setPayload] = useState<IUIKitContextualBarInteraction>();
	const [appId, setAppId] = useState<string>();

	const { _id: roomId } = useRoom();

	const viewId = params?.context;

	useEffect(() => {
		if (viewId) {
			setPayload(getUserInteractionPayloadByViewId(viewId) as IUIKitContextualBarInteraction);
		}

		if (payload?.appId) {
			setAppId(payload.appId);
		}

		return (): void => {
			setPayload(undefined);
			setAppId(undefined);
		};
	}, [viewId, payload?.appId]);

	if (viewId && payload && appId) {
		return {
			viewId,
			roomId,
			payload,
			appId,
		};
	}

	return undefined;
};


import { useForm } from '../../../../../hooks/useForm';

import RoomsAvailableForTeamsAutoComplete from './RoomsAvailableForTeamsAutoComplete';

//! moment.js locale configuration
//! locale : Afrikaans [af]
//! author : Werner Mollentze : https://github.com/wernerm

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

    var af = moment.defineLocale('af', {
        months : 'Januarie_Februarie_Maart_April_Mei_Junie_Julie_Augustus_September_Oktober_November_Desember'.split('_'),
        monthsShort : 'Jan_Feb_Mrt_Apr_Mei_Jun_Jul_Aug_Sep_Okt_Nov_Des'.split('_'),
        weekdays : 'Sondag_Maandag_Dinsdag_Woensdag_Donderdag_Vrydag_Saterdag'.split('_'),
        weekdaysShort : 'Son_Maa_Din_Woe_Don_Vry_Sat'.split('_'),
        weekdaysMin : 'So_Ma_Di_Wo_Do_Vr_Sa'.split('_'),
        meridiemParse: /vm|nm/i,
        isPM : function (input) {
            return /^nm$/i.test(input);
        },
        meridiem : function (hours, minutes, isLower) {
            if (hours < 12) {
                return isLower ? 'vm' : 'VM';
            } else {
                return isLower ? 'nm' : 'NM';
            }
        },
        longDateFormat : {
            LT : 'HH:mm',
            LTS : 'HH:mm:ss',
            L : 'DD/MM/YYYY',
            LL : 'D MMMM YYYY',
            LLL : 'D MMMM YYYY HH:mm',
            LLLL : 'dddd, D MMMM YYYY HH:mm'
        },
        calendar : {
            sameDay : '[Vandag om] LT',
            nextDay : '[Môre om] LT',
            nextWeek : 'dddd [om] LT',
            lastDay : '[Gister om] LT',
            lastWeek : '[Laas] dddd [om] LT',
            sameElse : 'L'
        },
        relativeTime : {
            future : 'oor %s',
            past : '%s gelede',
            s : '\'n paar sekondes',
            m : '\'n minuut',
            mm : '%d minute',
            h : '\'n uur',
            hh : '%d ure',
            d : '\'n dag',
            dd : '%d dae',
            M : '\'n maand',
            MM : '%d maande',
            y : '\'n jaar',
            yy : '%d jaar'
        },
        ordinalParse: /\d{1,2}(ste|de)/,
        ordinal : function (number) {
            return number + ((number === 1 || number === 8 || number >= 20) ? 'ste' : 'de'); // Thanks to Joris Röling : https://github.com/jjupiter
        },
        week : {
            dow : 1, // Maandag is die eerste dag van die week.
            doy : 4  // Die week wat die 4de Januarie bevat is die eerste week van die jaar.
        }
    });

    return af;

}));


import store from '../store';

export default class Commands {
	connected() {
		store.setState({ connecting: false });
	}
}


keep keep keep keep keep replace replace keep keep

import type { IRoom, Serialized } from '@rocket.chat/core-typings';
import { Box, Button, Field, Modal } from '@rocket.chat/fuselage';
import { useToastMessageDispatch, useEndpoint, useTranslation } from '@rocket.chat/ui-contexts';
import type { ComponentProps } from 'react';
import React, { memo, useCallback } from 'react';

import { useForm } from '../../../../../hooks/useForm';
import RoomsAvailableForTeamsAutoComplete from './RoomsAvailableForTeamsAutoComplete';

type AddExistingModalProps = {
	teamId: string;
	onClose: () => void;
};

const AddExistingModal = ({ onClose, teamId }: AddExistingModalProps) => {
	const t = useTranslation();

	const addRoomEndpoint = useEndpoint('POST', '/v1/teams.addRooms');
	const dispatchToastMessage = useToastMessageDispatch();

	const { values, handlers, hasUnsavedChanges } = useForm({
		rooms: [] as Serialized<IRoom>[],
	});

	const { rooms } = values as { rooms: string[] };
	const { handleRooms } = handlers;

	const handleAddChannels = useCallback(
		async (e) => {
			e.preventDefault();
			try {
				await addRoomEndpoint({
					rooms,
					teamId,
				});

				dispatchToastMessage({ type: 'success', message: t('Channels_added') });
			} catch (error) {
				dispatchToastMessage({ type: 'error', message: error });
			} finally {
				onClose();
			}
		},
		[addRoomEndpoint, rooms, teamId, onClose, dispatchToastMessage, t],
	);

	return (
		<Modal wrapperFunction={(props: ComponentProps<typeof Box>) => <Box is='form' onSubmit={handleAddChannels} {...props} />}>
			<Modal.Header>
				<Modal.Title>{t('Team_Add_existing_channels')}</Modal.Title>
				<Modal.Close onClick={onClose} />
			</Modal.Header>
			<Modal.Content>
				<Field mbe='x24'>
					<Field.Label>{t('Channels')}</Field.Label>
					<RoomsAvailableForTeamsAutoComplete value={rooms} onChange={handleRooms} />
				</Field>
			</Modal.Content>
			<Modal.Footer>
				<Modal.FooterControllers>
					<Button onClick={onClose}>{t('Cancel')}</Button>
					<Button disabled={!hasUnsavedChanges} type='submit' primary>
						{t('Add')}
					</Button>
				</Modal.FooterControllers>
			</Modal.Footer>
		</Modal>
	);
};

export default memo(AddExistingModal);


type StartupCallback = () => Promise<void>;

const callbackList: StartupCallback[] = [];
let hasStarted = false;

export const onStartup = (cb: StartupCallback): void => {
	if (hasStarted) {
		return Promise.await(cb());
	}

	callbackList.push(cb);
};

const runCallbacks = async (): Promise<void> => {
	for await (const cb of callbackList) {
		await cb();
	}

	callbackList.splice(0, callbackList.length);
};

Meteor.startup(() => {
	hasStarted = true;
	Promise.await(runCallbacks());
});


import React from 'react';
import { useTranslation } from '@rocket.chat/ui-contexts';
import { OptionAvatar, OptionColumn, OptionContent } from '@rocket.chat/fuselage';

import UserAvatar from '../../../../../../client/components/avatar/UserAvatar';
import ReactiveUserStatus from '../../../../../../client/components/UserStatus/ReactiveUserStatus';

export type ComposerBoxPopupUserProps = {
	_id: string;
	system?: boolean;
	outside?: boolean;
	suggestion?: boolean;
	username: string;
	name?: string;
	nickname?: string;
	status?: string;
	sort?: number;
};

const ComposerBoxPopupUser = ({ _id, system, username, name, nickname, outside, suggestion }: ComposerBoxPopupUserProps) => {
	const t = useTranslation();
	return (
		<>
			{!system && (
				<>
					<OptionAvatar>
						<UserAvatar size='x28' username={username} />
					</OptionAvatar>
					<OptionColumn>
						<ReactiveUserStatus uid={_id} />
					</OptionColumn>
					<OptionContent>
						{name ?? username} {name && name !== username && username}
						{nickname && ({nickname})}
					</OptionContent>
				</>
			)}

			{system && (
				<OptionContent>
					{username} {name}
				</OptionContent>
			)}

			{outside && <OptionColumn>{t('Not_in_channel')}</OptionColumn>}
			{suggestion && <OptionColumn>{t('Suggestion_from_recent_messages')}</OptionColumn>}
		</>
	);
};

export default ComposerBoxPopupUser;


module.exports = {
	presets: [
		[
			'@babel/preset-env',
			{
				shippedProposals: true,
				useBuiltIns: 'usage',
				corejs: '3',
				modules: 'commonjs',
				include: [
					'@babel/plugin-proposal-class-properties',
					'@babel/plugin-proposal-optional-chaining',
					'@babel/plugin-proposal-nullish-coalescing-operator',
				],
			},
		],
		'@babel/preset-react',
		'@babel/preset-flow',
		['@babel/preset-typescript', { allowDeclareFields: true }],
	],
};


const { values, handlers, hasUnsavedChanges } = useForm({

const addRoomEndpoint = useEndpoint('POST', '/v1/teams.addRooms');


import '../../../apps/meteor/app/theme/client/main.css';
import 'highlight.js/styles/github.css';

export const parameters = {
  actions: { argTypesRegex: "^on[A-Z].*" },
  controls: {
    matchers: {
      color: /(background|color)$/i,
      date: /Date$/,
    },
  },
}

export const decorators = [
	(Story) => (
		<div className="rc-old">
			<style>{`
				body {
					background-color: white;
				}
			`}</style>
			<Story />
		</div>
	)
];


import Ajv from 'ajv';

const ajv = new Ajv({
	coerceTypes: true,
});

export type FederationRemoveServerProps = {
	serverName: string;
};

const FederationRemoveServerPropsSchema = {
	type: 'object',
	properties: {
		serverName: {
			type: 'string',
		},
	},
	additionalProperties: false,
	required: ['serverName'],
};

export const isFederationRemoveServerProps = ajv.compile<FederationRemoveServerProps>(FederationRemoveServerPropsSchema);


import React from 'react';

import NotFoundPage from './NotFoundPage';

export default {
	title: 'Not Found/NotFoundPage',
	component: NotFoundPage,
};

export const Default = () => <NotFoundPage />;


const { rooms } = values as { rooms: string[] };

} = useForm({ defaultValues: { rooms: [] } });


import { Sidebar, IconButton } from '@rocket.chat/fuselage';
import { useMutableCallback, usePrefersReducedMotion } from '@rocket.chat/fuselage-hooks';
import type { VFC } from 'react';
import React, { memo, useState } from 'react';

type MediumProps = {
	title: React.ReactNode;
	titleIcon?: React.ReactNode;
	avatar: React.ReactNode | boolean;
	icon?: string;
	actions?: React.ReactNode;
	href?: string;
	unread?: boolean;
	menu?: () => React.ReactNode;
	badges?: React.ReactNode;
	selected?: boolean;
	menuOptions?: any;
};

const Medium: VFC<MediumProps> = ({ icon, title = '', avatar, actions, href, badges, unread, menu, ...props }) => {
	const [menuVisibility, setMenuVisibility] = useState(!!window.DISABLE_ANIMATION);

	const isReduceMotionEnabled = usePrefersReducedMotion();

	const handleMenu = useMutableCallback((e) => {
		setMenuVisibility(e.target.offsetWidth > 0 && Boolean(menu));
	});
	const handleMenuEvent = {
		[isReduceMotionEnabled ? 'onMouseEnter' : 'onTransitionEnd']: handleMenu,
	};

	return (
		<Sidebar.Item {...props} {...({ href } as any)} clickable={!!href}>
			{avatar && <Sidebar.Item.Avatar>{avatar}</Sidebar.Item.Avatar>}
			<Sidebar.Item.Content>
				<Sidebar.Item.Wrapper>
					{icon}
					<Sidebar.Item.Title data-qa='sidebar-item-title' className={(unread && 'rcx-sidebar-item--highlighted') as string}>
						{title}
					</Sidebar.Item.Title>
				</Sidebar.Item.Wrapper>
				{badges && <Sidebar.Item.Badge>{badges}</Sidebar.Item.Badge>}
				{menu && (
					<Sidebar.Item.Menu {...handleMenuEvent}>
						{menuVisibility ? menu() : <IconButton mini rcx-sidebar-item__menu icon='kebab' />}
					</Sidebar.Item.Menu>
				)}
			</Sidebar.Item.Content>
			{actions && <Sidebar.Item.Container>{<Sidebar.Item.Actions>{actions}</Sidebar.Item.Actions>}</Sidebar.Item.Container>}
		</Sidebar.Item>
	);
};

export default memo(Medium);


import moment from 'moment';
import type { ParsedMail } from 'mailparser';
import type { IMessage } from '@rocket.chat/core-typings';
import { Messages, Subscriptions, Users, Rooms } from '@rocket.chat/models';

import { settings } from '../../../settings/server';
import { metrics } from '../../../metrics/server';
import { canAccessRoomAsync } from '../../../authorization/server';
import { hasPermissionAsync } from '../../../authorization/server/functions/hasPermission';
import { sendMessage } from '../functions/sendMessage';

const isParsedEmail = (email: ParsedMail): email is Required<ParsedMail> => 'date' in email && 'html' in email;

export const processDirectEmail = async function (email: ParsedMail): Promise<void> {
	if (!isParsedEmail(email)) {
		return;
	}

	const to = Array.isArray(email.to) ? email.to.find((email) => email.text) : email.to;
	const mid = to?.text.split('@')[0].split('+')[1];

	if (!mid) {
		return;
	}

	const ts = new Date(email.date);

	const tsDiff = Math.abs(moment(ts).diff(new Date()));

	let msg = email.text.split('\n\n').join('\n');

	if (msg && msg.length > (settings.get('Message_MaxAllowedSize') as number)) {
		return;
	}
	const emailAdress = email.from.value[0].address;
	if (!emailAdress) {
		return;
	}

	const user = await Users.findOneByEmailAddress(emailAdress, {
		projection: {
			username: 1,
			name: 1,
		},
	});

	if (!user?.username) {
		// user not found
		return;
	}

	const prevMessage = await Messages.findOneById(mid, {
		rid: 1,
		u: 1,
	});

	if (!prevMessage) {
		// message doesn't exist anymore
		return;
	}

	const roomInfo = await Rooms.findOneById(prevMessage.rid);

	if (!roomInfo) {
		// room doesn't exist anymore
		return;
	}

	const room = await canAccessRoomAsync(roomInfo, user);
	if (!room) {
		return;
	}

	// check mention
	if (msg.indexOf(`@${prevMessage.u.username}`) === -1 && roomInfo.t !== 'd') {
		msg = `@${prevMessage.u.username} ${msg}`;
	}

	// reply message link WE HA THREADS KNOW DONT NEED TO QUOTE THE previous message
	// let prevMessageLink = `[ ](${ Meteor.absoluteUrl().replace(/\/$/, '') }`;
	// if (roomInfo.t === 'c') {
	// 	prevMessageLink += `/channel/${ roomInfo.name }?msg=${ mid }) `;
	// } else if (roomInfo.t === 'd') {
	// 	prevMessageLink += `/direct/${ prevMessage.u.username }?msg=${ mid }) `;
	// } else if (roomInfo.t === 'p') {
	// 	prevMessageLink += `/group/${ roomInfo.name }?msg=${ mid }) `;
	// }
	// // add reply message link
	// msg = prevMessageLink + msg;

	const subscription = await Subscriptions.findOneByRoomIdAndUserId(prevMessage.rid, user._id);
	if (subscription && (subscription.blocked || subscription.blocker)) {
		// room is blocked
		return;
	}

	if ((roomInfo.muted || []).includes(user.username || '')) {
		// user is muted
		return;
	}

	// room is readonly
	if (roomInfo.ro === true) {
		if (!(await hasPermissionAsync(user._id, 'post-readonly', roomInfo._id))) {
			// Check if the user was manually unmuted
			if (!(roomInfo.unmuted || []).includes(user.username || '')) {
				return;
			}
		}
	}

	metrics.messagesSent.inc(); // TODO This line needs to be moved to it's proper place. See the comments on: https://github.com/RocketChat/Rocket.Chat/pull/5736

	const message: Pick<IMessage, 'ts' | 'msg' | 'groupable' | 'rid' | 'sentByEmail' | 'tmid'> = {
		ts: tsDiff < 10000 ? ts : new Date(),
		msg,
		sentByEmail: true,
		groupable: false,
		tmid: mid,
		rid: prevMessage.rid,
	};

	return sendMessage(user, message, roomInfo);
};


import { expect } from 'chai';
import { describe, it } from 'mocha';

import { callbacks, Callbacks } from './callbacks';

describe('callbacks legacy', () => {
	it("if the callback doesn't return any value should return the original", async () => {
		callbacks.add('test', () => undefined, callbacks.priority.LOW, '1');

		const result = await callbacks.run('test', true);

		expect(result).to.be.true;

		callbacks.remove('test', '1');
	});

	it('should return the value returned by the callback', async () => {
		callbacks.add('test', () => false, callbacks.priority.LOW, '1');

		const result = await callbacks.run('test', true);

		expect(result).to.be.false;

		callbacks.remove('test', '1');
	});

	it('should accumulate the values returned by the callbacks', async () => {
		callbacks.add('test', (old: number) => old * 5);

		callbacks.add('test', (old: number) => old * 2);

		expect(await callbacks.run('test', 3)).to.be.equal(30);

		expect(await callbacks.run('test', 2)).to.be.equal(20);
	});
});

describe('callbacks', () => {
	it("if the callback doesn't return any value should return the original", async () => {
		const test = Callbacks.create<boolean, boolean>('test');

		test.add(() => undefined, callbacks.priority.LOW, '1');

		const result = await test.run(true);

		expect(result).to.be.true;
	});

	it('should return the value returned by the callback', async () => {
		const test = Callbacks.create<boolean, boolean>('test');

		test.add(() => false, callbacks.priority.LOW, '1');

		const result = await test.run(true);

		expect(result).to.be.false;
	});

	it('should accumulate the values returned by the callbacks', async () => {
		const test = Callbacks.create<number, number>('test');

		test.add((old) => old * 5);

		test.add((old) => old * 2);

		expect(await test.run(3)).to.be.equal(30);

		expect(await test.run(2)).to.be.equal(20);
	});
});


keep keep replace replace keep keep keep keep keep

import { AutoComplete, Box, Icon, Option, Options, Chip } from '@rocket.chat/fuselage';
import { useDebouncedValue } from '@rocket.chat/fuselage-hooks';
import { useEndpoint } from '@rocket.chat/ui-contexts';
import { useQuery } from '@tanstack/react-query';
import type { ComponentProps } from 'react';
import React, { memo, useMemo, useState } from 'react';

import RoomAvatar from '../../../../../components/avatar/RoomAvatar';
import { roomCoordinator } from '../../../../../lib/rooms/roomCoordinator';

type RoomsAvailableForTeamsAutoCompleteProps = Omit<ComponentProps<typeof AutoComplete>, 'filter'>;

const RoomsAvailableForTeamsAutoComplete = ({ value, onChange, ...props }: RoomsAvailableForTeamsAutoCompleteProps) => {
	const [filter, setFilter] = useState('');
	const debouncedFilter = useDebouncedValue(filter, 1000);

	const roomsAvailableForTeamsAutoCompleteEndpoint = useEndpoint('GET', '/v1/rooms.autocomplete.availableForTeams');

	const { data } = useQuery(
		['roomsAvailableForTeamsAutoComplete', debouncedFilter],
		async () => roomsAvailableForTeamsAutoCompleteEndpoint({ name: debouncedFilter }),
		{ keepPreviousData: true },
	);

	const options = useMemo(() => {
		if (!data) {
			return [];
		}

		return data.items.map((room) => ({
			value: room._id,
			label: {
				name: roomCoordinator.getRoomName(room.t, { _id: room._id, name: room.name, fname: room.fname, prid: room.prid }),
				type: room.t,
				avatarETag: room.avatarETag,
			},
		}));
	}, [data]);

	return (
		<AutoComplete
			{...props}
			multiple
			value={value}
			onChange={onChange}
			filter={filter}
			setFilter={setFilter}
			renderSelected={({ selected: { value, label }, onRemove }) => (
				<Chip key={value} height='x20' value={value} onClick={onRemove} mie='x4'>
					<Icon name={label.type === 'c' ? 'hash' : 'hashtag-lock'} size='x12' />
					<Box is='span' margin='none' mis='x4'>
						{label.name}
					</Box>
				</Chip>
			)}
			renderItem={({ value, label, ...props }) => (
				<Option
					key={value}
					{...props}
					label={label.name}
					avatar={<RoomAvatar room={{ _id: value, type: label.type, avatarETag: label.avatarETag }} size={Options.AvatarSize} />}
				/>
			)}
			options={options}
		/>
	);
};

export default memo(RoomsAvailableForTeamsAutoComplete);


import { Settings } from '@rocket.chat/models';
import { NPS, Banner } from '@rocket.chat/core-services';
import { serverFetch as fetch } from '@rocket.chat/server-fetch';

import { buildWorkspaceRegistrationData } from './buildRegistrationData';
import { retrieveRegistrationStatus } from './retrieveRegistrationStatus';
import { getWorkspaceAccessToken } from './getWorkspaceAccessToken';
import { getWorkspaceLicense } from './getWorkspaceLicense';
import { settings } from '../../../settings/server';
import { getAndCreateNpsSurvey } from '../../../../server/services/nps/getAndCreateNpsSurvey';
import { SystemLogger } from '../../../../server/lib/logger/system';

export async function syncWorkspace(reconnectCheck = false) {
	const { workspaceRegistered, connectToCloud } = await retrieveRegistrationStatus();
	if (!workspaceRegistered || (!connectToCloud && !reconnectCheck)) {
		return false;
	}

	const info = await buildWorkspaceRegistrationData(undefined);

	const workspaceUrl = settings.get('Cloud_Workspace_Registration_Client_Uri');

	let result;
	try {
		const headers: Record<string, string> = {};
		const token = await getWorkspaceAccessToken(true);

		if (token) {
			headers.Authorization = `Bearer ${token}`;
		} else {
			return false;
		}

		const request = await fetch(`${workspaceUrl}/client`, {
			headers,
			body: info,
			method: 'POST',
		});

		if (!request.ok) {
			throw new Error((await request.json()).error);
		}

		result = await request.json();
	} catch (err: any) {
		SystemLogger.error({
			msg: 'Failed to sync with Rocket.Chat Cloud',
			url: '/client',
			err,
		});

		return false;
	} finally {
		// aways fetch the license
		await getWorkspaceLicense();
	}

	const data = result;
	if (!data) {
		return true;
	}

	if (data.publicKey) {
		await Settings.updateValueById('Cloud_Workspace_PublicKey', data.publicKey);
	}

	if (data.trial?.trialId) {
		await Settings.updateValueById('Cloud_Workspace_Had_Trial', true);
	}

	if (data.nps) {
		const { id: npsId, expireAt } = data.nps;

		const startAt = new Date(data.nps.startAt);

		await NPS.create({
			npsId,
			startAt,
			expireAt: new Date(expireAt),
			createdBy: {
				_id: 'rocket.cat',
				username: 'rocket.cat',
			},
		});

		const now = new Date();

		if (startAt.getFullYear() === now.getFullYear() && startAt.getMonth() === now.getMonth() && startAt.getDate() === now.getDate()) {
			await getAndCreateNpsSurvey(npsId);
		}
	}

	// add banners
	if (data.banners) {
		for await (const banner of data.banners) {
			const { createdAt, expireAt, startAt } = banner;

			await Banner.create({
				...banner,
				createdAt: new Date(createdAt),
				expireAt: new Date(expireAt),
				startAt: new Date(startAt),
			});
		}
	}

	return true;
}


import { useTranslation } from '@rocket.chat/ui-contexts';
import type { ComponentProps, FC } from 'react';
import React from 'react';

import Action from '../../Action';

type AttachmentDownloadProps = Omit<ComponentProps<typeof Action>, 'icon'> & { title?: string | undefined; href: string };

const AttachmentDownload: FC<AttachmentDownloadProps> = ({ title, href, ...props }) => {
	const t = useTranslation();
	return (
		<Action
			icon='cloud-arrow-down'
			href={`${href}?download`}
			title={t('Download')}
			is='a'
			target='_blank'
			rel='noopener noreferrer'
			download={title}
			{...props}
		/>
	);
};

export default AttachmentDownload;


import { Meteor } from 'meteor/meteor';

import { API } from '../../../app/api/server/api';
import { hasLicense } from '../../app/license/server/license';

API.v1.addRoute(
	'chat.getMessageReadReceipts',
	{ authRequired: true },
	{
		async get() {
			if (!hasLicense('message-read-receipt')) {
				throw new Meteor.Error('error-action-not-allowed', 'This is an enterprise feature');
			}

			const { messageId } = this.queryParams;
			if (!messageId) {
				return API.v1.failure({
					error: "The required 'messageId' param is missing.",
				});
			}

			return API.v1.success({
				receipts: await Meteor.callAsync('getReadReceipts', { messageId }),
			});
		},
	},
);


dispatchToastMessage({ type: 'error', message: error });

[addRoomEndpoint, rooms, teamId, onClose, dispatchToastMessage, t],

keep keep keep keep keep replace keep keep keep

[addRoomEndpoint, teamId, onClose, dispatchToastMessage, t],


import type { FC } from 'react';
import React from 'react';

import AutoCompleteTagsMultiple from './AutoCompleteTagsMultiple';

const CurrentChatTags: FC<{ value: Array<string>; handler: () => void }> = ({ value, handler }) => (
	<AutoCompleteTagsMultiple onChange={handler} value={value} />
);

export default CurrentChatTags;


import type { IInvite, IRoom } from '@rocket.chat/core-typings';
import type { JSONSchemaType } from 'ajv';
import Ajv from 'ajv';

const ajv = new Ajv({
	coerceTypes: true,
});

type UseInviteTokenProps = {
	token: string;
};

const UseInviteTokenPropsSchema: JSONSchemaType<UseInviteTokenProps> = {
	type: 'object',
	properties: {
		token: {
			type: 'string',
		},
	},
	required: ['token'],
	additionalProperties: false,
};

export const isUseInviteTokenProps = ajv.compile<UseInviteTokenProps>(UseInviteTokenPropsSchema);

type ValidateInviteTokenProps = {
	token: string;
};

const ValidateInviteTokenPropsSchema: JSONSchemaType<ValidateInviteTokenProps> = {
	type: 'object',
	properties: {
		token: {
			type: 'string',
		},
	},
	required: ['token'],
	additionalProperties: false,
};

export const isValidateInviteTokenProps = ajv.compile<ValidateInviteTokenProps>(ValidateInviteTokenPropsSchema);

type FindOrCreateInviteParams = { rid: IRoom['_id']; days: number; maxUses: number };

const FindOrCreateInviteParamsSchema: JSONSchemaType<FindOrCreateInviteParams> = {
	type: 'object',
	properties: {
		rid: {
			type: 'string',
		},
		days: {
			type: 'integer',
		},
		maxUses: {
			type: 'integer',
		},
	},
	required: ['rid', 'days', 'maxUses'],
	additionalProperties: false,
};

export const isFindOrCreateInviteParams = ajv.compile<FindOrCreateInviteParams>(FindOrCreateInviteParamsSchema);

type SendInvitationEmailParams = {
	emails: string[];
};

const SendInvitationEmailParamsSchema: JSONSchemaType<SendInvitationEmailParams> = {
	type: 'object',
	properties: {
		emails: {
			type: 'array',
			items: {
				type: 'string',
			},
		},
	},
	required: ['emails'],
	additionalProperties: false,
};

export const isSendInvitationEmailParams = ajv.compile<SendInvitationEmailParams>(SendInvitationEmailParamsSchema);

export type InvitesEndpoints = {
	'/v1/listInvites': {
		GET: () => Array<IInvite>;
	};
	'/v1/removeInvite/:_id': {
		DELETE: () => boolean;
	};
	'/v1/useInviteToken': {
		POST: (params: UseInviteTokenProps) => {
			room: {
				rid: IRoom['_id'];
				prid: IRoom['prid'];
				fname: IRoom['fname'];
				name: IRoom['name'];
				t: IRoom['t'];
			};
		};
	};
	'/v1/validateInviteToken': {
		POST: (params: ValidateInviteTokenProps) => { valid: boolean };
	};
	'/v1/findOrCreateInvite': {
		POST: (params: FindOrCreateInviteParams) => IInvite;
	};
	'/v1/sendInvitationEmail': {
		POST: (params: SendInvitationEmailParams) => { success: boolean };
	};
};


import type { Locator, Page } from '@playwright/test';

import { HomeContent } from './home-content';
import { OmnichannelCloseChatModal } from './omnichannel-close-chat-modal';

export class HomeOmnichannelContent extends HomeContent {
	readonly omnichannelCloseChatModal: OmnichannelCloseChatModal;

	constructor(page: Page) {
		super(page);
		this.omnichannelCloseChatModal = new OmnichannelCloseChatModal(page);
	}

	get inputMessage(): Locator {
		return this.page.locator('[name="msg"]');
	}

	get btnForwardChat(): Locator {
		return this.page.locator('[data-qa-id="ToolBoxAction-balloon-arrow-top-right"]');
	}

	get btnCloseChat(): Locator {
		return this.page.locator('[data-qa-id="ToolBoxAction-balloon-close-top-right"]');
	}
}


keep keep keep replace keep keep keep keep

<Modal wrapperFunction={(props) => <Box is='form' onSubmit={handleSubmit(handleAddChannels)} {...props} />}>

import type { IRoom, Serialized } from '@rocket.chat/core-typings';
import { Box, Button, Field, Modal } from '@rocket.chat/fuselage';
import { useToastMessageDispatch, useEndpoint, useTranslation } from '@rocket.chat/ui-contexts';
import type { ComponentProps } from 'react';
import React, { memo, useCallback } from 'react';

import { useForm } from '../../../../../hooks/useForm';
import RoomsAvailableForTeamsAutoComplete from './RoomsAvailableForTeamsAutoComplete';

type AddExistingModalProps = {
	teamId: string;
	onClose: () => void;
};

const AddExistingModal = ({ onClose, teamId }: AddExistingModalProps) => {
	const t = useTranslation();

	const addRoomEndpoint = useEndpoint('POST', '/v1/teams.addRooms');
	const dispatchToastMessage = useToastMessageDispatch();

	const { values, handlers, hasUnsavedChanges } = useForm({
		rooms: [] as Serialized<IRoom>[],
	});

	const { rooms } = values as { rooms: string[] };
	const { handleRooms } = handlers;

	const handleAddChannels = useCallback(
		async (e) => {
			e.preventDefault();
			try {
				await addRoomEndpoint({
					rooms,
					teamId,
				});

				dispatchToastMessage({ type: 'success', message: t('Channels_added') });
			} catch (error) {
				dispatchToastMessage({ type: 'error', message: error });
			} finally {
				onClose();
			}
		},
		[addRoomEndpoint, rooms, teamId, onClose, dispatchToastMessage, t],
	);

	return (
		<Modal wrapperFunction={(props: ComponentProps<typeof Box>) => <Box is='form' onSubmit={handleAddChannels} {...props} />}>
			<Modal.Header>
				<Modal.Title>{t('Team_Add_existing_channels')}</Modal.Title>
				<Modal.Close onClick={onClose} />
			</Modal.Header>
			<Modal.Content>
				<Field mbe='x24'>
					<Field.Label>{t('Channels')}</Field.Label>
					<RoomsAvailableForTeamsAutoComplete value={rooms} onChange={handleRooms} />
				</Field>
			</Modal.Content>
			<Modal.Footer>
				<Modal.FooterControllers>
					<Button onClick={onClose}>{t('Cancel')}</Button>
					<Button disabled={!hasUnsavedChanges} type='submit' primary>
						{t('Add')}
					</Button>
				</Modal.FooterControllers>
			</Modal.Footer>
		</Modal>
	);
};

export default memo(AddExistingModal);


import Ajv from 'ajv';

const ajv = new Ajv({
	coerceTypes: true,
});

export type MailerProps = {
	from: string;
	subject: string;
	body: string;
	dryrun?: boolean;
	query?: string;
};

const MailerPropsSchema = {
	type: 'object',
	properties: {
		from: {
			type: 'string',
		},
		subject: {
			type: 'string',
		},
		body: {
			type: 'string',
		},
		dryrun: {
			type: 'boolean',
			nullable: true,
		},
		query: {
			type: 'string',
			nullable: true,
		},
	},
	required: ['from', 'subject', 'body'],
	additionalProperties: false,
};

export const isMailerProps = ajv.compile<MailerProps>(MailerPropsSchema);


export const sortAppsByClosestOrFarthestModificationDate = (firstDateString: string, secondDateString: string): number => {
	const firstDate = new Date(firstDateString);
	const secondDate = new Date(secondDateString);

	return +secondDate - +firstDate;
};


import { CustomSoundContext } from '@rocket.chat/ui-contexts';
import type { FC } from 'react';
import React from 'react';

import { CustomSounds } from '../../app/custom-sounds/client/lib/CustomSounds';

const CustomSoundProvider: FC = ({ children }) => <CustomSoundContext.Provider children={children} value={CustomSounds} />;

export default CustomSoundProvider;


<RoomsAvailableForTeamsAutoComplete value={rooms} onChange={handleRooms} />

keep keep keep keep keep replace keep keep keep keep keep

render={({ field: { value, onChange } }) => <RoomsAvailableForTeamsAutoComplete value={value} onChange={onChange} />}


export { default } from './OTRWithData';


import { Box } from '@rocket.chat/fuselage';
import type { FC } from 'react';
import React from 'react';

type SortIconProps = {
	direction?: 'asc' | 'desc';
};

const SortIcon: FC<SortIconProps> = ({ direction }) => (
	<Box is='svg' width='x16' height='x16' viewBox='0 0 16 16' fill='none' xmlns='http://www.w3.org/2000/svg'>
		<path
			d='M5.33337 5.99999L8.00004 3.33333L10.6667 5.99999'
			stroke={direction === 'desc' ? '#9EA2A8' : '#E4E7EA'}
			strokeWidth='1.33333'
			strokeLinecap='round'
			strokeLinejoin='round'
		/>
		<path
			d='M5.33337 10L8.00004 12.6667L10.6667 10'
			stroke={direction === 'asc' ? '#9EA2A8' : '#E4E7EA'}
			strokeWidth='1.33333'
			strokeLinecap='round'
			strokeLinejoin='round'
		/>
	</Box>
);

export default SortIcon;


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

export const createSlackBridgeSettings = () =>
	settingsRegistry.addGroup('SlackBridge', async function () {
		await this.add('SlackBridge_Enabled', false, {
			type: 'boolean',
			i18nLabel: 'Enabled',
			public: true,
		});

		await this.add('SlackBridge_APIToken', '', {
			type: 'string',
			multiline: true,
			enableQuery: {
				_id: 'SlackBridge_Enabled',
				value: true,
			},
			i18nLabel: 'SlackBridge_APIToken',
			i18nDescription: 'SlackBridge_APIToken_Description',
			secret: true,
		});

		await this.add('SlackBridge_FileUpload_Enabled', true, {
			type: 'boolean',
			enableQuery: {
				_id: 'SlackBridge_Enabled',
				value: true,
			},
			i18nLabel: 'FileUpload',
		});

		await this.add('SlackBridge_Out_Enabled', false, {
			type: 'boolean',
			enableQuery: {
				_id: 'SlackBridge_Enabled',
				value: true,
			},
		});

		await this.add('SlackBridge_Out_All', false, {
			type: 'boolean',
			enableQuery: [
				{
					_id: 'SlackBridge_Enabled',
					value: true,
				},
				{
					_id: 'SlackBridge_Out_Enabled',
					value: true,
				},
			],
		});

		await this.add('SlackBridge_Out_Channels', '', {
			type: 'roomPick',
			enableQuery: [
				{
					_id: 'SlackBridge_Enabled',
					value: true,
				},
				{
					_id: 'SlackBridge_Out_Enabled',
					value: true,
				},
				{
					_id: 'SlackBridge_Out_All',
					value: false,
				},
			],
		});

		await this.add('SlackBridge_AliasFormat', '', {
			type: 'string',
			enableQuery: {
				_id: 'SlackBridge_Enabled',
				value: true,
			},
			i18nLabel: 'Alias_Format',
			i18nDescription: 'Alias_Format_Description',
		});

		await this.add('SlackBridge_ExcludeBotnames', '', {
			type: 'string',
			enableQuery: {
				_id: 'SlackBridge_Enabled',
				value: true,
			},
			i18nLabel: 'Exclude_Botnames',
			i18nDescription: 'Exclude_Botnames_Description',
		});

		await this.add('SlackBridge_Reactions_Enabled', true, {
			type: 'boolean',
			enableQuery: {
				_id: 'SlackBridge_Enabled',
				value: true,
			},
			i18nLabel: 'Reactions',
		});

		await this.add('SlackBridge_Remove_Channel_Links', 'removeSlackBridgeChannelLinks', {
			type: 'action',
			actionText: 'Remove_Channel_Links',
			i18nDescription: 'SlackBridge_Remove_Channel_Links_Description',
			enableQuery: {
				_id: 'SlackBridge_Enabled',
				value: true,
			},
		});
	});


<Button disabled={!hasUnsavedChanges} type='submit' primary>

<Button disabled={!isDirty} type='submit' primary>


import { useTranslation } from '@rocket.chat/ui-contexts';
import React, { useRef, useEffect } from 'react';

import { drawLineChart } from '../../../../../app/livechat/client/lib/chartHandler';
import { AsyncStatePhase } from '../../../../hooks/useAsyncState';
import { useEndpointData } from '../../../../hooks/useEndpointData';
import Chart from './Chart';
import { useUpdateChartData } from './useUpdateChartData';

const initialData = {
	departments: {},
};

const init = (canvas, context, t) =>
	drawLineChart(canvas, context, [t('Open'), t('Closed')], [], [[], []], {
		legends: true,
		anim: true,
		smallTicks: true,
	});

const ChatsPerDepartmentChart = ({ params, reloadRef, ...props }) => {
	const t = useTranslation();

	const canvas = useRef();
	const context = useRef();

	const updateChartData = useUpdateChartData({
		context,
		canvas,
		t,
		init,
	});

	const {
		value: data,
		phase: state,
		reload,
	} = useEndpointData('/v1/livechat/analytics/dashboards/charts/chats-per-department', { params });

	reloadRef.current.chatsPerDepartmentChart = reload;

	const chartData = data ?? initialData;

	useEffect(() => {
		const initChart = async () => {
			context.current = await init(canvas.current, context.current, t);
		};
		initChart();
	}, [t]);

	useEffect(() => {
		if (state === AsyncStatePhase.RESOLVED) {
			if (chartData && chartData.success) {
				delete chartData.success;
				Object.entries(chartData).forEach(([name, value]) => {
					updateChartData(name, [value.open, value.closed]);
				});
			}
		}
	}, [chartData, state, t, updateChartData]);

	return <Chart ref={canvas} {...props} />;
};

export default ChatsPerDepartmentChart;


Tem certeza que deseja trocar de departamento?

Uma nova mensagem desde {{val, datetime}}

{{count}} novas mensagens desde {{val, datetime}}

Não estamos online agora. Por favor deixe uma mensagem.

Seu lugar é #{{spot}} (Tempo estimado: {{estimatedWaitTime}})


import { useMutableCallback } from '@rocket.chat/fuselage-hooks';
import { useSetModal } from '@rocket.chat/ui-contexts';
import type { MouseEvent, FC } from 'react';
import React from 'react';

export const useCreateRoomModal = (Component: FC<any>): ((e: MouseEvent<HTMLElement>) => void) => {
	const setModal = useSetModal();

	return useMutableCallback((e) => {
		e.preventDefault();

		const handleClose = (): void => {
			setModal(null);
		};

		setModal(() => <Component onClose={handleClose} />);
	});
};


import { AutoComplete, Box, Icon, Option, Options, Chip } from '@rocket.chat/fuselage';

import { useDebouncedValue } from '@rocket.chat/fuselage-hooks';

import { useEndpoint } from '@rocket.chat/ui-contexts';

import { useQuery } from '@tanstack/react-query';

import type { ComponentProps } from 'react';

import React, { memo, useMemo, useState } from 'react';

import { AutoComplete, Box, Option, Options, Chip } from '@rocket.chat/fuselage';


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

import './Presence';

class Notifications {
	constructor(...args) {
		this.logged = Meteor.userId() !== null;
		this.loginCb = [];
		Tracker.autorun(() => {
			if (Meteor.userId() !== null && this.logged === false) {
				this.loginCb.forEach((cb) => cb());
			}
			this.logged = Meteor.userId() !== null;
		});
		this.debug = false;
		this.streamAll = new Meteor.Streamer('notify-all');
		this.streamLogged = new Meteor.Streamer('notify-logged');
		this.streamRoom = new Meteor.Streamer('notify-room');
		this.streamRoomUsers = new Meteor.Streamer('notify-room-users');
		this.streamUser = new Meteor.Streamer('notify-user');

		if (this.debug === true) {
			this.onAll(function () {
				return console.log('RocketChat.Notifications: onAll', args);
			});
			this.onUser(function () {
				return console.log('RocketChat.Notifications: onAll', args);
			});
		}
	}

	onLogin(cb) {
		this.loginCb.push(cb);
		if (this.logged) {
			return cb();
		}
	}

	notifyRoom(room, eventName, ...args) {
		if (this.debug === true) {
			console.log('RocketChat.Notifications: notifyRoom', [room, eventName, ...args]);
		}
		args.unshift(`${room}/${eventName}`);
		return this.streamRoom.emit.apply(this.streamRoom, args);
	}

	notifyUser(userId, eventName, ...args) {
		if (this.debug === true) {
			console.log('RocketChat.Notifications: notifyUser', [userId, eventName, ...args]);
		}
		args.unshift(`${userId}/${eventName}`);
		return this.streamUser.emit.apply(this.streamUser, args);
	}

	notifyUsersOfRoom(room, eventName, ...args) {
		if (this.debug === true) {
			console.log('RocketChat.Notifications: notifyUsersOfRoom', [room, eventName, ...args]);
		}
		args.unshift(`${room}/${eventName}`);
		return this.streamRoomUsers.emit.apply(this.streamRoomUsers, args);
	}

	onAll(eventName, callback) {
		return this.streamAll.on(eventName, callback);
	}

	onLogged(eventName, callback) {
		return this.onLogin(() => this.streamLogged.on(eventName, callback));
	}

	onRoom(room, eventName, callback) {
		if (this.debug === true) {
			this.streamRoom.on(room, function () {
				return console.log(`RocketChat.Notifications: onRoom ${room}`, [room, eventName, callback]);
			});
		}
		return this.streamRoom.on(`${room}/${eventName}`, callback);
	}

	async onUser(eventName, callback, visitorId = null) {
		await this.streamUser.on(`${Meteor.userId() || visitorId}/${eventName}`, callback);
		return () => this.unUser(eventName, callback, visitorId);
	}

	unAll(callback) {
		return this.streamAll.removeListener('notify', callback);
	}

	unLogged(callback) {
		return this.streamLogged.removeListener('notify', callback);
	}

	unRoom(room, eventName, callback) {
		return this.streamRoom.removeListener(`${room}/${eventName}`, callback);
	}

	unUser(eventName, callback, visitorId = null) {
		return this.streamUser.removeListener(`${Meteor.userId() || visitorId}/${eventName}`, callback);
	}
}

export default new Notifications();


import type { SettingValue } from '@rocket.chat/core-typings';

export interface ISettingsService {
	get<T extends SettingValue>(settingId: string): Promise<T>;
}


renderSelected={({ selected: { value, label }, onRemove }) => (

<Chip key={value} height='x20' value={value} onClick={onRemove} mie='x4'>

<Icon name={label.type === 'c' ? 'hash' : 'hashtag-lock'} size='x12' />

keep keep keep keep replace replace keep keep keep keep keep

<Chip key={value} height='x20' value={value} onClick={onRemove} mb='x2' mie='x4'>

<RoomAvatar size='x20' room={{ type: label?.type || 'c', _id: value, ...label }} />


import * as path from 'path';

import ReactPDF, { Font, Document, Page, StyleSheet } from '@react-pdf/renderer';
import type { ILivechatAgent, ILivechatVisitor, IMessage, Serialized } from '@rocket.chat/core-typings';
import colors from '@rocket.chat/fuselage-tokens/colors.json';
import type { Root } from '@rocket.chat/message-parser';

import { Header } from './components/Header';
import { MessageList } from './components/MessageList';

const FONT_PATH = path.resolve(__dirname, '../../public');

export type PDFFile = { name?: string; buffer: Buffer | null; extension?: 'png' | 'jpg' };

export type Quote = { md: Root; name: string; ts: string };

export type PDFMessage = Serialized<Omit<Pick<IMessage, 'msg' | 'u' | 'ts' | 'md'>, 'files'>> & {
	files?: PDFFile[];
} & { divider?: string } & { quotes?: Quote[] };

export type ChatTranscriptData = {
	header: {
		agent: Pick<ILivechatAgent, 'name' | 'username'>;
		visitor: Pick<ILivechatVisitor, 'name' | 'username'>;
		siteName: string;
		date: string;
		time: string;
	};
	messages: PDFMessage[];
	t: (key: string) => string;
};

const styles = StyleSheet.create({
	page: {
		fontFamily: 'Inter',
		lineHeight: 1.25,
		color: colors.n800,
	},
	wrapper: {
		paddingHorizontal: 32,
	},
	message: {
		wordWrap: 'break-word',
		fontSize: 12,
		marginBottom: 20,
		textAlign: 'justify',
	},
});

export const ChatTranscriptPDF = ({ header, messages, t }: ChatTranscriptData) => {
	const agentValue = header.agent?.name || header.agent?.username || t('Not_assigned');
	const customerValue = header.visitor?.name || header.visitor?.username;
	const dateValue = header.date;
	const timeValue = header.time;

	return (
		<Document>
			<Page size='A4' style={styles.page}>
				<Header
					title={header.siteName}
					subtitle={t('Chat_transcript')}
					values={[
						{ key: t('Agent'), value: agentValue },
						{ key: t('Date'), value: dateValue },
						{ key: t('Customer'), value: customerValue },
						{ key: t('Time'), value: timeValue },
					]}
				/>
				<MessageList messages={messages} invalidFileMessage={t('This_attachment_is_not_supported')} />
			</Page>
		</Document>
	);
};

export default async (data: ChatTranscriptData): Promise<NodeJS.ReadableStream> => {
	Font.register({
		family: 'Inter',
		fonts: [
			{ src: `${FONT_PATH}/inter400.ttf` },
			{ src: `${FONT_PATH}/inter400-italic.ttf`, fontStyle: 'italic' },
			{ src: `${FONT_PATH}/inter500.ttf`, fontWeight: 500 },
			{ src: `${FONT_PATH}/inter500-italic.ttf`, fontWeight: 500, fontStyle: 'italic' },
			{ src: `${FONT_PATH}/inter700.ttf`, fontWeight: 700 },
			{ src: `${FONT_PATH}/inter700-italic.ttf`, fontWeight: 700, fontStyle: 'italic' },
		],
	});
	Font.register({
		family: 'FiraCode',
		fonts: [{ src: `${FONT_PATH}/fira-code700.ttf`, fontWeight: 700 }],
	});
	Font.registerHyphenationCallback((word) => [word]);

	return ReactPDF.renderToStream(<ChatTranscriptPDF {...data} />);
};


/* eslint no-multi-spaces: 0 */
import type { IPermission, ISetting } from '@rocket.chat/core-typings';
import { Permissions, Settings } from '@rocket.chat/models';

import { settings } from '../../../settings/server';
import { getSettingPermissionId, CONSTANTS } from '../../lib';
import { createOrUpdateProtectedRoleAsync } from '../../../../server/lib/roles/createOrUpdateProtectedRole';

export const upsertPermissions = async (): Promise<void> => {
	// Note:
	// 1.if we need to create a role that can only edit channel message, but not edit group message
	// then we can define edit-<type>-message instead of edit-message
	// 2. admin, moderator, and user roles should not be deleted as they are referenced in the code.
	const permissions = [
		{ _id: 'access-permissions', roles: ['admin'] },
		{ _id: 'access-setting-permissions', roles: ['admin'] },
		{ _id: 'add-oauth-service', roles: ['admin'] },
		{ _id: 'add-user-to-joined-room', roles: ['admin', 'owner', 'moderator'] },
		{ _id: 'add-user-to-any-c-room', roles: ['admin'] },
		{ _id: 'add-user-to-any-p-room', roles: [] },
		{ _id: 'api-bypass-rate-limit', roles: ['admin', 'bot', 'app'] },
		{ _id: 'archive-room', roles: ['admin', 'owner'] },
		{ _id: 'assign-admin-role', roles: ['admin'] },
		{ _id: 'assign-roles', roles: ['admin'] },
		{ _id: 'ban-user', roles: ['admin', 'owner', 'moderator'] },
		{ _id: 'bulk-register-user', roles: ['admin'] },
		{ _id: 'change-livechat-room-visitor', roles: ['admin', 'livechat-manager', 'livechat-agent'] },
		{ _id: 'create-c', roles: ['admin', 'user', 'bot', 'app'] },
		{ _id: 'create-d', roles: ['admin', 'user', 'bot', 'app'] },
		{ _id: 'create-p', roles: ['admin', 'user', 'bot', 'app'] },
		{ _id: 'create-personal-access-tokens', roles: ['admin', 'user'] },
		{ _id: 'create-user', roles: ['admin'] },
		{ _id: 'clean-channel-history', roles: ['admin'] },
		{ _id: 'delete-c', roles: ['admin', 'owner'] },
		{ _id: 'delete-d', roles: ['admin'] },
		{ _id: 'delete-message', roles: ['admin', 'owner', 'moderator'] },
		{ _id: 'delete-own-message', roles: ['admin', 'user'] },
		{ _id: 'delete-p', roles: ['admin', 'owner'] },
		{ _id: 'delete-user', roles: ['admin'] },
		{ _id: 'edit-message', roles: ['admin', 'owner', 'moderator'] },
		{ _id: 'edit-other-user-active-status', roles: ['admin'] },
		{ _id: 'edit-other-user-info', roles: ['admin'] },
		{ _id: 'edit-other-user-password', roles: ['admin'] },
		{ _id: 'edit-other-user-avatar', roles: ['admin'] },
		{ _id: 'edit-other-user-e2ee', roles: ['admin'] },
		{ _id: 'edit-other-user-totp', roles: ['admin'] },
		{ _id: 'edit-privileged-setting', roles: ['admin'] },
		{ _id: 'edit-room', roles: ['admin', 'owner', 'moderator'] },
		{ _id: 'edit-room-avatar', roles: ['admin', 'owner', 'moderator'] },
		{ _id: 'edit-room-retention-policy', roles: ['admin'] },
		{ _id: 'force-delete-message', roles: ['admin', 'owner'] },
		{ _id: 'join-without-join-code', roles: ['admin', 'bot', 'app'] },
		{ _id: 'leave-c', roles: ['admin', 'user', 'bot', 'anonymous', 'app'] },
		{ _id: 'leave-p', roles: ['admin', 'user', 'bot', 'anonymous', 'app'] },
		{ _id: 'logout-other-user', roles: ['admin'] },
		{ _id: 'manage-assets', roles: ['admin'] },
		{ _id: 'manage-email-inbox', roles: ['admin'] },
		{ _id: 'manage-emoji', roles: ['admin'] },
		{ _id: 'manage-user-status', roles: ['admin'] },
		{ _id: 'manage-outgoing-integrations', roles: ['admin'] },
		{ _id: 'manage-incoming-integrations', roles: ['admin'] },
		{ _id: 'manage-own-outgoing-integrations', roles: ['admin'] },
		{ _id: 'manage-own-incoming-integrations', roles: ['admin'] },
		{ _id: 'manage-oauth-apps', roles: ['admin'] },
		{ _id: 'manage-selected-settings', roles: ['admin'] },
		{ _id: 'mention-all', roles: ['admin', 'owner', 'moderator', 'user'] },
		{ _id: 'mention-here', roles: ['admin', 'owner', 'moderator', 'user'] },
		{ _id: 'mute-user', roles: ['admin', 'owner', 'moderator'] },
		{ _id: 'remove-user', roles: ['admin', 'owner', 'moderator'] },
		{ _id: 'run-import', roles: ['admin'] },
		{ _id: 'run-migration', roles: ['admin'] },
		{ _id: 'set-moderator', roles: ['admin', 'owner'] },
		{ _id: 'set-owner', roles: ['admin', 'owner'] },
		{ _id: 'send-many-messages', roles: ['admin', 'bot', 'app'] },
		{ _id: 'set-leader', roles: ['admin', 'owner'] },
		{ _id: 'unarchive-room', roles: ['admin'] },
		{ _id: 'view-c-room', roles: ['admin', 'user', 'bot', 'app', 'anonymous'] },
		{ _id: 'user-generate-access-token', roles: ['admin'] },
		{ _id: 'view-d-room', roles: ['admin', 'user', 'bot', 'app', 'guest'] },
		{ _id: 'view-full-other-user-info', roles: ['admin'] },
		{ _id: 'view-history', roles: ['admin', 'user', 'anonymous'] },
		{ _id: 'view-joined-room', roles: ['guest', 'bot', 'app', 'anonymous'] },
		{ _id: 'view-join-code', roles: ['admin'] },
		{ _id: 'view-logs', roles: ['admin'] },
		{ _id: 'view-other-user-channels', roles: ['admin'] },
		{ _id: 'view-p-room', roles: ['admin', 'user', 'anonymous', 'guest'] },
		{ _id: 'view-privileged-setting', roles: ['admin'] },
		{ _id: 'view-room-administration', roles: ['admin'] },
		{ _id: 'view-statistics', roles: ['admin'] },
		{ _id: 'view-user-administration', roles: ['admin'] },
		{ _id: 'preview-c-room', roles: ['admin', 'user', 'anonymous'] },
		{ _id: 'view-outside-room', roles: ['admin', 'owner', 'moderator', 'user'] },
		{ _id: 'view-broadcast-member-list', roles: ['admin', 'owner', 'moderator'] },
		{ _id: 'call-management', roles: ['admin', 'owner', 'moderator'] },
		{ _id: 'create-invite-links', roles: ['admin', 'owner', 'moderator'] },
		{
			_id: 'view-l-room',
			roles: ['livechat-manager', 'livechat-monitor', 'livechat-agent', 'admin'],
		},
		{ _id: 'view-livechat-manager', roles: ['livechat-manager', 'livechat-monitor', 'admin'] },
		{
			_id: 'view-omnichannel-contact-center',
			roles: ['livechat-manager', 'livechat-agent', 'livechat-monitor', 'admin'],
		},
		{ _id: 'edit-omnichannel-contact', roles: ['livechat-manager', 'livechat-agent', 'admin'] },
		{ _id: 'view-livechat-rooms', roles: ['livechat-manager', 'livechat-monitor', 'admin'] },
		{
			_id: 'close-livechat-room',
			roles: ['livechat-manager', 'livechat-monitor', 'livechat-agent', 'admin'],
		},
		{ _id: 'close-others-livechat-room', roles: ['livechat-manager', 'livechat-monitor', 'admin'] },
		{
			_id: 'on-hold-livechat-room',
			roles: ['livechat-manager', 'livechat-monitor', 'livechat-agent', 'admin'],
		},
		{
			_id: 'on-hold-others-livechat-room',
			roles: ['livechat-manager', 'livechat-monitor', 'admin'],
		},
		{ _id: 'save-others-livechat-room-info', roles: ['livechat-manager', 'livechat-monitor', 'admin'] },
		{
			_id: 'remove-closed-livechat-rooms',
			roles: ['livechat-manager', 'livechat-monitor', 'admin'],
		},
		{ _id: 'view-livechat-analytics', roles: ['livechat-manager', 'livechat-monitor', 'admin'] },
		{
			_id: 'view-livechat-queue',
			roles: ['livechat-manager', 'livechat-monitor', 'livechat-agent', 'admin'],
		},
		{ _id: 'transfer-livechat-guest', roles: ['livechat-manager', 'livechat-monitor', 'admin'] },
		{ _id: 'manage-livechat-managers', roles: ['livechat-manager', 'admin'] },
		{ _id: 'manage-livechat-agents', roles: ['livechat-manager', 'livechat-monitor', 'admin'] },
		{
			_id: 'manage-livechat-departments',
			roles: ['livechat-manager', 'livechat-monitor', 'admin'],
		},
		{ _id: 'view-livechat-departments', roles: ['livechat-manager', 'livechat-monitor', 'admin'] },
		{
			_id: 'add-livechat-department-agents',
			roles: ['livechat-manager', 'livechat-monitor', 'admin'],
		},
		{
			_id: 'view-livechat-current-chats',
			roles: ['livechat-manager', 'livechat-monitor', 'admin'],
		},
		{
			_id: 'view-livechat-real-time-monitoring',
			roles: ['livechat-manager', 'livechat-monitor', 'admin'],
		},
		{ _id: 'view-livechat-triggers', roles: ['livechat-manager', 'admin'] },
		{ _id: 'view-livechat-customfields', roles: ['livechat-manager', 'admin'] },
		{ _id: 'view-livechat-installation', roles: ['livechat-manager', 'admin'] },
		{ _id: 'view-livechat-appearance', roles: ['livechat-manager', 'admin'] },
		{ _id: 'view-livechat-webhooks', roles: ['livechat-manager', 'admin'] },
		{
			_id: 'view-livechat-business-hours',
			roles: ['livechat-manager', 'livechat-monitor', 'admin'],
		},
		{
			_id: 'view-livechat-room-closed-same-department',
			roles: ['livechat-manager', 'livechat-monitor', 'admin'],
		},
		{
			_id: 'view-livechat-room-closed-by-another-agent',
			roles: ['livechat-manager', 'livechat-monitor', 'admin'],
		},
		{
			_id: 'view-livechat-room-customfields',
			roles: ['livechat-manager', 'livechat-monitor', 'livechat-agent', 'admin'],
		},
		{
			_id: 'edit-livechat-room-customfields',
			roles: ['livechat-manager', 'livechat-monitor', 'livechat-agent', 'admin'],
		},
		{ _id: 'send-omnichannel-chat-transcript', roles: ['livechat-manager', 'admin'] },
		{ _id: 'mail-messages', roles: ['admin'] },
		{ _id: 'toggle-room-e2e-encryption', roles: ['owner', 'admin'] },
		{ _id: 'message-impersonate', roles: ['bot', 'app'] },
		{ _id: 'create-team', roles: ['admin', 'user'] },
		{ _id: 'delete-team', roles: ['admin', 'owner'] },
		{ _id: 'convert-team', roles: ['admin', 'owner'] },
		{ _id: 'edit-team', roles: ['admin', 'owner'] },
		{ _id: 'add-team-member', roles: ['admin', 'owner', 'moderator'] },
		{ _id: 'edit-team-member', roles: ['admin', 'owner', 'moderator'] },
		{ _id: 'add-team-channel', roles: ['admin', 'owner', 'moderator'] },
		{ _id: 'edit-team-channel', roles: ['admin', 'owner', 'moderator'] },
		{ _id: 'remove-team-channel', roles: ['admin', 'owner', 'moderator'] },
		{ _id: 'view-all-team-channels', roles: ['admin', 'owner'] },
		{ _id: 'view-all-teams', roles: ['admin'] },
		{ _id: 'remove-closed-livechat-room', roles: ['livechat-manager', 'admin'] },
		{ _id: 'remove-livechat-department', roles: ['livechat-manager', 'admin'] },

		// VOIP Permissions
		// allows to manage voip calls configuration
		{ _id: 'manage-voip-call-settings', roles: ['livechat-manager', 'admin'] },
		{ _id: 'manage-voip-contact-center-settings', roles: ['livechat-manager', 'admin'] },
		// allows agent-extension association.
		{ _id: 'manage-agent-extension-association', roles: ['admin'] },
		{ _id: 'view-agent-extension-association', roles: ['livechat-manager', 'admin', 'livechat-agent'] },
		// allows to receive a voip call
		{ _id: 'inbound-voip-calls', roles: ['livechat-agent'] },

		{ _id: 'remove-livechat-department', roles: ['livechat-manager', 'admin'] },
		{ _id: 'manage-apps', roles: ['admin'] },
		{ _id: 'post-readonly', roles: ['admin', 'owner', 'moderator'] },
		{ _id: 'set-readonly', roles: ['admin', 'owner'] },
		{ _id: 'set-react-when-readonly', roles: ['admin', 'owner'] },
		{ _id: 'manage-cloud', roles: ['admin'] },
		{ _id: 'manage-sounds', roles: ['admin'] },
		{ _id: 'access-mailer', roles: ['admin'] },
		{ _id: 'pin-message', roles: ['owner', 'moderator', 'admin'] },
		{ _id: 'mobile-upload-file', roles: ['user', 'admin'] },
		{ _id: 'send-mail', roles: ['admin'] },
		{ _id: 'view-federation-data', roles: ['admin'] },
		{ _id: 'add-all-to-room', roles: ['admin'] },
		{ _id: 'get-server-info', roles: ['admin'] },
		{ _id: 'register-on-cloud', roles: ['admin'] },
		{ _id: 'test-admin-options', roles: ['admin'] },
		{ _id: 'sync-auth-services-users', roles: ['admin'] },
		{ _id: 'restart-server', roles: ['admin'] },
		{ _id: 'remove-slackbridge-links', roles: ['admin'] },
		{ _id: 'view-import-operations', roles: ['admin'] },
		{ _id: 'clear-oembed-cache', roles: ['admin'] },
		{ _id: 'videoconf-ring-users', roles: ['admin', 'owner', 'moderator', 'user'] },
		{ _id: 'bypass-time-limit-edit-and-delete', roles: ['bot', 'app'] },
	];

	for await (const permission of permissions) {
		await Permissions.create(permission._id, permission.roles);
	}

	const defaultRoles = [
		{ name: 'admin', scope: 'Users', description: 'Admin' },
		{ name: 'moderator', scope: 'Subscriptions', description: 'Moderator' },
		{ name: 'leader', scope: 'Subscriptions', description: 'Leader' },
		{ name: 'owner', scope: 'Subscriptions', description: 'Owner' },
		{ name: 'user', scope: 'Users', description: '' },
		{ name: 'bot', scope: 'Users', description: '' },
		{ name: 'app', scope: 'Users', description: '' },
		{ name: 'guest', scope: 'Users', description: '' },
		{ name: 'anonymous', scope: 'Users', description: '' },
		{ name: 'livechat-agent', scope: 'Users', description: 'Livechat Agent' },
		{ name: 'livechat-manager', scope: 'Users', description: 'Livechat Manager' },
	] as const;

	for await (const role of defaultRoles) {
		await createOrUpdateProtectedRoleAsync(role.name, role);
	}

	const getPreviousPermissions = async function (settingId?: string): Promise<Record<string, IPermission>> {
		const previousSettingPermissions: {
			[key: string]: IPermission;
		} = {};

		const selector = { level: 'settings' as const, ...(settingId && { settingId }) };

		await Permissions.find(selector).forEach(function (permission: IPermission) {
			previousSettingPermissions[permission._id] = permission;
		});
		return previousSettingPermissions;
	};

	const createSettingPermission = async function (
		setting: ISetting,
		previousSettingPermissions: {
			[key: string]: IPermission;
		},
	): Promise<void> {
		const permissionId = getSettingPermissionId(setting._id);
		const permission: Omit<IPermission, '_id' | '_updatedAt'> = {
			level: CONSTANTS.SETTINGS_LEVEL as 'settings' | undefined,
			// copy those setting-properties which are needed to properly publish the setting-based permissions
			settingId: setting._id,
			group: setting.group,
			section: setting.section ?? undefined,
			sorter: setting.sorter,
			roles: [],
		};
		// copy previously assigned roles if available
		if (previousSettingPermissions[permissionId]?.roles) {
			permission.roles = previousSettingPermissions[permissionId].roles;
		}
		if (setting.group) {
			permission.groupPermissionId = getSettingPermissionId(setting.group);
		}
		if (setting.section) {
			permission.sectionPermissionId = getSettingPermissionId(setting.section);
		}

		const existent = await Permissions.findOne(
			{
				_id: permissionId,
				...permission,
			},
			{ projection: { _id: 1 } },
		);

		if (!existent) {
			try {
				await Permissions.updateOne({ _id: permissionId }, { $set: permission }, { upsert: true });
			} catch (e) {
				if (!(e as Error).message.includes('E11000')) {
					// E11000 refers to a MongoDB error that can occur when using unique indexes for upserts
					// https://docs.mongodb.com/manual/reference/method/db.collection.update/#use-unique-indexes
					await Permissions.updateOne({ _id: permissionId }, { $set: permission }, { upsert: true });
				}
			}
		}

		delete previousSettingPermissions[permissionId];
	};

	const createPermissionsForExistingSettings = async function (): Promise<void> {
		const previousSettingPermissions = await getPreviousPermissions();

		const settings = await Settings.findNotHidden().toArray();
		for await (const setting of settings) {
			await createSettingPermission(setting, previousSettingPermissions);
		}

		// remove permissions for non-existent settings
		for await (const obsoletePermission of Object.keys(previousSettingPermissions)) {
			if (previousSettingPermissions.hasOwnProperty(obsoletePermission)) {
				await Permissions.deleteOne({ _id: obsoletePermission });
			}
		}
	};

	// for each setting which already exists, create a permission to allow changing just this one setting
	await createPermissionsForExistingSettings();

	// register a callback for settings for be create in higher-level-packages
	settings.on('*', async function ([settingId]) {
		const previousSettingPermissions = await getPreviousPermissions(settingId);
		const setting = await Settings.findOneById(settingId);
		if (setting && !setting.hidden) {
			await createSettingPermission(setting, previousSettingPermissions);
		}
	});
};


import type { ComponentProps, ReactElement, Ref } from 'react';
import React, { forwardRef } from 'react';

import ScrollableContentWrapper from '../../components/ScrollableContentWrapper';

type ScrollerWithCustomPropsProps = ComponentProps<typeof ScrollableContentWrapper>;

export default forwardRef(function ScrollerWithCustomProps(
	{ style, ...props }: ScrollerWithCustomPropsProps,
	ref: Ref<HTMLDivElement>,
): ReactElement {
	return (
		<ScrollableContentWrapper
			{...props}
			style={{ ...style, flexGrow: 1, overflowY: 'hidden', width: '100%', willChange: 'transform' }}
			ref={ref}
			renderView={({ style, ...props }): ReactElement => <div {...props} style={{ ...style }} />}
			renderTrackHorizontal={(props): ReactElement => <div {...props} style={{ display: 'none' }} className='track-horizontal' />}
		/>
	);
});


inheritAttrs: false,
props: {
activeClass: {
type: String,
default: 'list__tile--active'
},
avatar: Boolean,
inactive: Boolean,

@require './_theme'

// ============================================================
// Vuetify variables
// ============================================================

$color-pack := true

// Grid
// ============================================================
$grid-breakpoints := {
  xs: 0
  sm: 600px
  md: 960px
  lg: (1280px - 16px) // Desktop gets a 16dp reduction
  xl: (1920px - 16px) // https://material.io/guidelines/layout/responsive-ui.html#responsive-ui-breakpoints
}

$grid-gutter = 8px
$grid-columns := 12
$grid-gutters := {
  xs: ($grid-gutter / 4)
  sm: ($grid-gutter / 2)
  md: $grid-gutter
  lg: ($grid-gutter * 2)
  xl: ($grid-gutter * 3)
}

$container-max-widths := {
  sm: $grid-breakpoints.sm
  md: $grid-breakpoints.md
  lg: $grid-breakpoints.lg
  xl: $grid-breakpoints.xl
}

// Display
// ============================================================
$display-breakpoints := {
  xs-only: "only screen and (max-width: %s)" % ($grid-breakpoints.sm - 1)
  sm-only: "only screen and (min-width: %s) and (max-width: %s)" % ($grid-breakpoints.sm ($grid-breakpoints.md  - 1))
  sm-and-down: "only screen and (max-width: %s)" % ($grid-breakpoints.md - 1)
  sm-and-up: "only screen and (min-width: %s)" % $grid-breakpoints.sm
  md-only: "only screen and (min-width: %s) and (max-width: %s)" % ($grid-breakpoints.md ($grid-breakpoints.lg  - 1))
  md-and-down: "only screen and (max-width: %s )" % ($grid-breakpoints.lg - 1)
  md-and-up: "only screen and (min-width: %s)" % $grid-breakpoints.md
  lg-only: "only screen and (min-width: %s) and (max-width: %s)" % ($grid-breakpoints.lg ($grid-breakpoints.xl - 1))
  lg-and-down: "only screen and (max-width: (%s - 1))" % $grid-breakpoints.xl
  lg-and-up: "only screen and (min-width: %s)" % $grid-breakpoints.lg
  xl-only: "only screen and (min-width: %s)" % $grid-breakpoints.xl
}

// Spacing
// ============================================================
$spacer := 16px
$spacer-x := $spacer
$spacer-y := $spacer
$spacers := {
  zero: {
    x: 0,
    y: 0
  },
  one: {
    x: ($spacer-x * .25),
    y: ($spacer-y * .25)
  },
  two: {
    x: ($spacer-x * .5),
    y: ($spacer-y * .5)
  },
  three: {
    x: $spacer-x,
    y: $spacer-y
  },
  four: {
    x: ($spacer-x * 1.5),
    y: ($spacer-y * 1.5)
  },
  five: {
    x: ($spacer-x * 3),
    y: ($spacer-y * 3)
  }
}

$font-weights := {
  thin: 100,
  light: 300,
  regular: 400,
  medium: 500,
  bold: 700,
  black: 900
}

// Headings
// ============================================================
$heading-color := $theme.secondary
$headings := {
  h1: { size: 112px, weight: 300, line-height: 1, letter-spacing: -.04em },
  h2: { size: 56px, weight: 400, line-height: 1.35, letter-spacing: -.02em },
  h3: { size: 45px, weight: 400, line-height: 48px, letter-spacing: normal },
  h4: { size: 34px, weight: 400, line-height: 40px, letter-spacing: normal },
  h5: { size: 24px, weight: 400, line-height: 32px, letter-spacing: normal },
  h6: { size: 20px, weight: 500, line-height: 1, letter-spacing: .02em },
  subheading: { size: 16px, weight: 400 },
  body-2: { size: 14px, weight: 500 },
  body-1: { size: 14px, weight: 400 },
  caption: { size: 12px, weight: 400 },
  button: { size: 14px, weight: 500 }
}

// Transitions
$transition := {
  fast-out-slow-in: cubic-bezier(0.4, 0.0, 0.2, 1),
  linear-out-slow-in: cubic-bezier(0.0, 0.0, 0.2, 1),
  fast-out-linear-in: cubic-bezier(0.4, 0.0, 1, 1),
  ease-in-out: cubic-bezier(0.4, 0.0, 0.6, 1),
  fast-in-fast-out: cubic-bezier(.25,.8,.25,1),
  swing: cubic-bezier(.25,.8,.50,1)
}

$primary-transition := 0.3s $transition.swing
$secondary-transition := 0.2s $transition.ease-in-out

// Components
// ============================================================

// Alerts
$alert-color := inherit
$alert-border-radius := 0
$alert-border-width := 4px 0 0 0
$alert-font-size := $font-size-root
$alert-icon-color := rgba($material-light.fg-color, .3)
$alert-icon-font-size := 24px
$alert-padding := 16px
$alert-margin := $spacers.one.y auto

// Badges
$badge-border-radius := 50%
$badge-color := $material-dark.fg-color
$badge-font-family := $body-font-family
$badge-font-size := 14px
$badge-height := 22px
$badge-top := 0
$badge-right := 0
$badge-width := 22px

// Breadcrumbs
$breadcrumbs-flex := 0 1 auto
$breadcrumbs-padding := 18px 12px
$breadcrumbs-even-child-padding := 0 12px
$breadcrumbs-item-font-size := 14px
$breadcrumbs-item-large-font-size := 16px
$breadcrumbs-margin := 0
$breadcrumbs-padding := 0 14px

/** Buttons */

// Base
$button-border-radius := 2px
$button-color := $material-light.fg-color
$button-background-color := #999999
$button-height := 36px
$button-font-size := 14px
$button-font-weight := 500
$button-margin-y := 6px
$button-margin-x := 8px
$button-margin := $button-margin-y $button-margin-x
$button-min-width := 88px
$button-padding := 0 16px
$button-text-transform := uppercase

$button-small-height := 28px
$button-small-font-size := 13px

$button-large-height := 44px
$button-large-font-size := 15px

$button-floating-width := 56px
$button-floating-icon-height := 24px
$button-floating-icon-width := 24px
$button-floating-small-width := 40px
$button-floating-large-width := 72px
$button-floating-small-icon-width := 18px
$button-floating-large-icon-width := 30px

// Card
$card-border-radius := 2px

// Chip
$chip-border-radius := 28px
$chip-font-size := 13px
$chip-label-border-radius := $card-border-radius
$chip-label-color := rgba($material-light.fg-color, .87)
$chip-icon-font-size := 24px
$chip-icon-color := rgba($material-light.fg-color, .54)
$chip-padding := 0 12px
$chip-removeable-padding := 0 4px 0 12px
$chip-outline-background := transparent !important
$chip-outline-color := $grey.base
$chip-height := 32px
$chip-margin := 4px
$chip-small-height := 26px

// Collapsible
$expansion-panel-collapse-height := 48px
$collapsible-transition-height := 0.23s

// Footer
$footer-height := 36px

/** Lists */
//  list fonts and weights */
$list-tile-font-size := $headings.subheading.size //16px - the mdc scss uses subheading to configure like this
$list-tile-font-weight := $headings.subheading.weight //400
$list-tile-active-color := $theme.primary

$list-action-text-font-size := 12px //this is for the stacked actions
$list-action-text-font-color := $grey.base

$list-sub-title-font-size := ($list-tile-font-size - 2px) //14px

$subheader-height-font-size := ($list-tile-font-size - 2px) //14px
$subheader-font-weight := $font-weights.medium //500

//   list spacing*/
$list-left-padding := 16px
$list-right-padding := 16px
$list-top-padding := 8px
$list-bottom-padding := 8px

$list-item-right-margin := 56px
$list-item-left-padding := 72px

$list-item-single-height := 48px
$list-item-double-height := 72px
$list-item-triple-height := 88px
$list-item-avatar-single-height := 56px

$subheader-inset-margin := $list-item-right-margin

//   list dense overrides */
$list-item-dense-font-size := ($list-tile-font-size - 3px) //13px
$subheader-dense-font-size := $list-item-dense-font-size
$list-item-dense-icon-font-size := 21px //weird rendering issue

$list-item-dense-top-padding := $list-top-padding / 2
$list-item-dense-single-height := $list-item-single-height - 8px //40px
$list-item-dense-double-height := $list-item-double-height - 12px //60px
$list-item-dense-triple-height := $list-item-triple-height - 12px //76px

$list-item-dense-avatar-single-height := $list-item-single-height //48px the dense concept was to take avatars to a normal single item height
$list-item-dense-avatar-height := 38px

$list-dense-subheader-height := $list-item-dense-single-height
$list-dense-subheader-group-height := $list-dense-subheader-height //the subheader under a group menu

/** Pickers */
$time-picker-number-font-size := 16px

// Toolbar
$toolbar-height := 56px
$toolbar-item-padding := 0 16px
$toolbar-padding-y := 0
$toolbar-padding-x := 16px

// Progress Circular
$progress-circular-rotate-animation := progress-circular-rotate 1.4s linear infinite
$progress-circular-rotate-dash := progress-circular-dash 1.4s ease-in-out infinite
$process-circular-intermediate-svg-transition := all .2s ease-in-out
$progress-circular-underlay-stroke := rgba($material-light.fg-color, 0.1)
$progress-circular-overlay-transition := all .6s ease-in-out

// Navigation drawer
$navigation-drawer-item-font-size := 14px
$navigation-drawer-item-font-weight := $font-weights.medium
$navigation-drawer-item-active-color := $theme.primary
$navigation-drawer-group-item-font-weight := $font-weights.regular

/** Snackbar */
$snackbar-background-color := #323232

/* Text Input */
$input-group-padding := 18px 0 0
$input-font-size := 16px
$input-height := 30px
$input-group-text-field-label-top := 18px

// Directives
// ============================================================

// Ripple animation
$ripple-animation-transition := .4s $transition.linear-out-slow-in
$ripple-animation-visible-opacity := .15


export default {
  name: 'v-form',

  inheritAttrs: false,

  data () {
    return {
      inputs: [],
      errorBag: {}
    }
  },

  props: {
    value: Boolean,
    lazyValidation: Boolean
  },

  watch: {
    errorBag: {
      handler () {
        const errors = Object.values(this.errorBag).includes(true)

        this.$emit('input', !errors)

        return !errors
      },
      deep: true
    }
  },

  methods: {
    getInputs () {
      const results = []

      const search = (children, depth = 0) => {
        for (const child of children) {
          if (child.errorBucket !== undefined) {
            results.push(child)
          } else {
            search(child.$children, depth + 1)
          }
        }
        if (depth === 0) return results
      }

      return search(this.$children)
    },
    watchInputs (inputs = this.getInputs()) {
      for (const child of inputs) {
        if (this.inputs.includes(child)) {
          continue // We already know about this input
        }

        this.inputs.push(child)
        this.watchChild(child)
      }
    },
    watchChild (child) {
      const watcher = (child) => {
        child.$watch('valid', (val) => {
          this.$set(this.errorBag, child._uid, !val)
        }, { immediate: true })
      }

      if (!this.lazyValidation) return watcher(child)

      // Only start watching inputs if we need to
      child.$watch('shouldValidate', (val) => {
        if (!val) return

        // Only watch if we're not already doing it
        if (this.errorBag.hasOwnProperty(child._uid)) return

        watcher(child)
      })
    },
    validate () {
      const errors = this.inputs.filter(input => !input.validate(true)).length
      return !errors
    },
    reset () {
      this.inputs.forEach((input) => input.reset())
      if (this.lazyValidation) {
        Object.keys(this.errorBag).forEach(key => this.$delete(this.errorBag, key))
      }
    }
  },

  mounted () {
    this.$vuetify.load(() => this.watchInputs())
  },

  updated () {
    const inputs = this.getInputs()

    if (inputs.length < this.inputs.length) {
      // Something was removed, we don't want it in the errorBag any more
      const removed = this.inputs.filter(i => !inputs.includes(i))

      for (const input of removed) {
        this.$delete(this.errorBag, input._uid)
        this.$delete(this.inputs, this.inputs.indexOf(input))
      }
    }

    this.watchInputs(inputs)
  },

  render (h) {
    return h('form', {
      attrs: this.$attrs,
      on: {
        submit: e => this.$emit('submit', e)
      }
    }, this.$slots.default)
  }
}


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

exports[`VListTile.vue should render <li> with <a> when using href prop 1`] = `

`;

exports[`VListTile.vue should render <li> with <button> when using to prop 1`] = `

  <button class="list__tile list__tile--link"
          data-ripple="false"
  >
  </button>

`;

exports[`VListTile.vue should render with a div when href and to are not used 1`] = `

`;


require('../../stylus/components/_button-toggle.styl')

import ButtonGroup from '../../mixins/button-group'
import Themeable from '../../mixins/themeable'

export default {
  name: 'v-btn-toggle',

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

  mixins: [ButtonGroup, Themeable],

  props: {
    inputValue: {
      required: false
    },
    items: {
      type: Array,
      default: () => []
    },
    mandatory: Boolean,
    multiple: Boolean
  },

  computed: {
    classes () {
      return {
        'btn-toggle': true,
        'btn-toggle--selected': this.hasValue,
        'theme--light': this.light,
        'theme--dark': this.dark
      }
    },
    hasValue () {
      return (this.multiple && this.inputValue.length) ||
        (!this.multiple && this.inputValue !== null &&
          typeof this.inputValue !== 'undefined')
    }
  },

  watch: {
    inputValue: {
      handler () {
        this.update()
      },
      deep: true
    }
  },

  methods: {
    isSelected (i) {
      const item = this.getValue(i)
      if (!this.multiple) {
        return this.inputValue === item
      }

      return this.inputValue.includes(item)
    },
    updateValue (i) {
      const item = this.getValue(i)
      if (!this.multiple) {
        if (this.mandatory && this.inputValue === item) return
        return this.$emit('change', this.inputValue === item ? null : item)
      }

      const items = this.inputValue.slice()

      const index = items.indexOf(item)
      if (index > -1) {
        if (this.mandatory && items.length === 1) return
        items.length >= 1 && items.splice(index, 1)
      } else {
        items.push(item)
      }

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

  mounted () {
    if (this.items.length > 0) {
      console.warn('The \'items\' props has been deprecated. v-btn-toggle now has a default slot where you can place buttons.')
    }
  },

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


'list__tile--link': this.isLink && !this.inactive,

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

exports[`VSelect should render buttons correctly when using items array with segmented prop 1`] = `

      <button type="button"
              class="btn btn--flat"
              data-ripple="true"
      >

          Hello

      </button>
      <input disabled="disabled"
             tabindex="-1"
             class="input-group--select__autocomplete"
             style="display: none;"
      >

                    Hello

      arrow_drop_down

`;

exports[`VSelect should render buttons correctly when using slot with segmented prop 1`] = `

        Hello

      <input disabled="disabled"
             tabindex="-1"
             class="input-group--select__autocomplete"
             style="display: none;"
      >

                    Hello

      arrow_drop_down

`;


import { VExpandTransition } from '../transitions'

import Bootable from '../../mixins/bootable'
import Toggleable from '../../mixins/toggleable'

import VIcon from '../VIcon'

import Ripple from '../../directives/ripple'
import ClickOutside from '../../directives/click-outside'

export default {
  name: 'v-expansion-panel-content',

  mixins: [Bootable, Toggleable],

  components: {
    VIcon
  },

  directives: {
    Ripple,
    ClickOutside
  },

  inject: ['focusable', 'panelClick'],

  data () {
    return {
      height: 'auto'
    }
  },

  props: {
    hideActions: Boolean,
    ripple: Boolean
  },

  methods: {
    genBody () {
      return this.$createElement('div', {
        ref: 'body',
        class: 'expansion-panel__body',
        directives: [
          {
            name: 'show',
            value: this.isActive
          }
        ]
      }, this.showLazyContent(this.$slots.default))
    },
    genHeader () {
      return this.$createElement('div', {
        staticClass: 'expansion-panel__header',
        directives: [{
          name: 'ripple',
          value: this.ripple
        }],
        on: {
          click: () => this.panelClick(this._uid)
        }
      }, [
        this.$slots.header,
        this.genIcon()
      ])
    },
    genIcon (h) {
      if (this.hideActions) return null

      const icon = this.$slots.actions ||
        this.$createElement('v-icon', 'keyboard_arrow_down')

      return this.$createElement('div', {
        staticClass: 'header__icon'
      }, [icon])
    },
    toggle (uid) {
      const isActive = this._uid === uid && !this.isActive

      if (isActive) this.isBooted = true

      // We treat bootable differently
      // Needs time to calc height
      this.$nextTick(() => (this.isActive = isActive))
    }
  },

  render (h) {
    const children = []

    this.$slots.header && children.push(this.genHeader())
    children.push(h(VExpandTransition, [this.genBody()]))

    return h('li', {
      staticClass: 'expansion-panel__container',
      'class': {
        'expansion-panel__container--active': this.isActive
      },
      attrs: {
        tabindex: 0
      },
      on: {
        keydown: e => {
          // Ensure element is focusable and the activeElement
          if (this.focusable &&
            this.$el === document.activeElement &&
            e.keyCode === 13
          ) this.panelClick(this._uid)
        }
      }
    }, children)
  }
}


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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    wrapper.vm.focus()

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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


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

import Translatable from '../../mixins/translatable'

export default {
  name: 'v-parallax',

  mixins: [Translatable],

  data () {
    return {
      isBooted: false
    }
  },

  props: {
    alt: String,
    height: {
      type: [String, Number],
      default: 500
    },
    jumbotron: Boolean,
    src: String
  },

  computed: {
    styles () {
      return {
        display: 'block',
        opacity: this.isBooted ? 1 : 0,
        transform: `translate3d(-50%, ${this.jumbotron ? 0 : this.parallax + 'px'}, 0)`
      }
    }
  },

  watch: {
    parallax () {
      this.isBooted = true
    }
  },

  methods: {
    init () {
      if (!this.$refs.img) return

      if (this.$refs.img.complete) {
        this.translate()
        this.listeners()
      } else {
        this.$refs.img.addEventListener('load', () => {
          this.translate()
          this.listeners()
        }, false)
      }
    },
    objHeight () {
      return this.$refs.img.naturalHeight
    },
    elOffsetTop () {
      return this.$el.offsetTop
    }
  },

  render (h) {
    const imgData = {
      staticClass: 'parallax__image',
      'class': {
        'parallax__image--jumbotron': this.jumbotron
      },
      style: this.styles,
      attrs: {
        src: this.src
      },
      ref: 'img'
    }

    if (this.alt) imgData.attrs.alt = this.alt

    const container = h('div', {
      staticClass: 'parallax__image-container'
    }, [
      h('img', imgData)
    ])

    const content = h('div', {
      staticClass: 'parallax__content'
    }, this.$slots.default)

    return h('div', {
      staticClass: 'parallax',
      style: {
        height: this.jumbotron
          ? this.normalizedHeight
          : `${this.normalizedHeight}px`
      },
      on: this.$listeners
    }, [container, content])
  }
}


keep keep keep keep replace keep keep keep

class="list__tile list__tile--link list__tile--active primary--text"

import Routable from '../../mixins/routable'
import Toggleable from '../../mixins/toggleable'
import Ripple from '../../directives/ripple'

export default {
  name: 'v-list-tile',

  mixins: [Routable, Toggleable],

  directives: {
    Ripple
  },

  inheritAttrs: false,

  props: {
    activeClass: {
      type: String,
      default: 'list__tile--active'
    },
    avatar: Boolean,
    inactive: Boolean,
    tag: String
  },

  computed: {
    classes () {
      return {
        'list__tile': true,
        'list__tile--link': this.isLink && !this.inactive,
        'list__tile--avatar': this.avatar,
        'list__tile--disabled': this.disabled,
        [this.activeClass]: this.isActive
      }
    },
    isLink () {
      return this.href || this.to ||
        (this.$listeners && (this.$listeners.click || this.$listeners['!click']))
    }
  },

  render (h) {
    const isRouteLink = !this.inactive && this.isLink
    const { tag, data } = isRouteLink ? this.generateRouteLink() : {
      tag: this.tag || 'div',
      data: {
        class: this.classes
      }
    }

    data.attrs = Object.assign({}, data.attrs, this.$attrs)

    return h('li', {
      attrs: {
        disabled: this.disabled
      },
      on: {
        ...this.$listeners
      }
    }, [h(tag, data, this.$slots.default)])
  }
}


<template>
  <v-app>
  </v-app>
</template>

<script>
  export default {
    data: () => ({
    })
  }
</script>


import { test } from '~util/testing'
import VTabs from './VTabs'
import VTabsBar from './VTabsBar'
import VTabsItem from './VTabsItem'
import Vue from 'vue/dist/vue.common'
import { createRange } from '~util/helpers'

function createBar (items = ['foo', 'bar']) {
  return Vue.component('test', {
    components: {
      VTabsBar,
      VTabsItem
    },
    render (h) {
      return h('v-tabs-bar', {}, items.map(i => {
        return h('v-tabs-item', { props: { href: `${i}` } })
      }))
    }
  })
}

test('VTabs.vue', ({ mount }) => {
  it('should change model when tab is clicked', async () => {
    const wrapper = mount(VTabs, {
      slots: {
        default: [
          createBar()
        ]
      }
    })

    await wrapper.vm.$nextTick()

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

    const item1 = wrapper.find('.tabs__item')[0]
    const item2 = wrapper.find('.tabs__item')[1]

    item1.trigger('click')
    await wrapper.vm.$nextTick()
    expect(input).toBeCalledWith('foo')

    item2.trigger('click')
    await wrapper.vm.$nextTick()
    expect(input).toBeCalledWith('bar')
  })

  it('should change tab when model changes', async () => {
    const wrapper = mount(VTabs, {
      propsData: {
        value: 'bar'
      },
      slots: {
        default: [
          createBar()
        ]
      }
    })

    await wrapper.vm.$nextTick()

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

    expect(wrapper.vm.value).toBe('bar')

    wrapper.setProps({ value: 'foo' })
    await wrapper.vm.$nextTick()
    expect(input).toBeCalledWith('foo')
  })

  it('should mount with booted false then activate to remove transition', async () => {
    const wrapper = mount(VTabs, {
      propsData: {
        value: 'bar'
      },
      slots: {
        default: [
          createBar()
        ]
      }
    })

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

    wrapper.vm.activeIndex = 0
    await wrapper.vm.$nextTick()

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

  /* TODO: Don't know how to dynamically change slots after wrapper created??
  it('should add navigation if dynamically added tabs result in overflow', async () => {
    const wrapper = mount(VTabs, {
      slots: {
        default: [createBar(createRange(2))]
      },
      attachToDocument: true
    })

    console.log(wrapper.html())
  })
  */
})


import VRadioGroup from './VRadioGroup'
import VRadio from './VRadio'

export { VRadioGroup, VRadio }

VRadioGroup.install = function install (Vue) {
  Vue.component(VRadioGroup.name, VRadioGroup)
  Vue.component(VRadio.name, VRadio)
}

export default VRadioGroup


keep keep keep keep replace keep keep keep keep


export function factory (prop = 'value', event = 'input') {
  return {
    model: { prop, event },

    props: {
      [prop]: { required: false }
    },

    data () {
      return {
        isActive: !!this[prop]
      }
    },

    watch: {
      [prop] (val) {
        this.isActive = !!val
      },
      isActive (val) {
        !!val !== this[prop] && this.$emit(event, val)
      }
    }
  }
}

const Toggleable = factory()

export default Toggleable


@import '../bootstrap'

.input-group--select
  // TODO: revisit this
  .input-group--select__autocomplete
    display: block
    height: 0px

    &--index
      opacity: 0 !important

  .input-group__append-icon
    transition: .3s $transition.linear-out-slow-in

    &.input-group__icon-clearable
      transition: none

  &:not(.input-group--dirty),
  &.input-group--focused
    .input-group--select__autocomplete
      padding-bottom: 1px
      height: 30px

  &.input-group--focused
    .input-group--select__autocomplete
      display: inline-block
      opacity: 1

    .input-group__append-icon:not(.input-group__icon-clearable)
      transform: rotate(-180deg)

  .input-group__input
    cursor: pointer

  &.input-group--disabled
    cursor: default
    pointer-events: none

.input-group--select
  .input-group__selections
    align-items: center
    display: flex
    flex-wrap: wrap
    position: relative
    width: 100%

    &__comma
      display: inline-flex
      font-size: 16px
      padding: 3px 4px 3px 0

      &--active
        color: inherit

  .menu
    display: inline

  .fade-transition-leave-active
    position: absolute
    left: 0

  &.input-group--autocomplete.input-group--search-focused
    .input-group__selections__comma
      display: none

.input-group--autocomplete
  .input-group__selections
    cursor: text

.input-group
  &.input-group--overflow,
  &.input-group--editable,
  &.input-group--segmented
    padding: 0

    .input-group__append-icon
      padding: 0

    .input-group__selections,
    input
      height: 48px

    // Handle weird transition issue
    .input-group__selections__comma, input
      top: 0
      left: 0
      padding-left: 16px

    .input-group__selections
      width: calc(100% - 55px)

      .btn
        border-radius: 0
        margin: 0
        height: 48px
        width: 100%

        .btn__content
          justify-content: start

    .input-group__input
      transition: .3s $transition.swing

    &.input-group--focused
      .input-group__input
        elevation(8)

    label
      left: 16px !important
      top: 9px !important

    .input-group__details
      &:after
        display: none

    .input-group__input
      padding: 0

      &:before
        content: ''
        position: absolute
        left: 0
        width: 100%
        height: 1px
        top: 0
        transition: .3s $transition.swing

    .input-group__append-icon
      width: 55px

  &--segmented .input-group__input,
  &--editable .input-group__input:hover,
  &.input-group--focused.input-group--editable .input-group__input
    &:after
      background-color: rgba(0,0,0,0.12)
      content: ''
      position: absolute
      right: 55px
      height: 48px
      top: 0
      width: 1px

.menu__content--select
  // // IE11 Fix for overflow
  .input-group--selection-controls__ripple
    display: none

.menu__content--autocomplete
    border-radius: 0

    > .card
      border-radius: 0