// Styles
import './VTabs.sass'

// Components
import { VTab } from './VTab'
import { makeVSlideGroupProps, VSlideGroup } from '@/components/VSlideGroup/VSlideGroup'

// Composables
import { useBackgroundColor } from '@/composables/color'
import { provideDefaults } from '@/composables/defaults'
import { makeDensityProps, useDensity } from '@/composables/density'
import { useProxiedModel } from '@/composables/proxiedModel'
import { makeTagProps } from '@/composables/tag'

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

// Types
import type { PropType } from 'vue'
import { VTabsSymbol } from './shared'

export type TabItem = string | Record<string, any>

function parseItems (items: readonly TabItem[] | undefined) {
  if (!items) return []

  return items.map(item => {
    if (typeof item === 'string') return { title: item, value: item }

    return item
  })
}

export const makeVTabsProps = propsFactory({
  alignTabs: {
    type: String as PropType<'start' | 'title' | 'center' | 'end'>,
    default: 'start',
  },
  color: String,
  fixedTabs: Boolean,
  items: {
    type: Array as PropType<readonly TabItem[]>,
    default: () => ([]),
  },
  stacked: Boolean,
  bgColor: String,
  grow: Boolean,
  height: {
    type: [Number, String],
    default: undefined,
  },
  hideSlider: Boolean,
  sliderColor: String,

  ...makeVSlideGroupProps({ mandatory: 'force' as const }),
  ...makeDensityProps(),
  ...makeTagProps(),
}, 'VTabs')

export const VTabs = genericComponent()({
  name: 'VTabs',

  props: makeVTabsProps(),

  emits: {
    'update:modelValue': (v: unknown) => true,
  },

  setup (props, { slots }) {
    const model = useProxiedModel(props, 'modelValue')
    const parsedItems = computed(() => parseItems(props.items))
    const { densityClasses } = useDensity(props)
    const { backgroundColorClasses, backgroundColorStyles } = useBackgroundColor(toRef(props, 'bgColor'))

    provideDefaults({
      VTab: {
        color: toRef(props, 'color'),
        direction: toRef(props, 'direction'),
        stacked: toRef(props, 'stacked'),
        fixed: toRef(props, 'fixedTabs'),
        sliderColor: toRef(props, 'sliderColor'),
        hideSlider: toRef(props, 'hideSlider'),
      },
    })

    useRender(() => {
      const [slideGroupProps] = VSlideGroup.filterProps(props)

      return (
        <VSlideGroup
          { ...slideGroupProps }
          v-model={ model.value }
          class={[
            'v-tabs',
            `v-tabs--${props.direction}`,
            `v-tabs--align-tabs-${props.alignTabs}`,
            {
              'v-tabs--fixed-tabs': props.fixedTabs,
              'v-tabs--grow': props.grow,
              'v-tabs--stacked': props.stacked,
            },
            densityClasses.value,
            backgroundColorClasses.value,
            props.class,
          ]}
          style={[
            { '--v-tabs-height': convertToUnit(props.height) },
            backgroundColorStyles.value,
            props.style,
          ]}
          role="tablist"
          symbol={ VTabsSymbol }
        >
          { slots.default ? slots.default() : parsedItems.value.map(item => (
            <VTab { ...item } key={ item.title } />
          ))}
        </VSlideGroup>
      )
    })

    return {}
  },
})

export type VTabs = InstanceType<typeof VTabs>


title: route?.meta?.nav ?? route?.meta?.title ?? litem.title,

subtitle: litem.subtitle && te(litem.subtitle) ? t(litem.subtitle) : litem.subtitle,

emphasized: route?.meta?.emphasized ?? false,

keep keep keep keep replace keep keep keep keep keep

<template>
  <v-card
    class="mx-auto"
    max-width="400"
  >
    <v-toolbar
      color="orange-lighten-1"
      dark
    >
      <v-app-bar-nav-icon></v-app-bar-nav-icon>

      <v-toolbar-title>Message Board</v-toolbar-title>

      <v-btn icon>
        <v-icon>mdi-magnify</v-icon>
      </v-btn>
    </v-toolbar>

    <v-list lines="two">
      <template v-for="(item, index) in items">
        <v-list-subheader
          v-if="item.header"
          :key="item.header"
          inset
        >
          {{ item.header }}
        </v-list-subheader>

        <v-divider
          v-else-if="item.divider"
          :key="index"
          inset
        ></v-divider>

        <v-list-item
          v-else
          :key="item.title"
          :prepend-avatar="item.avatar"
          ripple
        >
          <template v-slot:title>

          </template>

          <template v-slot:subtitle>

          </template>
        </v-list-item>
      </template>
    </v-list>
  </v-card>
</template>


const name = route.params.name?.replace('/', '')

const pascalName = name && `${name.charAt(0).toUpperCase()}${camelize(name.slice(1))}`

const model = ref(components.some(v => v.value === name) ? name : pascalName)

const sections = ['props', 'events', 'slots', 'exposed', 'sass', 'options', 'argument', 'modifiers']

keep keep keep keep keep replace keep keep keep

const pascalName = name ? `${name.charAt(0).toUpperCase()}${camelize(name.slice(1))}` : undefined

<template>
  <v-list
    v-model:opened="opened"
    :nav="nav"
    :items="computedItems"
    :lines="false"
    color="primary"
    density="compact"
    item-props
  >
    <template #divider>
      <slot name="divider" />

      <v-divider
        v-if="!$slots.divider"
        class="my-3 mb-4 ms-2 me-n2"
      />
    </template>

    <template #title="{ item }">
      {{ item.title }}

      <v-badge
        v-if="item.emphasized"
        class="ms-n1"
        color="success"
        dot
        inline
      />
    </template>

    <template #subtitle="{ item }">
      <span v-if="item.subtitle" class="text-high-emphasis">
        {{ item.subtitle }}
      </span>
    </template>

    <template #subheader="{ props: subheaderProps }">
      <slot
        name="subheader"
        v-bind="{ subheaderProps }"
      />

      <v-list-subheader
        v-if="!$slots.subheader"
        class="text-high-emphasis text-uppercase font-weight-black"
      >
        {{ subheaderProps.title }}
      </v-list-subheader>
    </template>
  </v-list>
</template>

<script setup lang="ts">
  // Composables
  import { useI18n } from 'vue-i18n'

  // Utiltities
  import { computed, ref } from 'vue'
  import { generatedRoutes as routes } from '@/util/routes'
  import { RouteLocationRaw, RouteRecordRaw } from 'vue-router'

  // Types
  import type { Prop } from 'vue'

  export type Item = {
    title?: string
    subtitle?: string
    appendIcon?: string
    activeIcon?: string
    inactiveIcon?: string
    items?: (string | Item)[]
    subheader?: string
    divider?: boolean
    to?: RouteLocationRaw
    href?: string
    subfolder?: string
    disabled?: boolean
  }

  function generateApiItems (locale: string) {
    return (routes as RouteRecordRaw[])
      .filter(route => route.path.includes(`${locale}/api/`))
      .map(route => {
        return {
          title: (route.meta!.title as string).slice(0, -4),
          to: route.path,
        }
      })
      .sort((a, b) => a.title.localeCompare(b.title))
  }

  function generateListItem (item: string | Item, path = '', locale = 'en', t = (key: string) => key): any {
    const isString = typeof item === 'string'
    const isLink = !isString && (item.to || item.href)
    const isParent = !isString && item.items
    const isType = !isString && (item.divider || item.subheader)

    if (isString || (!isLink && !isParent && !isType)) {
      const litem = isString ? { title: item } : item

      if (litem.subfolder) path = litem.subfolder

      const route = routes.find((route: { path: string }) => route.path.endsWith(`/${locale}/${path}/${litem.title}/`))

      return {
        title: route?.meta?.nav ?? route?.meta?.title ?? litem.title,
        subtitle: litem.subtitle && te(litem.subtitle) ? t(litem.subtitle) : litem.subtitle,
        emphasized: route?.meta?.emphasized ?? false,
        to: route?.path,
        disabled: !route,
      }
    } else if (item.divider) {
      return {
        type: 'divider',
      }
    } else if (item.subheader) {
      return {
        title: t(item.subheader!),
        type: 'subheader',
      }
    } else if (item.items) {
      const p = item.subfolder ? `${item.subfolder}/${item.title}` : path
      return {
        title: t(item.title!),
        children: item.items.map(item => generateListItem(item, p, locale, t)),
      }
    }

    return item
  }

  function generateListItems (item: Item, path: string, locale: string, t: (key: string) => string): any {
    if (!item.items) return undefined

    return item.items.map(child => generateListItem(child, path, locale, t))
  }

  const props = defineProps({
    items: {
      type: Array,
      default: () => ([]),
    } as Prop<Item[]>,
    nav: Boolean,
  })

  const { t, te, locale } = useI18n()
  const opened = ref<string[]>([])

  const computedItems = computed(() => props.items?.map(item => {
    if (item.divider || item.subheader) return generateListItem(item)

    const title = item.title && te(item.title) ? t(item.title) : item.title

    return {
      ...generateListItem({
        title,
        to: item?.to,
        href: item?.href,
      }),
      rel: item.href ? 'noopener noreferrer' : undefined,
      target: item.href ? '_blank' : undefined,
      children: item.title === 'api' ? generateApiItems(locale.value) : generateListItems(item, item.title!, locale.value, t),
      prependIcon: opened.value.includes(title ?? '') ? item.activeIcon : item.inactiveIcon,
      value: title,
      appendIcon: item.appendIcon,
      disabled: item.disabled,
    }
  }))
</script>


// Composables
import { makeDateProps, useDate } from '../date'
import { createVuetify } from '@/framework'

// Utilities
import { describe, expect, it } from '@jest/globals'
import { mount } from '@vue/test-utils'
import { defineComponent, h } from 'vue'
import { VuetifyDateAdapter } from '../adapters/vuetify'

describe('date.ts', () => {
  const Component = defineComponent({
    props: makeDateProps(),

    setup (props) {
      const date = useDate()

      return { date }
    },

    render () {
      return h('div', this.$slots.default?.(this.date))
    },
  })

  const vuetify = createVuetify({
    date: {
      adapter: VuetifyDateAdapter,
    },
  })

  it('should have the correct days in a month', () => {
    const wrapper = mount(Component, {
      global: {
        plugins: [vuetify],
      },
    })

    expect(wrapper).toBeTruthy()
  })
})


<template>
  <v-data-table-server
    v-model:items-per-page="itemsPerPage"
    :headers="headers"
    :items-length="totalItems"
    :items="serverItems"
    :loading="loading"
    :search="search"
    class="elevation-1"
    item-value="name"
    @update:options="loadItems"
  ></v-data-table-server>
</template>

<script setup>
  import { ref } from 'vue'

  const desserts = [
    {
      name: 'Frozen Yogurt',
      calories: 159,
      fat: 6,
      carbs: 24,
      protein: 4,
      iron: '1',
    },
    {
      name: 'Jelly bean',
      calories: 375,
      fat: 0,
      carbs: 94,
      protein: 0,
      iron: '0',
    },
    {
      name: 'KitKat',
      calories: 518,
      fat: 26,
      carbs: 65,
      protein: 7,
      iron: '6',
    },
    {
      name: 'Eclair',
      calories: 262,
      fat: 16,
      carbs: 23,
      protein: 6,
      iron: '7',
    },
    {
      name: 'Gingerbread',
      calories: 356,
      fat: 16,
      carbs: 49,
      protein: 3.9,
      iron: '16',
    },
    {
      name: 'Ice cream sandwich',
      calories: 237,
      fat: 9,
      carbs: 37,
      protein: 4.3,
      iron: '1',
    },
    {
      name: 'Lollipop',
      calories: 392,
      fat: 0.2,
      carbs: 98,
      protein: 0,
      iron: '2',
    },
    {
      name: 'Cupcake',
      calories: 305,
      fat: 3.7,
      carbs: 67,
      protein: 4.3,
      iron: '8',
    },
    {
      name: 'Honeycomb',
      calories: 408,
      fat: 3.2,
      carbs: 87,
      protein: 6.5,
      iron: '45',
    },
    {
      name: 'Donut',
      calories: 452,
      fat: 25,
      carbs: 51,
      protein: 4.9,
      iron: '22',
    },
  ]
  const FakeAPI = {
    async fetch ({ page, itemsPerPage, sortBy }) {
      return new Promise(resolve => {
        setTimeout(() => {
          const start = (page - 1) * itemsPerPage
          const end = start + itemsPerPage
          const items = desserts.slice()
          if (sortBy.length) {
            const sortKey = sortBy[0].key
            const sortOrder = sortBy[0].order
            items.sort((a, b) => {
              const aValue = a[sortKey]
              const bValue = b[sortKey]
              return sortOrder === 'desc' ? bValue - aValue : aValue - bValue
            })
          }
          const paginated = items.slice(start, end)
          resolve({ items: paginated, total: items.length })
        }, 500)
      })
    },
  }
  const itemsPerPage = ref(5)
  const headers = ref([
    {
      title: 'Dessert (100g serving)',
      align: 'start',
      sortable: false,
      key: 'name',
    },
    { title: 'Calories', key: 'calories', align: 'end' },
    { title: 'Fat (g)', key: 'fat', align: 'end' },
    { title: 'Carbs (g)', key: 'carbs', align: 'end' },
    { title: 'Protein (g)', key: 'protein', align: 'end' },
    { title: 'Iron (%)', key: 'iron', align: 'end' },
  ])
  const search = ref('')
  const serverItems = ref([])
  const loading = ref(true)
  const totalItems = ref(0)
  function loadItems ({ page, itemsPerPage, sortBy }) {
    loading.value = true
    FakeAPI.fetch({ page, itemsPerPage, sortBy }).then(({ items, total }) => {
      serverItems.value = items
      totalItems.value = total
      loading.value = false
    })
  }
</script>

<script>
  const desserts = [
    {
      name: 'Frozen Yogurt',
      calories: 159,
      fat: 6.0,
      carbs: 24,
      protein: 4.0,
      iron: '1',
    },
    {
      name: 'Jelly bean',
      calories: 375,
      fat: 0.0,
      carbs: 94,
      protein: 0.0,
      iron: '0',
    },
    {
      name: 'KitKat',
      calories: 518,
      fat: 26.0,
      carbs: 65,
      protein: 7,
      iron: '6',
    },
    {
      name: 'Eclair',
      calories: 262,
      fat: 16.0,
      carbs: 23,
      protein: 6.0,
      iron: '7',
    },
    {
      name: 'Gingerbread',
      calories: 356,
      fat: 16.0,
      carbs: 49,
      protein: 3.9,
      iron: '16',
    },
    {
      name: 'Ice cream sandwich',
      calories: 237,
      fat: 9.0,
      carbs: 37,
      protein: 4.3,
      iron: '1',
    },
    {
      name: 'Lollipop',
      calories: 392,
      fat: 0.2,
      carbs: 98,
      protein: 0,
      iron: '2',
    },
    {
      name: 'Cupcake',
      calories: 305,
      fat: 3.7,
      carbs: 67,
      protein: 4.3,
      iron: '8',
    },
    {
      name: 'Honeycomb',
      calories: 408,
      fat: 3.2,
      carbs: 87,
      protein: 6.5,
      iron: '45',
    },
    {
      name: 'Donut',
      calories: 452,
      fat: 25.0,
      carbs: 51,
      protein: 4.9,
      iron: '22',
    },
  ]

  const FakeAPI = {
    async fetch ({ page, itemsPerPage, sortBy }) {
      return new Promise(resolve => {
        setTimeout(() => {
          const start = (page - 1) * itemsPerPage
          const end = start + itemsPerPage
          const items = desserts.slice()

          if (sortBy.length) {
            const sortKey = sortBy[0].key
            const sortOrder = sortBy[0].order
            items.sort((a, b) => {
              const aValue = a[sortKey]
              const bValue = b[sortKey]
              return sortOrder === 'desc' ? bValue - aValue : aValue - bValue
            })
          }

          const paginated = items.slice(start, end)

          resolve({ items: paginated, total: items.length })
        }, 500)
      })
    },
  }

  export default {
    data: () => ({
      itemsPerPage: 5,
      headers: [
        {
          title: 'Dessert (100g serving)',
          align: 'start',
          sortable: false,
          key: 'name',
        },
        { title: 'Calories', key: 'calories', align: 'end' },
        { title: 'Fat (g)', key: 'fat', align: 'end' },
        { title: 'Carbs (g)', key: 'carbs', align: 'end' },
        { title: 'Protein (g)', key: 'protein', align: 'end' },
        { title: 'Iron (%)', key: 'iron', align: 'end' },
      ],
      search: '',
      serverItems: [],
      loading: true,
      totalItems: 0,
    }),
    methods: {
      loadItems ({ page, itemsPerPage, sortBy }) {
        this.loading = true
        FakeAPI.fetch({ page, itemsPerPage, sortBy }).then(({ items, total }) => {
          this.serverItems = items
          this.totalItems = total
          this.loading = false
        })
      },
    },
  }
</script>


<template>
  <v-sheet class="d-flex bg-surface-variant">
    <v-sheet class="ma-2 pa-2">
      I'm a single element in a flexbox container!
    </v-sheet>
  </v-sheet>
</template>


{ "title": "explorer", "subtitle": "browse-components" },

keep keep keep replace keep keep keep keep


<template>
  <v-time-picker
    v-bind="$attrs"
  ></v-time-picker>
</template>

<script>
  export default {
    name: 'Usage',

    inheritAttrs: false,

    data: () => ({
      defaults: {
        'ampm-in-title': false,
        disabled: false,
        format: 'ampm',
        'full-width': false,
        landscape: false,
        'no-title': false,
        readonly: false,
        scrollable: false,
        'use-seconds': false,
      },
      options: {
        booleans: [
          'ampm-in-title',
          'disabled',
          'full-width',
          'no-title',
          'readonly',
          'use-seconds',
        ],
        btnToggles: {
          format: ['ampm', '24hr'],
        },
      },
      tabs: ['landscape', 'scrollable'],
    }),
  }
</script>


<template>
  <v-data-table
    v-model="selected"
    :headers="headers"
    :items="desserts"
    item-value="name"
    show-select
    class="elevation-1"
  ></v-data-table>
</template>

<script setup>
  import { ref } from 'vue'

  const selected = ref([])

  const headers = [
    {
      title: 'Dessert (100g serving)',
      align: 'start',
      sortable: false,
      key: 'name',
    },
    { title: 'Calories', key: 'calories' },
    { title: 'Fat (g)', key: 'fat' },
    { title: 'Carbs (g)', key: 'carbs' },
    { title: 'Protein (g)', key: 'protein' },
    { title: 'Iron (%)', key: 'iron' },
  ]
  const desserts = [
    {
      name: 'Frozen Yogurt',
      calories: 159,
      fat: 6,
      carbs: 24,
      protein: 4,
      iron: 1,
    },
    {
      name: 'Ice cream sandwich',
      calories: 237,
      fat: 9,
      carbs: 37,
      protein: 4.3,
      iron: 1,
    },
    {
      name: 'Eclair',
      calories: 262,
      fat: 16,
      carbs: 23,
      protein: 6,
      iron: 7,
    },
    {
      name: 'Cupcake',
      calories: 305,
      fat: 3.7,
      carbs: 67,
      protein: 4.3,
      iron: 8,
    },
    {
      name: 'Gingerbread',
      calories: 356,
      fat: 16,
      carbs: 49,
      protein: 3.9,
      iron: 16,
    },
    {
      name: 'Jelly bean',
      calories: 375,
      fat: 0,
      carbs: 94,
      protein: 0,
      iron: 0,
    },
    {
      name: 'Lollipop',
      calories: 392,
      fat: 0.2,
      carbs: 98,
      protein: 0,
      iron: 2,
    },
    {
      name: 'Honeycomb',
      calories: 408,
      fat: 3.2,
      carbs: 87,
      protein: 6.5,
      iron: 45,
    },
    {
      name: 'Donut',
      calories: 452,
      fat: 25,
      carbs: 51,
      protein: 4.9,
      iron: 22,
    },
    {
      name: 'KitKat',
      calories: 518,
      fat: 26,
      carbs: 65,
      protein: 7,
      iron: 6,
    },
  ]
</script>

<script>
  export default {
    data () {
      return {
        selected: [],
        headers: [
          {
            title: 'Dessert (100g serving)',
            align: 'start',
            sortable: false,
            key: 'name',
          },
          { title: 'Calories', key: 'calories' },
          { title: 'Fat (g)', key: 'fat' },
          { title: 'Carbs (g)', key: 'carbs' },
          { title: 'Protein (g)', key: 'protein' },
          { title: 'Iron (%)', key: 'iron' },
        ],
        desserts: [
          {
            name: 'Frozen Yogurt',
            calories: 159,
            fat: 6.0,
            carbs: 24,
            protein: 4.0,
            iron: 1,
          },
          {
            name: 'Ice cream sandwich',
            calories: 237,
            fat: 9.0,
            carbs: 37,
            protein: 4.3,
            iron: 1,
          },
          {
            name: 'Eclair',
            calories: 262,
            fat: 16.0,
            carbs: 23,
            protein: 6.0,
            iron: 7,
          },
          {
            name: 'Cupcake',
            calories: 305,
            fat: 3.7,
            carbs: 67,
            protein: 4.3,
            iron: 8,
          },
          {
            name: 'Gingerbread',
            calories: 356,
            fat: 16.0,
            carbs: 49,
            protein: 3.9,
            iron: 16,
          },
          {
            name: 'Jelly bean',
            calories: 375,
            fat: 0.0,
            carbs: 94,
            protein: 0.0,
            iron: 0,
          },
          {
            name: 'Lollipop',
            calories: 392,
            fat: 0.2,
            carbs: 98,
            protein: 0,
            iron: 2,
          },
          {
            name: 'Honeycomb',
            calories: 408,
            fat: 3.2,
            carbs: 87,
            protein: 6.5,
            iron: 45,
          },
          {
            name: 'Donut',
            calories: 452,
            fat: 25.0,
            carbs: 51,
            protein: 4.9,
            iron: 22,
          },
          {
            name: 'KitKat',
            calories: 518,
            fat: 26.0,
            carbs: 65,
            protein: 7,
            iron: 6,
          },
        ],
      }
    },
  }
</script>


<template>

Active picker: {{ activePicker || 'null' }}

    <v-menu
      ref="menu"
      v-model="menuActive"
      :close-on-content-click="false"
      transition="scale-transition"
      offset-y
      min-width="auto"
    >
      <template v-slot:activator="{ on, attrs }">
        <v-text-field
          v-model="date"
          label="Birthday date"
          prepend-icon="mdi-calendar"
          readonly
          v-bind="attrs"
          v-on="on"
        ></v-text-field>
      </template>
      <v-date-picker
        v-model="date"
        v-model:active-picker="activePicker"
        :max="(new Date(Date.now() - (new Date()).getTimezoneOffset() * 60000)).toISOString().substr(0, 10)"
        min="1950-01-01"
        @change="save"
      ></v-date-picker>
    </v-menu>

</template>


keep keep keep keep keep replace keep keep keep keep

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

import { Meteor } from 'meteor/meteor';
import { EJSON } from 'meteor/ejson';

import { API } from '../../../api/server';
import { logger } from '../lib/logger';
import { contextDefinitions, eventTypes } from '../../../models/server/models/FederationEvents';
import {
	FederationRoomEvents, FederationServers,
	Messages,
	Rooms,
	Subscriptions,
	Users,
} from '../../../models/server';
import { normalizers } from '../normalizers';
import { deleteRoom } from '../../../lib/server/functions';
import { Notifications } from '../../../notifications/server';
import { FileUpload } from '../../../file-upload';
import { getFederationDomain } from '../lib/getFederationDomain';
import { decryptIfNeeded } from '../lib/crypt';
import { isFederationEnabled } from '../lib/isFederationEnabled';
import { getUpload, requestEventsFromLatest } from '../handler';
import { notifyUsersOnMessage } from '../../../lib/server/lib/notifyUsersOnMessage';
import { sendAllNotifications } from '../../../lib/server/lib/sendNotificationsOnMessage';
import { processThreads } from '../../../threads/server/hooks/aftersavemessage';

const eventHandlers = {
	//
	// PING
	//
	async [eventTypes.PING]() {
		return {
			success: true,
		};
	},

	//
	// GENESIS
	//
	async [eventTypes.GENESIS](event) {
		switch (event.data.contextType) {
			case contextDefinitions.ROOM.type:
				const eventResult = await FederationRoomEvents.addEvent(event.context, event);

				// If the event was successfully added, handle the event locally
				if (eventResult.success) {
					const { data: { room } } = event;

					// Check if room exists
					const persistedRoom = Rooms.findOne({ _id: room._id });

					if (persistedRoom) {
						// Update the federation
						Rooms.update({ _id: persistedRoom._id }, { $set: { federation: room.federation } });
					} else {
						// Denormalize room
						const denormalizedRoom = normalizers.denormalizeRoom(room);

						// Create the room
						Rooms.insert(denormalizedRoom);
					}
				}
				return eventResult;
		}
	},

	//
	// ROOM_DELETE
	//
	async [eventTypes.ROOM_DELETE](event) {
		const { data: { roomId } } = event;

		// Check if room exists
		const persistedRoom = Rooms.findOne({ _id: roomId });

		if (persistedRoom) {
			// Delete the room
			deleteRoom(roomId);
		}

		// Remove all room events
		await FederationRoomEvents.removeRoomEvents(roomId);

		return {
			success: true,
		};
	},

	//
	// ROOM_ADD_USER
	//
	async [eventTypes.ROOM_ADD_USER](event) {
		const eventResult = await FederationRoomEvents.addEvent(event.context, event);

		// We only want to refresh the server list and update the room federation array if something changed
		let federationAltered = false;

		// If the event was successfully added, handle the event locally
		if (eventResult.success) {
			const { data: { roomId, user, subscription, domainsAfterAdd } } = event;

			// Check if user exists
			const persistedUser = Users.findOne({ _id: user._id });

			if (persistedUser) {
				// Update the federation, if its not already set (if it's set, this is likely an event being reprocessed)
				if (!persistedUser.federation) {
					Users.update({ _id: persistedUser._id }, { $set: { federation: user.federation } });
					federationAltered = true;
				}
			} else {
				// Denormalize user
				const denormalizedUser = normalizers.denormalizeUser(user);

				// Create the user
				Users.insert(denormalizedUser);
				federationAltered = true;
			}

			// Check if subscription exists
			const persistedSubscription = Subscriptions.findOne({ _id: subscription._id });

			try {
				if (persistedSubscription) {
					// Update the federation, if its not already set (if it's set, this is likely an event being reprocessed
					if (!persistedSubscription.federation) {
						Subscriptions.update({ _id: persistedSubscription._id }, { $set: { federation: subscription.federation } });
						federationAltered = true;
					}
				} else {
					// Denormalize subscription
					const denormalizedSubscription = normalizers.denormalizeSubscription(subscription);

					// Create the subscription
					Subscriptions.insert(denormalizedSubscription);
					federationAltered = true;
				}
			} catch (ex) {
				logger.server.debug(`unable to create subscription for user ( ${ user._id } ) in room (${ roomId })`);
			}

			// Refresh the servers list
			if (federationAltered) {
				FederationServers.refreshServers();

				// Update the room's federation property
				Rooms.update({ _id: roomId }, { $set: { 'federation.domains': domainsAfterAdd } });
			}
		}

		return eventResult;
	},

	//
	// ROOM_REMOVE_USER
	//
	async [eventTypes.ROOM_REMOVE_USER](event) {
		const eventResult = await FederationRoomEvents.addEvent(event.context, event);

		// If the event was successfully added, handle the event locally
		if (eventResult.success) {
			const { data: { roomId, user, domainsAfterRemoval } } = event;

			// Remove the user's subscription
			Subscriptions.removeByRoomIdAndUserId(roomId, user._id);

			// Refresh the servers list
			FederationServers.refreshServers();

			// Update the room's federation property
			Rooms.update({ _id: roomId }, { $set: { 'federation.domains': domainsAfterRemoval } });
		}

		return eventResult;
	},

	//
	// ROOM_USER_LEFT
	//
	async [eventTypes.ROOM_USER_LEFT](event) {
		const eventResult = await FederationRoomEvents.addEvent(event.context, event);

		// If the event was successfully added, handle the event locally
		if (eventResult.success) {
			const { data: { roomId, user, domainsAfterRemoval } } = event;

			// Remove the user's subscription
			Subscriptions.removeByRoomIdAndUserId(roomId, user._id);

			// Refresh the servers list
			FederationServers.refreshServers();

			// Update the room's federation property
			Rooms.update({ _id: roomId }, { $set: { 'federation.domains': domainsAfterRemoval } });
		}

		return eventResult;
	},

	//
	// ROOM_MESSAGE
	//
	async [eventTypes.ROOM_MESSAGE](event) {
		const eventResult = await FederationRoomEvents.addEvent(event.context, event);

		// If the event was successfully added, handle the event locally
		if (eventResult.success) {
			const { data: { message } } = event;

			// Check if message exists
			const persistedMessage = Messages.findOne({ _id: message._id });

			if (persistedMessage) {
				// Update the federation
				if (!persistedMessage.federation) {
					Messages.update({ _id: persistedMessage._id }, { $set: { federation: message.federation } });
				}
			} else {
				// Load the room
				const room = Rooms.findOneById(message.rid);

				// Denormalize message
				const denormalizedMessage = normalizers.denormalizeMessage(message);

				// Is there a file?
				if (denormalizedMessage.file) {
					const fileStore = FileUpload.getStore('Uploads');

					const { federation: { origin } } = denormalizedMessage;

					const { upload, buffer } = getUpload(origin, denormalizedMessage.file._id);

					const oldUploadId = upload._id;

					// Normalize upload
					delete upload._id;
					upload.rid = denormalizedMessage.rid;
					upload.userId = denormalizedMessage.u._id;
					upload.federation = {
						_id: denormalizedMessage.file._id,
						origin,
					};

					Meteor.runAsUser(upload.userId, () => Meteor.wrapAsync(fileStore.insert.bind(fileStore))(upload, buffer));

					// Update the message's file
					denormalizedMessage.file._id = upload._id;

					// Update the message's attachments dependent on type
					for (const attachment of denormalizedMessage.attachments) {
						attachment.title_link = attachment.title_link.replace(oldUploadId, upload._id);
						if (/^image\/.+/.test(denormalizedMessage.file.type)) {
							attachment.image_url = attachment.image_url.replace(oldUploadId, upload._id);
						} else if (/^audio\/.+/.test(denormalizedMessage.file.type)) {
							attachment.audio_url = attachment.audio_url.replace(oldUploadId, upload._id);
						} else if (/^video\/.+/.test(denormalizedMessage.file.type)) {
							attachment.video_url = attachment.video_url.replace(oldUploadId, upload._id);
						}
					}
				}

				// Create the message
				try {
					Messages.insert(denormalizedMessage);

					processThreads(denormalizedMessage, room);

					// Notify users
					notifyUsersOnMessage(denormalizedMessage, room);
					sendAllNotifications(denormalizedMessage, room);
				} catch (err) {
					logger.server.debug(`Error on creating message: ${ message._id }`);
				}
			}
		}

		return eventResult;
	},

	//
	// ROOM_EDIT_MESSAGE
	//
	async [eventTypes.ROOM_EDIT_MESSAGE](event) {
		const eventResult = await FederationRoomEvents.addEvent(event.context, event);

		// If the event was successfully added, handle the event locally
		if (eventResult.success) {
			const { data: { message } } = event;

			// Check if message exists
			const persistedMessage = Messages.findOne({ _id: message._id });

			if (!persistedMessage) {
				eventResult.success = false;
				eventResult.reason = 'missingMessageToEdit';
			} else {
				// Update the message
				Messages.update({ _id: persistedMessage._id }, { $set: { msg: message.msg, federation: message.federation } });
			}
		}

		return eventResult;
	},

	//
	// ROOM_DELETE_MESSAGE
	//
	async [eventTypes.ROOM_DELETE_MESSAGE](event) {
		const eventResult = await FederationRoomEvents.addEvent(event.context, event);

		// If the event was successfully added, handle the event locally
		if (eventResult.success) {
			const { data: { roomId, messageId } } = event;

			// Remove the message
			Messages.removeById(messageId);

			// Notify the room
			Notifications.notifyRoom(roomId, 'deleteMessage', { _id: messageId });
		}

		return eventResult;
	},

	//
	// ROOM_SET_MESSAGE_REACTION
	//
	async [eventTypes.ROOM_SET_MESSAGE_REACTION](event) {
		const eventResult = await FederationRoomEvents.addEvent(event.context, event);

		// If the event was successfully added, handle the event locally
		if (eventResult.success) {
			const { data: { messageId, username, reaction } } = event;

			// Get persisted message
			const persistedMessage = Messages.findOne({ _id: messageId });

			// Make sure reactions exist
			persistedMessage.reactions = persistedMessage.reactions || {};

			let reactionObj = persistedMessage.reactions[reaction];

			// If there are no reactions of that type, add it
			if (!reactionObj) {
				reactionObj = {
					usernames: [username],
				};
			} else {
				// Otherwise, add the username
				reactionObj.usernames.push(username);
				reactionObj.usernames = [...new Set(reactionObj.usernames)];
			}

			// Update the property
			Messages.update({ _id: messageId }, { $set: { [`reactions.${ reaction }`]: reactionObj } });
		}

		return eventResult;
	},

	//
	// ROOM_UNSET_MESSAGE_REACTION
	//
	async [eventTypes.ROOM_UNSET_MESSAGE_REACTION](event) {
		const eventResult = await FederationRoomEvents.addEvent(event.context, event);

		// If the event was successfully added, handle the event locally
		if (eventResult.success) {
			const { data: { messageId, username, reaction } } = event;

			// Get persisted message
			const persistedMessage = Messages.findOne({ _id: messageId });

			// Make sure reactions exist
			persistedMessage.reactions = persistedMessage.reactions || {};

			// If there are no reactions of that type, ignore
			if (!persistedMessage.reactions[reaction]) {
				return eventResult;
			}

			const reactionObj = persistedMessage.reactions[reaction];

			// Get the username index on the list
			const usernameIdx = reactionObj.usernames.indexOf(username);

			// If the index is not found, ignore
			if (usernameIdx === -1) {
				return eventResult;
			}

			// Remove the username from the given reaction
			reactionObj.usernames.splice(usernameIdx, 1);

			// If there are no more users for that reaction, remove the property
			if (reactionObj.usernames.length === 0) {
				Messages.update({ _id: messageId }, { $unset: { [`reactions.${ reaction }`]: 1 } });
			} else {
				// Otherwise, update the property
				Messages.update({ _id: messageId }, { $set: { [`reactions.${ reaction }`]: reactionObj } });
			}
		}

		return eventResult;
	},

	//
	// ROOM_MUTE_USER
	//
	async [eventTypes.ROOM_MUTE_USER](event) {
		const eventResult = await FederationRoomEvents.addEvent(event.context, event);

		// If the event was successfully added, handle the event locally
		if (eventResult.success) {
			const { data: { roomId, user } } = event;

			// Denormalize user
			const denormalizedUser = normalizers.denormalizeUser(user);

			// Mute user
			Rooms.muteUsernameByRoomId(roomId, denormalizedUser.username);
		}

		return eventResult;
	},

	//
	// ROOM_UNMUTE_USER
	//
	async [eventTypes.ROOM_UNMUTE_USER](event) {
		const eventResult = await FederationRoomEvents.addEvent(event.context, event);

		// If the event was successfully added, handle the event locally
		if (eventResult.success) {
			const { data: { roomId, user } } = event;

			// Denormalize user
			const denormalizedUser = normalizers.denormalizeUser(user);

			// Mute user
			Rooms.unmuteUsernameByRoomId(roomId, denormalizedUser.username);
		}

		return eventResult;
	},
};

API.v1.addRoute('federation.events.dispatch', { authRequired: false }, {
	async post() {
		if (!isFederationEnabled()) {
			return API.v1.failure('Federation not enabled');
		}

		//
		// Decrypt the payload if needed
		let payload;

		try {
			payload = decryptIfNeeded(this.request, this.bodyParams);
		} catch (err) {
			return API.v1.failure('Could not decrypt payload');
		}

		//
		// Convert from EJSON
		const { events } = EJSON.fromJSONValue(payload);

		logger.server.debug(`federation.events.dispatch => events=${ events.map((e) => JSON.stringify(e, null, 2)) }`);

		// Loop over received events
		for (const event of events) {
			/* eslint-disable no-await-in-loop */

			let eventResult;

			if (eventHandlers[event.type]) {
				eventResult = await eventHandlers[event.type](event);
			}

			// If there was an error handling the event, take action
			if (!eventResult || !eventResult.success) {
				try {
					logger.server.debug(`federation.events.dispatch => Event has missing parents -> event=${ JSON.stringify(event, null, 2) }`);

					requestEventsFromLatest(event.origin, getFederationDomain(), contextDefinitions.defineType(event), event.context, eventResult.latestEventIds);

					// And stop handling the events
					break;
				} catch (err) {
					logger.server.error(() => `dispatch => event=${ JSON.stringify(event, null, 2) } eventResult=${ JSON.stringify(eventResult, null, 2) } error=${ err.toString() } ${ err.stack }`);

					throw err;
				}
			}

			/* eslint-enable no-await-in-loop */
		}

		// Respond
		return API.v1.success();
	},
});


import React, { useMemo } from 'react';
import { Box, Field, TextInput, Select, ToggleSwitch } from '@rocket.chat/fuselage';

import { useTranslation } from '../../contexts/TranslationContext';

const RoleForm = ({ values, handlers, className, editing = false, isProtected = false }) => {
	const t = useTranslation();

	const {
		name,
		description,
		scope,
		mandatory2fa,
	} = values;

	const {
		handleName,
		handleDescription,
		handleScope,
		handleMandatory2fa,
	} = handlers;

	const options = useMemo(() => [
		['Users', t('Global')],
		['Subscriptions', t('Rooms')],
	], [t]);

	return <>
		<Field className={className}>
			<Field.Label>{t('Role')}</Field.Label>
			<Field.Row>
				<TextInput disabled={editing} value={name} onChange={handleName} placeholder={t('Role')}/>
			</Field.Row>
		</Field>
		<Field className={className}>
			<Field.Label>{t('Description')}</Field.Label>
			<Field.Row>
				<TextInput value={description} onChange={handleDescription} placeholder={t('Description')}/>
			</Field.Row>
			<Field.Hint>{('Leave the description field blank if you dont want to show the role')}</Field.Hint>
		</Field>
		<Field className={className}>
			<Field.Label>{t('Scope')}</Field.Label>
			<Field.Row>
				<Select disabled={isProtected} options={options} value={scope} onChange={handleScope} placeholder={t('Scope')}/>
			</Field.Row>
		</Field>
		<Field className={className}>
			<Box display='flex' flexDirection='row'>
				<Field.Label>{t('Users must use Two Factor Authentication')}</Field.Label>
				<Field.Row>
					<ToggleSwitch checked={mandatory2fa} onChange={handleMandatory2fa}/>
				</Field.Row>
			</Box>
		</Field>
	</>;
};

export default RoleForm;


import React from 'react';

import CounterRow from './CounterRow';
import CounterItem from './CounterItem';

export default {
	title: 'omnichannel/RealtimeMonitoring/Counter',
	component: CounterRow,
};

export const Default = () => <CounterRow>
	<CounterItem title='total conversations' count={10}/>
	<CounterItem title='open conversations' count={10}/>
	<CounterItem title='total messages' count={10}/>
	<CounterItem title='total visitors'/>
</CounterRow>;


export class AppDetailChangesBridge {
	constructor(orch) {
		this.orch = orch;
	}

	onAppSettingsChange(appId, setting) {
		try {
			this.orch.getNotifier().appSettingsChange(appId, setting);
		} catch (e) {
			console.warn('failed to notify about the setting change.', appId);
		}
	}
}


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

import qs from 'querystring';

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

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

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

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

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

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

	return users;
}

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

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

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

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

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

	return user;
}

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

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

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

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

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

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

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

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

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

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

	dispatchEvents(distinctDomains, [event]);
}

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

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


import './settings';
import './methods/deleteOldOTRMessages';
import './methods/updateOTRAck';


//! moment.js locale configuration
//! locale : Chuvash [cv]
//! author : Anatoly Mironov : https://github.com/mirontoli

;(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 cv = moment.defineLocale('cv', {
        months : 'кӑрлач_нарӑс_пуш_ака_май_ҫӗртме_утӑ_ҫурла_авӑн_юпа_чӳк_раштав'.split('_'),
        monthsShort : 'кӑр_нар_пуш_ака_май_ҫӗр_утӑ_ҫур_авн_юпа_чӳк_раш'.split('_'),
        weekdays : 'вырсарникун_тунтикун_ытларикун_юнкун_кӗҫнерникун_эрнекун_шӑматкун'.split('_'),
        weekdaysShort : 'выр_тун_ытл_юн_кӗҫ_эрн_шӑм'.split('_'),
        weekdaysMin : 'вр_тн_ыт_юн_кҫ_эр_шм'.split('_'),
        longDateFormat : {
            LT : 'HH:mm',
            LTS : 'HH:mm:ss',
            L : 'DD-MM-YYYY',
            LL : 'YYYY [ҫулхи] MMMM [уйӑхӗн] D[-мӗшӗ]',
            LLL : 'YYYY [ҫулхи] MMMM [уйӑхӗн] D[-мӗшӗ], HH:mm',
            LLLL : 'dddd, YYYY [ҫулхи] MMMM [уйӑхӗн] D[-мӗшӗ], HH:mm'
        },
        calendar : {
            sameDay: '[Паян] LT [сехетре]',
            nextDay: '[Ыран] LT [сехетре]',
            lastDay: '[Ӗнер] LT [сехетре]',
            nextWeek: '[Ҫитес] dddd LT [сехетре]',
            lastWeek: '[Иртнӗ] dddd LT [сехетре]',
            sameElse: 'L'
        },
        relativeTime : {
            future : function (output) {
                var affix = /сехет$/i.exec(output) ? 'рен' : /ҫул$/i.exec(output) ? 'тан' : 'ран';
                return output + affix;
            },
            past : '%s каялла',
            s : 'пӗр-ик ҫеккунт',
            m : 'пӗр минут',
            mm : '%d минут',
            h : 'пӗр сехет',
            hh : '%d сехет',
            d : 'пӗр кун',
            dd : '%d кун',
            M : 'пӗр уйӑх',
            MM : '%d уйӑх',
            y : 'пӗр ҫул',
            yy : '%d ҫул'
        },
        ordinalParse: /\d{1,2}-мӗш/,
        ordinal : '%d-мӗш',
        week : {
            dow : 1, // Monday is the first day of the week.
            doy : 7  // The week that contains Jan 1st is the first week of the year.
        }
    });

    return cv;

}));


import { Meteor } from 'meteor/meteor';
import { DDPCommon } from 'meteor/ddp-common';

const changedPayload = function(collection, id, fields) {
	return DDPCommon.stringifyDDP({
		msg: 'changed',
		collection,
		id,
		fields,
	});
};

const send = function(self, msg) {
	if (!self.socket) {
		return;
	}
	self.socket.send(msg);
};

class MessageStream extends Meteor.Streamer {
	getSubscriptionByUserIdAndRoomId(userId, rid) {
		return this.subscriptions.find((sub) => sub.eventName === rid && sub.subscription.userId === userId);
	}

	_publish(publication, eventName, options) {
		super._publish(publication, eventName, options);
		const uid = Meteor.userId();

		const userEvent = (clientAction, { rid }) => {
			switch (clientAction) {
				case 'removed':
					this.removeListener(uid, userEvent);
					this.removeSubscription(this.getSubscriptionByUserIdAndRoomId(uid, rid), eventName);
					break;
			}
		};
		this.on(uid, userEvent);
	}

	mymessage = (eventName, args) => {
		const subscriptions = this.subscriptionsByEventName[eventName];
		if (!Array.isArray(subscriptions)) {
			return;
		}
		subscriptions.forEach(({ subscription }) => {
			const options = this.isEmitAllowed(subscription, eventName, args);
			if (options) {
				send(subscription._session, changedPayload(this.subscriptionName, 'id', {
					eventName,
					args: [args, options],
				}));
			}
		});
	}
}

export const msgStream = new MessageStream('room-messages');


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

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

const callbackDefinitions = [];

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

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

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


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

import TriggersForm from './TriggersForm';
import { useTranslation } from '../../contexts/TranslationContext';
import { useMethod } from '../../contexts/ServerContext';
import { useForm } from '../../hooks/useForm';
import { useRoute } from '../../contexts/RouterContext';
import { useToastMessageDispatch } from '../../contexts/ToastMessagesContext';

const NewTriggerPage = ({ onSave }) => {
	const dispatchToastMessage = useToastMessageDispatch();
	const t = useTranslation();

	const router = useRoute('omnichannel-triggers');

	const save = useMethod('livechat:saveTrigger');

	const { values, handlers } = useForm({
		name: '',
		description: '',
		enabled: true,
		runOnce: false,
		conditions: {
			name: 'page-url',
			value: '',
		},
		actions: {
			name: '',
			params: {
				sender: 'queue',
				msg: '',
				name: '',
			},
		},
	});

	const handleSave = useMutableCallback(async () => {
		try {
			const { actions: { params: { sender, msg, name } }, ...restValues } = values;
			await save({
				...restValues,
				conditions: [values.conditions],
				actions: [{
					name: 'send-message',
					params: {
						sender,
						msg,
						...sender === 'custom' && { name },
					},
				}],
			});
			dispatchToastMessage({ type: 'success', message: t('Saved') });
			onSave();
			router.push({});
		} catch (error) {
			dispatchToastMessage({ type: 'error', message: error });
		}
	});

	return 	<>
		<FieldGroup>
			<TriggersForm values={values} handlers={handlers}/>
		</FieldGroup>
		<ButtonGroup align='end'>
			<Button primary onClick={handleSave}>
				{t('Save')}
			</Button>
		</ButtonGroup>
	</>;
};

export default NewTriggerPage;


//! moment.js locale configuration
//! locale : Basque [eu]
//! author : Eneko Illarramendi : https://github.com/eillarra

;(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 eu = moment.defineLocale('eu', {
        months : 'urtarrila_otsaila_martxoa_apirila_maiatza_ekaina_uztaila_abuztua_iraila_urria_azaroa_abendua'.split('_'),
        monthsShort : 'urt._ots._mar._api._mai._eka._uzt._abu._ira._urr._aza._abe.'.split('_'),
        monthsParseExact : true,
        weekdays : 'igandea_astelehena_asteartea_asteazkena_osteguna_ostirala_larunbata'.split('_'),
        weekdaysShort : 'ig._al._ar._az._og._ol._lr.'.split('_'),
        weekdaysMin : 'ig_al_ar_az_og_ol_lr'.split('_'),
        weekdaysParseExact : true,
        longDateFormat : {
            LT : 'HH:mm',
            LTS : 'HH:mm:ss',
            L : 'YYYY-MM-DD',
            LL : 'YYYY[ko] MMMM[ren] D[a]',
            LLL : 'YYYY[ko] MMMM[ren] D[a] HH:mm',
            LLLL : 'dddd, YYYY[ko] MMMM[ren] D[a] HH:mm',
            l : 'YYYY-M-D',
            ll : 'YYYY[ko] MMM D[a]',
            lll : 'YYYY[ko] MMM D[a] HH:mm',
            llll : 'ddd, YYYY[ko] MMM D[a] HH:mm'
        },
        calendar : {
            sameDay : '[gaur] LT[etan]',
            nextDay : '[bihar] LT[etan]',
            nextWeek : 'dddd LT[etan]',
            lastDay : '[atzo] LT[etan]',
            lastWeek : '[aurreko] dddd LT[etan]',
            sameElse : 'L'
        },
        relativeTime : {
            future : '%s barru',
            past : 'duela %s',
            s : 'segundo batzuk',
            m : 'minutu bat',
            mm : '%d minutu',
            h : 'ordu bat',
            hh : '%d ordu',
            d : 'egun bat',
            dd : '%d egun',
            M : 'hilabete bat',
            MM : '%d hilabete',
            y : 'urte bat',
            yy : '%d urte'
        },
        ordinalParse: /\d{1,2}\./,
        ordinal : '%d.',
        week : {
            dow : 1, // Monday is the first day of the week.
            doy : 7  // The week that contains Jan 1st is the first week of the year.
        }
    });

    return eu;

}));


export function registerCallback(callbackDefition) {

keep keep keep replace replace keep keep keep

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

export function registerCallback(callbackDefinition) {


import { LivechatDepartmentAgents } from '../../../../models/server';
import { Notifications } from '../../../../notifications';

const fields = { agentId: 1, departmentId: 1 };

const emitNotification = (action, payload = {}) => {
	const { agentId = null } = payload;
	if (!agentId) {
		return;
	}

	Notifications.notifyUserInThisInstance(agentId, 'departmentAgentData', {
		action,
		...payload,
	});
};

LivechatDepartmentAgents.on('change', ({ clientAction, id }) => {
	switch (clientAction) {
		case 'inserted':
		case 'updated':
			emitNotification(clientAction, LivechatDepartmentAgents.findOneById(id, { fields }));
			break;

		case 'removed':
			emitNotification(clientAction, LivechatDepartmentAgents.trashFindOneById(id, { fields }));
			break;
	}
});


import { Meteor } from 'meteor/meteor';
import { check } from 'meteor/check';

import { hasPermission } from '../../app/authorization';
import { Users, Subscriptions, Messages } from '../../app/models';
import { settings } from '../../app/settings';
import { Notifications } from '../../app/notifications';

Meteor.methods({
	removeRoomLeader(rid, userId) {
		check(rid, String);
		check(userId, String);

		if (!Meteor.userId()) {
			throw new Meteor.Error('error-invalid-user', 'Invalid user', {
				method: 'removeRoomLeader',
			});
		}

		if (!hasPermission(Meteor.userId(), 'set-leader', rid)) {
			throw new Meteor.Error('error-not-allowed', 'Not allowed', {
				method: 'removeRoomLeader',
			});
		}

		const user = Users.findOneById(userId);

		if (!user || !user.username) {
			throw new Meteor.Error('error-invalid-user', 'Invalid user', {
				method: 'removeRoomLeader',
			});
		}

		const subscription = Subscriptions.findOneByRoomIdAndUserId(rid, user._id);

		if (!subscription) {
			throw new Meteor.Error('error-user-not-in-room', 'User is not in this room', {
				method: 'removeRoomLeader',
			});
		}

		if (Array.isArray(subscription.roles) === true && subscription.roles.includes('leader') === false) {
			throw new Meteor.Error('error-user-not-leader', 'User is not a leader', {
				method: 'removeRoomLeader',
			});
		}

		Subscriptions.removeRoleById(subscription._id, 'leader');

		const fromUser = Users.findOneById(Meteor.userId());

		Messages.createSubscriptionRoleRemovedWithRoomIdAndUser(rid, user, {
			u: {
				_id: fromUser._id,
				username: fromUser.username,
			},
			role: 'leader',
		});

		if (settings.get('UI_DisplayRoles')) {
			Notifications.notifyLogged('roles-change', {
				type: 'removed',
				_id: 'leader',
				u: {
					_id: user._id,
					username: user.username,
					name: user.name,
				},
				scope: rid,
			});
		}

		return true;
	},
});


for (const definition of callbackDefinitions) {

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

keep keep keep keep replace keep keep keep keep


import _ from 'underscore';

import { CannedResponse } from '../../collections/CannedResponse';
import { addMessagePopup } from '../../../../../../app/ui-message/client/popup/customMessagePopups';
import { t } from '../../../../../../app/utils';

addMessagePopup((template) => ({
	title: t('Canned Responses'),
	collection: CannedResponse,
	trigger: '!',
	prefix: '',
	suffix: ' ',
	triggerAnywhere: true,
	template: 'messagePopupCannedResponse',
	rid: template.data.rid,
	getInput: template.data.getInput,
	textFilterDelay: 500,
	getFilter: (collection, filter) => {
		const exp = new RegExp(filter, 'i');
		const records = collection.find({
			shortcut: exp,
		}, {
			reactive: 1,
			limit: 12,
			sort: {
				shortcut: -1,
			},
		}).fetch();

		return records;
	},
	getValue: (_id, collection, records) => {
		const record = _.findWhere(records, {
			_id,
		});
		return record && record.text;
	},
}));


import React, { useMemo } from 'react';
import { Box, Table } from '@rocket.chat/fuselage';

import UserAvatar from '../../../client/components/basic/avatar/UserAvatar';
import { GenericTable } from '../../../client/components/GenericTable';
import { useTranslation } from '../../../client/contexts/TranslationContext';
import { useFormatDateAndTime } from '../../../client/hooks/useFormatDateAndTime';
import { useFormatDate } from '../../../client/hooks/useFormatDate';

const FilterDisplay = ({ users, room, startDate, endDate, t }) => <Box display='flex' flexDirection='column' alignItems='stretch' withTruncatedText>
	<Box withTruncatedText>
		{users ? `@${ users[0] } : @${ users[1] }` : `#${ room }`}
	</Box>
	<Box withTruncatedText>
		{startDate} {t('to')} {endDate}
	</Box>
</Box>;

const UserRow = React.memo(({ u, results, ts, _id, formatDateAndTime, formatDate, fields, mediaQuery }) => {
	const t = useTranslation();

	const {
		username,
		name,
		avatarETag,
	} = u;

	const {
		msg,
		users,
		room,
		startDate,
		endDate,
	} = fields;

	const when = useMemo(() => formatDateAndTime(ts), [formatDateAndTime, ts]);

	return <Table.Row key={_id} tabIndex={0} role='link'>
		<Table.Cell withTruncatedText>
			<Box display='flex' alignItems='center'>
				<UserAvatar size={mediaQuery ? 'x28' : 'x40'} title={username} username={username} etag={avatarETag}/>
				<Box display='flex' withTruncatedText mi='x8'>
					<Box display='flex' flexDirection='column' alignSelf='center' withTruncatedText>
						<Box fontScale='p2' withTruncatedText color='default'>{name || username}</Box>
						{name && <Box fontScale='p1' color='hint' withTruncatedText> {`@${ username }`} </Box>}
					</Box>
				</Box>
			</Box>
		</Table.Cell>
		<Table.Cell>
			<Box fontScale='p2' withTruncatedText color='hint'>{ msg }</Box> <Box mi='x4'/>
		</Table.Cell>
		<Table.Cell withTruncatedText>{when}</Table.Cell>
		<Table.Cell withTruncatedText>{results}</Table.Cell>
		<Table.Cell fontScale='p1' color='hint' withTruncatedText>
			<FilterDisplay t={t} users={users} room={room} startDate={formatDate(startDate)} endDate={formatDate(endDate)}/>
		</Table.Cell>
	</Table.Row>;
});

export function AuditLogTable({ data }) {
	const t = useTranslation();

	const formatDateAndTime = useFormatDateAndTime();
	const formatDate = useFormatDate();

	return <GenericTable
		header={<>
			<GenericTable.HeaderCell >
				{t('Username')}
			</GenericTable.HeaderCell>
			<GenericTable.HeaderCell >
				{t('Looked_for')}
			</GenericTable.HeaderCell>
			<GenericTable.HeaderCell>
				{t('When')}
			</GenericTable.HeaderCell>
			<GenericTable.HeaderCell w='x80'>
				{t('Results')}
			</GenericTable.HeaderCell>
			<GenericTable.HeaderCell >
				{t('Filters_applied')}
			</GenericTable.HeaderCell>
		</>}
		results={data}
	>
		{(props) => <UserRow key={props._id} formatDateAndTime={formatDateAndTime} formatDate={formatDate} {...props}/>}
	</GenericTable>;
}

export default AuditLogTable;


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

Migrations.add({
	version: 66,
	up() {
		if (Settings) {
			// New color settings - start with old settings as defaults
			const replace1 = Settings.findOne({ _id: 'theme-color-tertiary-background-color' });
			if (replace1) {
				Settings.upsert({ _id: 'theme-color-component-color' }, { $set: { value: replace1.value } });
			}
		}
	},
});


import { EventEmitter } from 'events';

import type WebSocket from 'ws';
import ejson from 'ejson';
import { v1 as uuidv1 } from 'uuid';
import { MeteorService, isMeteorError, MeteorError } from '@rocket.chat/core-services';

import { DDP_EVENTS } from './constants';
import { Publication } from './Publication';
import type { Client } from './Client';
import type { IPacket } from './types/IPacket';
import { Logger } from '../../../../apps/meteor/server/lib/logger/Logger';

const logger = new Logger('DDP-Streamer');

type SubscriptionFn = (this: Publication, eventName: string, options: object) => void;
type MethodFn = (this: Client, ...args: any[]) => any;
type Methods = {
	[k: string]: MethodFn;
};

const handleInternalException = (err: unknown, msg: string): MeteorError => {
	if (err instanceof MeteorError) {
		return err;
	}

	// default errors are logged to the console and redacted from the client
	// TODO switch to using the logger (ideally broker.logger)
	logger.error({ msg, err });

	return new MeteorError(500, 'Internal server error');
};

export const SERVER_ID = ejson.stringify({ server_id: '0' });

export class Server extends EventEmitter {
	private _subscriptions = new Map<string, SubscriptionFn>();

	private _methods = new Map<string, MethodFn>();

	public readonly id = uuidv1();

	serialize = ejson.stringify;

	parse = (data: WebSocket.Data, isBinary: boolean): IPacket => {
		if (isBinary) {
			throw new MeteorError(500, 'Binary data not supported');
		}
		const packet = data.toString();

		const payload = packet.startsWith('[') ? JSON.parse(packet)[0] : packet;
		return ejson.parse(payload);
	};

	async call(client: Client, packet: IPacket): Promise<void> {
		try {
			// if method was not defined on DDP Streamer we fall back to Meteor
			if (!this._methods.has(packet.method)) {
				const result = await MeteorService.callMethodWithToken(client.userId, client.userToken, packet.method, packet.params);
				if (result?.result) {
					return this.result(client, packet, result.result);
				}

				throw new MeteorError(404, `Method '${packet.method}' not found`);
			}

			const fn = this._methods.get(packet.method);
			if (!fn) {
				throw new MeteorError(404, `Method '${packet.method}' not found`);
			}

			const result = await fn.apply(client, packet.params);
			return this.result(client, packet, result);
		} catch (err: unknown) {
			return this.result(client, packet, null, handleInternalException(err, 'Method call error'));
		}
	}

	methods(obj: Methods): void {
		Object.entries(obj).forEach(([name, fn]) => {
			if (this._methods.has(name)) {
				return;
			}
			this._methods.set(name, fn);
		});
	}

	async subscribe(client: Client, packet: IPacket): Promise<void> {
		try {
			if (!this._subscriptions.has(packet.name)) {
				throw new MeteorError(404, `Subscription '${packet.name}' not found`);
			}
			const fn = this._subscriptions.get(packet.name);
			if (!fn) {
				throw new MeteorError(404, `Subscription '${packet.name}' not found`);
			}

			const publication = new Publication(client, packet, this);
			const [eventName, options] = packet.params;
			await fn.call(publication, eventName, options);
		} catch (err: unknown) {
			return this.nosub(client, packet, handleInternalException(err, 'Subscription error'));
		}
	}

	publish(name: string, fn: SubscriptionFn): void {
		if (this._subscriptions.has(name)) {
			return;
		}
		this._subscriptions.set(name, fn);
	}

	stream(stream: string, fn: SubscriptionFn): void {
		return this.publish(`stream-${stream}`, fn);
	}

	result(client: Client, { id }: IPacket, result?: any, error?: Error | MeteorError): void {
		client.send(
			this.serialize({
				[DDP_EVENTS.MSG]: DDP_EVENTS.RESULT,
				id,
				...(result && { result }),
				...(error && { error: isMeteorError(error) ? error.toJSON() : error }),
			}),
		);
		return client.send(
			this.serialize({
				[DDP_EVENTS.MSG]: DDP_EVENTS.UPDATED,
				[DDP_EVENTS.METHODS]: [id],
			}),
		);
	}

	nosub(client: Client, { id }: IPacket, error?: Error | MeteorError): void {
		return client.send(
			this.serialize({
				[DDP_EVENTS.MSG]: DDP_EVENTS.NO_SUBSCRIBE,
				id,
				...(error && { error: isMeteorError(error) ? error.toJSON() : error }),
			}),
		);
	}

	ready(client: Client, packet: IPacket): void {
		return client.send(
			this.serialize({
				[DDP_EVENTS.MSG]: DDP_EVENTS.READY,
				[DDP_EVENTS.SUBSCRIPTIONS]: [packet.id],
			}),
		);
	}

	added(client: Client, collection: string, id: string, fields: any): void {
		return client.send(
			this.serialize({
				[DDP_EVENTS.MSG]: DDP_EVENTS.ADDED,
				[DDP_EVENTS.COLLECTION]: collection,
				[DDP_EVENTS.ID]: id,
				[DDP_EVENTS.FIELDS]: fields,
			}),
		);
	}

	changed(client: Client, collection: string, id: string, fields: any): void {
		return client.send(
			this.serialize({
				[DDP_EVENTS.MSG]: DDP_EVENTS.CHANGED,
				[DDP_EVENTS.COLLECTION]: collection,
				[DDP_EVENTS.ID]: id,
				[DDP_EVENTS.FIELDS]: fields,
			}),
		);
	}

	removed(client: Client, collection: string, id: string): void {
		return client.send(
			this.serialize({
				[DDP_EVENTS.MSG]: DDP_EVENTS.REMOVED,
				[DDP_EVENTS.COLLECTION]: collection,
				[DDP_EVENTS.ID]: id,
			}),
		);
	}
}


import type { IFederationBridge } from '../domain/IFederationBridge';
import type { RocketChatFileAdapter } from '../infrastructure/rocket-chat/adapters/File';
import type { RocketChatNotificationAdapter } from '../infrastructure/rocket-chat/adapters/Notification';
import type { RocketChatRoomAdapter } from '../infrastructure/rocket-chat/adapters/Room';
import type { RocketChatSettingsAdapter } from '../infrastructure/rocket-chat/adapters/Settings';
import type { RocketChatUserAdapter } from '../infrastructure/rocket-chat/adapters/User';
import { FederationService } from './AbstractFederationService';
import type { FederationUserTypingStatusEventDto } from './input/UserReceiverDto';

export class FederationUserServiceListener extends FederationService {
	private usersTypingByRoomIdCache: Map<string, Record<string, string>[]> = new Map();

	constructor(
		protected internalRoomAdapter: RocketChatRoomAdapter,
		protected internalUserAdapter: RocketChatUserAdapter,
		protected internalFileAdapter: RocketChatFileAdapter,
		protected internalNotificationAdapter: RocketChatNotificationAdapter,
		protected internalSettingsAdapter: RocketChatSettingsAdapter,
		protected bridge: IFederationBridge,
	) {
		super(bridge, internalUserAdapter, internalFileAdapter, internalSettingsAdapter);
	}

	private handleUsersWhoStoppedTyping(externalRoomId: string, internalRoomId: string, externalUserIdsTyping: string[]): void {
		const isTyping = false;
		const notTypingAnymore = this.usersTypingByRoomIdCache
			.get(externalRoomId)
			?.filter((user) => !externalUserIdsTyping.includes(user.externalUserId));

		const stillTyping = this.usersTypingByRoomIdCache
			.get(externalRoomId)
			?.filter((user) => externalUserIdsTyping.includes(user.externalUserId));

		notTypingAnymore?.forEach((user) => this.internalNotificationAdapter.notifyUserTypingOnRoom(internalRoomId, user.username, isTyping));
		this.usersTypingByRoomIdCache.set(externalRoomId, stillTyping || []);
	}

	public async onUserTyping(userTypingInput: FederationUserTypingStatusEventDto): Promise<void> {
		const { externalUserIdsTyping, externalRoomId } = userTypingInput;
		const federatedRoom = await this.internalRoomAdapter.getFederatedRoomByExternalId(externalRoomId);
		if (!federatedRoom) {
			return;
		}
		if (this.usersTypingByRoomIdCache.has(externalRoomId)) {
			this.handleUsersWhoStoppedTyping(externalRoomId, federatedRoom.getInternalId(), externalUserIdsTyping);
		}

		if (externalUserIdsTyping.length === 0) {
			return;
		}

		const federatedUsers = await this.internalUserAdapter.getFederatedUsersByExternalIds(externalUserIdsTyping);
		if (federatedUsers.length === 0) {
			return;
		}

		const isTyping = true;

		this.usersTypingByRoomIdCache.set(
			externalRoomId,
			federatedUsers.map((federatedUser) => {
				this.internalNotificationAdapter.notifyUserTypingOnRoom(
					federatedRoom.getInternalId(),
					federatedUser.getUsername() as string,
					isTyping,
				);

				return {
					externalUserId: federatedUser.getInternalId(),
					username: federatedUser.getUsername() as string,
				};
			}),
		);
	}
}


import type { IUser } from '@rocket.chat/core-typings';
import { Accounts } from 'meteor/accounts-base';
import { FlowRouter } from 'meteor/kadira:flow-router';
import { Meteor } from 'meteor/meteor';
import { TAPi18n } from 'meteor/rocketchat:tap-i18n';
import { Tracker } from 'meteor/tracker';
import React, { lazy } from 'react';

import { KonchatNotification } from '../../app/ui/client';
import { APIClient } from '../../app/utils/client';
import { appLayout } from '../lib/appLayout';
import { dispatchToastMessage } from '../lib/toast';
import BlazeTemplate from '../views/root/BlazeTemplate';
import MainLayout from '../views/root/MainLayout';

const PageLoading = lazy(() => import('../views/root/PageLoading'));
const HomePage = lazy(() => import('../views/home/HomePage'));
const InvitePage = lazy(() => import('../views/invite/InvitePage'));
const SecretURLPage = lazy(() => import('../views/invite/SecretURLPage'));
const CMSPage = lazy(() => import('@rocket.chat/web-ui-registration').then(({ CMSPage }) => ({ default: CMSPage })));
const ResetPasswordPage = lazy(() =>
	import('@rocket.chat/web-ui-registration').then(({ ResetPasswordPage }) => ({ default: ResetPasswordPage })),
);

const MailerUnsubscriptionPage = lazy(() => import('../views/mailer/MailerUnsubscriptionPage'));
const SetupWizardRoute = lazy(() => import('../views/setupWizard/SetupWizardRoute'));
const NotFoundPage = lazy(() => import('../views/notFound/NotFoundPage'));
const MeetPage = lazy(() => import('../views/meet/MeetPage'));

const DirectoryPage = lazy(() => import('../views/directory'));
const OmnichannelDirectoryPage = lazy(() => import('../views/omnichannel/directory/OmnichannelDirectoryPage'));
const OmnichannelQueueList = lazy(() => import('../views/omnichannel/queueList'));

FlowRouter.wait();

FlowRouter.route('/', {
	name: 'index',
	action() {
		appLayout.render(
			<MainLayout>
				<PageLoading />
			</MainLayout>,
		);

		if (!Meteor.userId()) {
			return FlowRouter.go('home');
		}

		Tracker.autorun((c) => {
			if (FlowRouter.subsReady() === true) {
				Meteor.defer(() => {
					const user = Meteor.user() as IUser | null;
					if (user?.defaultRoom) {
						const room = user.defaultRoom.split('/');
						FlowRouter.go(room[0], { name: room[1] }, FlowRouter.current().queryParams);
					} else {
						FlowRouter.go('home');
					}
				});
				c.stop();
			}
		});
	},
});

FlowRouter.route('/login', {
	name: 'login',

	action() {
		FlowRouter.go('home');
	},
});

FlowRouter.route('/meet/:rid', {
	name: 'meet',

	async action(_params, queryParams) {
		if (queryParams?.token !== undefined) {
			// visitor login
			const result = await APIClient.get(`/v1/livechat/visitor/${queryParams.token}`);
			if ('visitor' in result) {
				appLayout.render(<MeetPage />);
				return;
			}

			dispatchToastMessage({ type: 'error', message: TAPi18n.__('Visitor_does_not_exist') });
			return;
		}

		if (!Meteor.userId()) {
			FlowRouter.go('home');
			return;
		}

		appLayout.render(<MeetPage />);
	},
});

FlowRouter.route('/home', {
	name: 'home',

	action(_params, queryParams) {
		KonchatNotification.getDesktopPermission();
		if (queryParams?.saml_idp_credentialToken !== undefined) {
			const token = queryParams.saml_idp_credentialToken;
			FlowRouter.setQueryParams({
				saml_idp_credentialToken: null,
			});
			(Meteor as any).loginWithSamlToken(token, (error?: unknown) => {
				if (error) {
					dispatchToastMessage({ type: 'error', message: error });
				}

				appLayout.render(
					<MainLayout>
						<HomePage />
					</MainLayout>,
				);
			});

			return;
		}

		appLayout.render(
			<MainLayout>
				<HomePage />
			</MainLayout>,
		);
	},
});

FlowRouter.route('/directory/:tab?', {
	name: 'directory',
	action: () => {
		appLayout.render(
			<MainLayout>
				<DirectoryPage />
			</MainLayout>,
		);
	},
});

FlowRouter.route('/omnichannel-directory/:page?/:bar?/:id?/:tab?/:context?', {
	name: 'omnichannel-directory',
	action: () => {
		appLayout.render(
			<MainLayout>
				<OmnichannelDirectoryPage />
			</MainLayout>,
		);
	},
});

FlowRouter.route('/livechat-queue', {
	name: 'livechat-queue',
	action: () => {
		appLayout.render(
			<MainLayout>
				<OmnichannelQueueList />
			</MainLayout>,
		);
	},
});

FlowRouter.route('/terms-of-service', {
	name: 'terms-of-service',
	action: () => {
		appLayout.render(<CMSPage page='Layout_Terms_of_Service' />);
	},
});

FlowRouter.route('/privacy-policy', {
	name: 'privacy-policy',
	action: () => {
		appLayout.render(<CMSPage page='Layout_Privacy_Policy' />);
	},
});

FlowRouter.route('/legal-notice', {
	name: 'legal-notice',
	action: () => {
		appLayout.render(<CMSPage page='Layout_Legal_Notice' />);
	},
});

FlowRouter.route('/register/:hash', {
	name: 'register-secret-url',
	action: () => {
		appLayout.render(<SecretURLPage />);
	},
});

FlowRouter.route('/invite/:hash', {
	name: 'invite',
	action: () => {
		appLayout.render(<InvitePage />);
	},
});

FlowRouter.route('/setup-wizard/:step?', {
	name: 'setup-wizard',
	action: () => {
		appLayout.render(<SetupWizardRoute />);
	},
});

FlowRouter.route('/mailer/unsubscribe/:_id/:createdAt', {
	name: 'mailer-unsubscribe',
	action: () => {
		appLayout.render(<MailerUnsubscriptionPage />);
	},
});

FlowRouter.route('/login-token/:token', {
	name: 'tokenLogin',
	action(params) {
		Accounts.callLoginMethod({
			methodArguments: [
				{
					loginToken: params?.token,
				},
			],
			userCallback(error) {
				console.error(error);
				FlowRouter.go('/');
			},
		});
	},
});

FlowRouter.route('/reset-password/:token', {
	name: 'resetPassword',
	action() {
		appLayout.render(<ResetPasswordPage />);
	},
});

FlowRouter.route('/snippet/:snippetId/:snippetName', {
	name: 'snippetView',
	action() {
		appLayout.render(
			<MainLayout>
				<BlazeTemplate template='snippetPage' />
			</MainLayout>,
		);
	},
});

FlowRouter.route('/oauth/authorize', {
	name: 'oauth/authorize',
	action() {
		appLayout.render(
			<MainLayout>
				<BlazeTemplate template='authorize' />
			</MainLayout>,
		);
	},
});

FlowRouter.route('/oauth/error/:error', {
	name: 'oauth/error',
	action() {
		appLayout.render(
			<MainLayout>
				<BlazeTemplate template='oauth404' />
			</MainLayout>,
		);
	},
});

FlowRouter.notFound = {
	action: (): void => {
		appLayout.render(<NotFoundPage />);
	},
};

Meteor.startup(() => {
	FlowRouter.initialize();
});


//! moment.js locale configuration
//! locale : Bulgarian [bg]
//! author : Krasen Borisov : https://github.com/kraz

;(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 bg = moment.defineLocale('bg', {
        months : 'януари_февруари_март_април_май_юни_юли_август_септември_октомври_ноември_декември'.split('_'),
        monthsShort : 'янр_фев_мар_апр_май_юни_юли_авг_сеп_окт_ное_дек'.split('_'),
        weekdays : 'неделя_понеделник_вторник_сряда_четвъртък_петък_събота'.split('_'),
        weekdaysShort : 'нед_пон_вто_сря_чет_пет_съб'.split('_'),
        weekdaysMin : 'нд_пн_вт_ср_чт_пт_сб'.split('_'),
        longDateFormat : {
            LT : 'H:mm',
            LTS : 'H:mm:ss',
            L : 'D.MM.YYYY',
            LL : 'D MMMM YYYY',
            LLL : 'D MMMM YYYY H:mm',
            LLLL : 'dddd, D MMMM YYYY H:mm'
        },
        calendar : {
            sameDay : '[Днес в] LT',
            nextDay : '[Утре в] LT',
            nextWeek : 'dddd [в] LT',
            lastDay : '[Вчера в] LT',
            lastWeek : function () {
                switch (this.day()) {
                    case 0:
                    case 3:
                    case 6:
                        return '[В изминалата] dddd [в] LT';
                    case 1:
                    case 2:
                    case 4:
                    case 5:
                        return '[В изминалия] dddd [в] LT';
                }
            },
            sameElse : 'L'
        },
        relativeTime : {
            future : 'след %s',
            past : 'преди %s',
            s : 'няколко секунди',
            m : 'минута',
            mm : '%d минути',
            h : 'час',
            hh : '%d часа',
            d : 'ден',
            dd : '%d дни',
            M : 'месец',
            MM : '%d месеца',
            y : 'година',
            yy : '%d години'
        },
        ordinalParse: /\d{1,2}-(ев|ен|ти|ви|ри|ми)/,
        ordinal : function (number) {
            var lastDigit = number % 10,
                last2Digits = number % 100;
            if (number === 0) {
                return number + '-ев';
            } else if (last2Digits === 0) {
                return number + '-ен';
            } else if (last2Digits > 10 && last2Digits < 20) {
                return number + '-ти';
            } else if (lastDigit === 1) {
                return number + '-ви';
            } else if (lastDigit === 2) {
                return number + '-ри';
            } else if (lastDigit === 7 || lastDigit === 8) {
                return number + '-ми';
            } else {
                return number + '-ти';
            }
        },
        week : {
            dow : 1, // Monday is the first day of the week.
            doy : 7  // The week that contains Jan 1st is the first week of the year.
        }
    });

    return bg;

}));


const payload = packet.startsWith('[') ? JSON.parse(packet)[0] : packet;

async call(client: Client, packet: IPacket): Promise<void> {

// if method was not defined on DDP Streamer we fall back to Meteor

const result = await MeteorService.callMethodWithToken(client.userId, client.userToken, packet.method, packet.params);

keep keep keep keep add keep keep keep keep

// if client is not connected we don't need to do anything

if (client.ws.readyState !== WebSocket.OPEN) {


import type { IRoom, IUser } from '@rocket.chat/core-typings';
import type { SelectOption } from '@rocket.chat/fuselage';
import { Box, Icon, TextInput, Margins, Select, Throbber, ButtonGroup, Button, Callout } from '@rocket.chat/fuselage';
import { useMutableCallback, useAutoFocus } from '@rocket.chat/fuselage-hooks';
import { useTranslation } from '@rocket.chat/ui-contexts';
import type { ReactElement, FormEventHandler, ComponentProps, MouseEvent } from 'react';
import React, { useMemo } from 'react';
import { Virtuoso } from 'react-virtuoso';

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

type RoomMemberUser = Pick<IUser, 'username' | '_id' | '_updatedAt' | 'name' | 'status'>;

type RoomMembersProps = {
	rid: IRoom['_id'];
	isTeam?: boolean;
	isDirect?: boolean;
	loading: boolean;
	text: string;
	type: string;
	setText: FormEventHandler<HTMLElement>;
	setType: (type: 'online' | 'all') => void;
	members: RoomMemberUser[];
	total: number;
	error?: Error;
	onClickClose: () => void;
	onClickView: (e: MouseEvent<HTMLElement>) => void;
	onClickAdd?: () => void;
	onClickInvite?: () => void;
	loadMoreItems: (start: number, end: number) => void;
	renderRow?: (props: ComponentProps<typeof RoomMembersRow>) => ReactElement | null;
	reload: () => void;
};

const RoomMembers = ({
	loading,
	members = [],
	text,
	type,
	setText,
	setType,
	onClickClose,
	onClickView,
	onClickAdd,
	onClickInvite,
	total,
	error,
	loadMoreItems,
	renderRow: RowComponent = RoomMembersRow,
	rid,
	isTeam,
	isDirect,
	reload,
}: RoomMembersProps): ReactElement => {
	const t = useTranslation();
	const inputRef = useAutoFocus<HTMLInputElement>(true);
	const itemData = useMemo(() => ({ onClickView, rid }), [onClickView, rid]);
	const loadMore = useMutableCallback((start) => !loading && loadMoreItems(start, Math.min(50, total - start)));

	const options: SelectOption[] = useMemo(
		() => [
			['online', t('Online')],
			['all', t('All')],
		],
		[t],
	);

	return (
		<>
			<VerticalBar.Header data-qa-id='RoomHeader-Members'>
				<VerticalBar.Icon name='members' />
				<VerticalBar.Text>{isTeam ? t('Teams_members') : t('Members')}</VerticalBar.Text>
				{onClickClose && <VerticalBar.Close onClick={onClickClose} />}
			</VerticalBar.Header>

			<VerticalBar.Content p='x12'>
				<Box display='flex' flexDirection='row' p='x12' flexShrink={0}>
					<Box display='flex' flexDirection='row' flexGrow={1} mi='neg-x4'>
						<Margins inline='x4'>
							<TextInput
								placeholder={t('Search_by_username')}
								value={text}
								ref={inputRef}
								onChange={setText}
								addon={<Icon name='magnifier' size='x20' />}
							/>
							<Select
								flexGrow={0}
								width='110px'
								onChange={(value): void => setType(value as 'online' | 'all')}
								value={type}
								options={options}
							/>
						</Margins>
					</Box>
				</Box>

				{loading && (
					<Box pi='x24' pb='x12'>
						<Throbber size='x12' />
					</Box>
				)}

				{error && (
					<Box pi='x12' pb='x12'>
						<Callout type='danger'>{error.message}</Callout>
					</Box>
				)}

				{!loading && members.length <= 0 && (
					<Box textAlign='center' p='x12' color='annotation'>
						{t('No_members_found')}
					</Box>
				)}

				{!loading && members.length > 0 && (
					<Box pi='x18' pb='x12'>
						<Box is='span' color='hint' fontScale='p2'>
							{t('Showing')}: {members.length}
						</Box>

						<Box is='span' color='hint' fontScale='p2' mis='x8'>
							{t('Total')}: {total}
						</Box>
					</Box>
				)}

				<Box w='full' h='full' overflow='hidden' flexShrink={1}>
					{!loading && members && members.length > 0 && (
						<Virtuoso
							style={{
								height: '100%',
								width: '100%',
							}}
							totalCount={total}
							endReached={loadMore}
							overscan={50}
							data={members}
							components={{ Scroller: ScrollableContentWrapper }}
							itemContent={(index, data): ReactElement => <RowComponent data={itemData} user={data} index={index} reload={reload} />}
						/>
					)}
				</Box>
			</VerticalBar.Content>
			{!isDirect && (onClickInvite || onClickAdd) && (
				<VerticalBar.Footer>
					<ButtonGroup stretch>
						{onClickInvite && (
							<Button onClick={onClickInvite} width='50%'>
								<Icon name='link' size='x20' mie='x4' />
								{t('Invite_Link')}
							</Button>
						)}
						{onClickAdd && (
							<Button onClick={onClickAdd} width='50%' primary>
								<Icon name='user-plus' size='x20' mie='x4' />
								{t('Add')}
							</Button>
						)}
					</ButtonGroup>
				</VerticalBar.Footer>
			)}
		</>
	);
};

export default RoomMembers;


// http://stackoverflow.com/a/14824756

const ltrChars = 'A-Za-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02B8\u0300-\u0590\u0800-\u1FFF\u2C00-\uFB1C\uFDFE-\uFE6F\uFEFD-\uFFFF';
const rtlChars = '\u0591-\u07FF\uFB1D-\uFDFD\uFE70-\uFEFC';
const rtlRegExp = new RegExp(`^[^${ltrChars}]*[${rtlChars}]`);

export const isRTL = (text: string): boolean => rtlRegExp.test(text);


async subscribe(client: Client, packet: IPacket): Promise<void> {

throw new MeteorError(404, `Subscription '${packet.name}' not found`);


import { callbacks } from '../../../../../lib/callbacks';
import { settings } from '../../../../../app/settings/server';
import { debouncedDispatchWaitingQueueStatus } from '../lib/Helper';
import { LivechatEnterprise } from '../lib/LivechatEnterprise';

const onCloseLivechat = (room) => {
	Promise.await(LivechatEnterprise.releaseOnHoldChat(room));

	if (!settings.get('Livechat_waiting_queue')) {
		return room;
	}

	const { departmentId } = room || {};
	debouncedDispatchWaitingQueueStatus(departmentId);

	return room;
};

callbacks.add('livechat.closeRoom', onCloseLivechat, callbacks.priority.HIGH, 'livechat-waiting-queue-monitor-close-room');


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

import { settings } from '../../settings';
import { CustomOAuth } from '../../custom-oauth';

const config = {
	serverURL: '',
	tokenPath: '/index.php/apps/oauth2/api/v1/token',
	tokenSentVia: 'header',
	authorizePath: '/index.php/apps/oauth2/authorize',
	identityPath: '/ocs/v2.php/cloud/user?format=json',
	scope: 'openid',
	addAutopublishFields: {
		forLoggedInUser: ['services.nextcloud'],
		forOtherUsers: ['services.nextcloud.name'],
	},
};

const Nextcloud = new CustomOAuth('nextcloud', config);

const fillServerURL = _.debounce(
	Meteor.bindEnvironment(() => {
		const nextcloudURL = settings.get('Accounts_OAuth_Nextcloud_URL');
		if (!nextcloudURL) {
			if (nextcloudURL === undefined) {
				return fillServerURL();
			}
			return;
		}
		config.serverURL = nextcloudURL.trim().replace(/\/*$/, '');
		return Nextcloud.configure(config);
	}),
	Meteor.isServer ? 1000 : 100,
);

Meteor.startup(function () {
	if (Meteor.isServer) {
		settings.watch('Accounts_OAuth_Nextcloud_URL', () => fillServerURL());
	} else {
		Tracker.autorun(function () {
			return fillServerURL();
		});
	}
});


/* eslint-env mocha */

import type { ILivechatPriority } from '@rocket.chat/core-typings';
import { expect } from 'chai';

import { getCredentials, api, request, credentials } from '../../../data/api-data';
import { savePriority } from '../../../data/livechat/priorities';
import { createVisitor, createLivechatRoom, takeInquiry } from '../../../data/livechat/rooms';
import { updatePermission, updateSetting } from '../../../data/permissions.helper';
import { IS_EE } from '../../../e2e/config/constants';

(IS_EE ? describe : describe.skip)('[EE] LIVECHAT - Priorities', function () {
	this.retries(0);

	before((done) => getCredentials(done));

	before((done) => {
		updateSetting('Livechat_enabled', true)
			.then(() => updateSetting('Livechat_Routing_Method', 'Manual_Selection'))
			.then(done);
	});

	describe('livechat/priorities', () => {
		it('should return an "unauthorized error" when the user does not have the necessary permission', async () => {
			await updatePermission('manage-livechat-priorities', []);
			await updatePermission('view-l-room', []);
			const response = await request
				.get(api('livechat/priorities'))
				.set(credentials)
				.expect('Content-Type', 'application/json')
				.expect(403);
			expect(response.body).to.have.property('success', false);
		});
		it('should return an array of priorities', async () => {
			await updatePermission('manage-livechat-priorities', ['admin']);
			await updatePermission('view-l-room', ['livechat-agent']);
			const priority = await savePriority();
			const response = await request
				.get(api('livechat/priorities'))
				.set(credentials)
				.expect('Content-Type', 'application/json')
				.expect(200);
			expect(response.body).to.have.property('success', true);
			expect(response.body.priorities).to.be.an('array').with.lengthOf.greaterThan(0);
			expect(response.body.priorities.find((p: ILivechatPriority) => p._id === priority._id)).to.be.an('object');
		});
	});

	describe('livechat/priorities/:priorityId', () => {
		it('should return an "unauthorized error" when the user does not have the necessary permission', async () => {
			await updatePermission('manage-livechat-priorities', []);
			await updatePermission('view-l-room', []);
			const response = await request
				.get(api('livechat/priorities/123'))
				.set(credentials)
				.expect('Content-Type', 'application/json')
				.expect(403);
			expect(response.body).to.have.property('success', false);
		});
		it('should return a priority', async () => {
			await updatePermission('manage-livechat-priorities', ['admin']);
			await updatePermission('view-l-room', ['livechat-agent']);
			const priority = await savePriority();
			const response = await request
				.get(api(`livechat/priorities/${priority._id}`))
				.set(credentials)
				.expect('Content-Type', 'application/json')
				.expect(200);
			expect(response.body).to.have.property('success', true);
			expect(response.body).to.be.an('object');
			expect(response.body._id).to.be.equal(priority._id);
		});
	});

	describe('livechat/inquiry.prioritize', () => {
		it('should return an "unauthorized error" when the user does not have the necessary permission', async () => {
			await updatePermission('manage-livechat-priorities', []);
			await updatePermission('view-l-room', []);
			const response = await request
				.put(api('livechat/inquiry.prioritize'))
				.set(credentials)
				.send({
					roomId: '123',
					priority: '123',
				})
				.expect('Content-Type', 'application/json')
				.expect(403);
			expect(response.body).to.have.property('success', false);
		});
		it('should fail if roomId is not in request body', async () => {
			await updatePermission('manage-livechat-priorities', ['admin']);
			await updatePermission('view-l-room', ['livechat-agent']);
			const response = await request
				.put(api('livechat/inquiry.prioritize'))
				.set(credentials)
				.send({
					priority: '123',
				})
				.expect('Content-Type', 'application/json')
				.expect(400);
			expect(response.body).to.have.property('success', false);
		});
		it('should fail if roomId is invalid', async () => {
			const response = await request
				.put(api('livechat/inquiry.prioritize'))
				.set(credentials)
				.send({
					roomId: '123',
					priority: '123',
				})
				.expect('Content-Type', 'application/json')
				.expect(400);
			expect(response.body).to.have.property('success', false);
		});
		it('should fail if priority is not in request body', async () => {
			const response = await request
				.put(api('livechat/inquiry.prioritize'))
				.set(credentials)
				.send({
					roomId: '123',
				})
				.expect('Content-Type', 'application/json')
				.expect(400);
			expect(response.body).to.have.property('success', false);
		});
		it('should fail if priority is not valid', async () => {
			const visitor = await createVisitor();
			const room = await createLivechatRoom(visitor.token);
			const response = await request
				.put(api('livechat/inquiry.prioritize'))
				.set(credentials)
				.send({
					roomId: room._id,
					priority: '123',
				})
				.expect('Content-Type', 'application/json')
				.expect(400);
			expect(response.body).to.have.property('success', false);
		});
		it('should fail if inquiry is not queued', async () => {
			const visitor = await createVisitor();
			const room = await createLivechatRoom(visitor.token);
			await takeInquiry(room._id);

			const response = await request
				.put(api('livechat/inquiry.prioritize'))
				.set(credentials)
				.send({
					roomId: room._id,
					priority: '123',
				})
				.expect('Content-Type', 'application/json')
				.expect(400);
			expect(response.body).to.have.property('success', false);
		});
		it('should prioritize an inquiry', async () => {
			const visitor = await createVisitor();
			const room = await createLivechatRoom(visitor.token);
			const priority = await savePriority();
			const response = await request
				.put(api('livechat/inquiry.prioritize'))
				.set(credentials)
				.send({
					roomId: room._id,
					priority: priority._id,
				})
				.expect('Content-Type', 'application/json')
				.expect(200);
			expect(response.body).to.have.property('success', true);
		});
	});
});


// if the connection state is not open anymore, it somehow got to a weird state,

// we'll emit close so it can clean up the weird state, and so we stop emitting to it

if (subscription.client.ws.readyState !== WebSocket.OPEN) {

import WebSocket from 'ws';
import type { DDPSubscription, Connection, TransformMessage } from 'meteor/rocketchat:streamer';
import { api } from '@rocket.chat/core-services';

import { server } from './configureServer';
import { DDP_EVENTS } from './constants';
import { isEmpty } from './lib/utils';
import { Streamer, StreamerCentral } from '../../../../apps/meteor/server/modules/streamer/streamer.module';

StreamerCentral.on('broadcast', (name, eventName, args) => {
	api.broadcast('stream', [name, eventName, args]);
});

export class Stream extends Streamer {
	registerPublication(name: string, fn: (eventName: string, options: boolean | { useCollection?: boolean; args?: any }) => void): void {
		server.publish(name, fn);
	}

	registerMethod(methods: Record<string, (eventName: string, ...args: any[]) => any>): void {
		server.methods(methods);
	}

	changedPayload(collection: string, id: string, fields: Record<string, any>): string | false {
		return (
			!isEmpty(fields) &&
			server.serialize({
				[DDP_EVENTS.MSG]: DDP_EVENTS.CHANGED,
				[DDP_EVENTS.COLLECTION]: collection,
				[DDP_EVENTS.ID]: id,
				[DDP_EVENTS.FIELDS]: fields,
			})
		);
	}

	async sendToManySubscriptions(
		subscriptions: Set<DDPSubscription>,
		origin: Connection | undefined,
		eventName: string,
		args: any[],
		getMsg: string | TransformMessage,
	): Promise<void> {
		if (typeof getMsg === 'function') {
			return super.sendToManySubscriptions(subscriptions, origin, eventName, args, getMsg);
		}

		const options = {
			fin: true, // sending a single fragment message
			rsv1: false, // don"t set rsv1 bit (no compression)
			opcode: 1, // opcode for a text frame
			mask: false, // set false for client-side
			readOnly: false, // the data can be modified as needed
		};

		const data = {
			meteor: [Buffer.concat(WebSocket.Sender.frame(Buffer.from(`a${JSON.stringify([getMsg])}`), options))],
			normal: [Buffer.concat(WebSocket.Sender.frame(Buffer.from(getMsg), options))],
		};

		for await (const { subscription } of subscriptions) {
			if (this.retransmitToSelf === false && origin && origin === subscription.connection) {
				continue;
			}

			if (await this.isEmitAllowed(subscription, eventName, ...args)) {
				await new Promise<void>((resolve, reject) => {
					const frame = data[subscription.client.meteorClient ? 'meteor' : 'normal'];

					subscription.client.ws._sender.sendFrame(frame, (err: unknown) => {
						if (err) {
							return reject(err);
						}
						resolve();
					});
				});
			}
		}
	}
}


import { Field, TextInput, ButtonGroup, Button } from '@rocket.chat/fuselage';
import { useMutableCallback } from '@rocket.chat/fuselage-hooks';
import { useToastMessageDispatch, useTranslation, useEndpoint } from '@rocket.chat/ui-contexts';
import React, { useState, useMemo } from 'react';

import { hasAtLeastOnePermission } from '../../../../../../app/authorization/client';
import CustomFieldsForm from '../../../../../components/CustomFieldsForm';
import Tags from '../../../../../components/Omnichannel/Tags';
import VerticalBar from '../../../../../components/VerticalBar';
import { AsyncStatePhase } from '../../../../../hooks/useAsyncState';
import { useEndpointData } from '../../../../../hooks/useEndpointData';
import { useForm } from '../../../../../hooks/useForm';
import { useFormsSubscription } from '../../../additionalForms';
import { FormSkeleton } from '../../Skeleton';

const initialValuesRoom = {
	topic: '',
	tags: [],
	livechatData: {},
	priorityId: '',
};

const getInitialValuesRoom = (room) => {
	if (!room) {
		return initialValuesRoom;
	}

	const { topic, tags, livechatData, priorityId } = room;

	return {
		topic: topic ?? '',
		tags: tags ?? [],
		livechatData: livechatData ?? {},
		priorityId: priorityId ?? '',
	};
};

function RoomEdit({ room, visitor, reload, reloadInfo, close }) {
	const t = useTranslation();

	const { values: valuesRoom, handlers: handlersRoom, hasUnsavedChanges: hasUnsavedChangesRoom } = useForm(getInitialValuesRoom(room));
	const canViewCustomFields = () => hasAtLeastOnePermission(['view-livechat-room-customfields', 'edit-livechat-room-customfields']);

	const { handleTopic, handleTags, handlePriorityId } = handlersRoom;
	const { topic, tags, priorityId } = valuesRoom;

	const forms = useFormsSubscription();

	const { usePrioritiesSelect = () => {} } = forms;

	const PrioritiesSelect = usePrioritiesSelect();

	const {
		values: valueCustom,
		handlers: handleValueCustom,
		hasUnsavedChanges: hasUnsavedChangesCustomFields,
	} = useForm({
		livechatData: valuesRoom.livechatData,
	});

	const { handleLivechatData } = handleValueCustom;
	const { livechatData } = valueCustom;

	const [customFieldsError, setCustomFieldsError] = useState([]);

	const { value: allCustomFields, phase: stateCustomFields } = useEndpointData('/v1/livechat/custom-fields');
	const { value: prioritiesResult = {}, phase: statePriorities } = useEndpointData('/v1/livechat/priorities');

	const jsonConverterToValidFormat = (customFields) => {
		const jsonObj = {};
		customFields.forEach(({ _id, label, visibility, options, scope, defaultValue, required }) => {
			(visibility === 'visible') & (scope === 'room') &&
				(jsonObj[_id] = {
					label,
					type: options ? 'select' : 'text',
					required,
					defaultValue,
					options: options && options.split(',').map((item) => item.trim()),
				});
		});
		return jsonObj;
	};

	const jsonCustomField = useMemo(
		() => (allCustomFields && allCustomFields.customFields ? jsonConverterToValidFormat(allCustomFields.customFields) : {}),
		[allCustomFields],
	);

	const dispatchToastMessage = useToastMessageDispatch();

	const saveRoom = useEndpoint('POST', '/v1/livechat/room.saveInfo');

	const handleSave = useMutableCallback(async (e) => {
		e.preventDefault();
		const guestData = {
			_id: visitor._id,
		};

		const roomData = {
			_id: room._id,
			topic,
			tags: tags.sort(),
			livechatData,
			...(priorityId && { priorityId }),
		};

		try {
			await saveRoom({
				guestData,
				roomData,
			});
			dispatchToastMessage({ type: 'success', message: t('Saved') });
			reload && reload();
			reloadInfo && reloadInfo();
			close();
		} catch (error) {
			dispatchToastMessage({ type: 'error', message: error });
		}
	});

	const formIsValid = (hasUnsavedChangesRoom || hasUnsavedChangesCustomFields) && customFieldsError.length === 0;

	if ([stateCustomFields, statePriorities].includes(AsyncStatePhase.LOADING)) {
		return <FormSkeleton />;
	}

	const { priorities } = prioritiesResult;

	return (
		<>
			<VerticalBar.ScrollableContent is='form'>
				{canViewCustomFields() && allCustomFields && (
					<CustomFieldsForm
						jsonCustomFields={jsonCustomField}
						customFieldsData={livechatData}
						setCustomFieldsData={handleLivechatData}
						setCustomFieldsError={setCustomFieldsError}
					/>
				)}
				<Field>
					<Field.Label>{t('Topic')}</Field.Label>
					<Field.Row>
						<TextInput flexGrow={1} value={topic} onChange={handleTopic} />
					</Field.Row>
				</Field>
				<Field>
					<Tags tags={tags} handler={handleTags} />
				</Field>
				{PrioritiesSelect && priorities && priorities.length > 0 && (
					<PrioritiesSelect value={priorityId} label={t('Priority')} options={priorities} handler={handlePriorityId} />
				)}
			</VerticalBar.ScrollableContent>
			<VerticalBar.Footer>
				<ButtonGroup stretch>
					<Button flexGrow={1} onClick={close}>
						{t('Cancel')}
					</Button>
					<Button mie='none' flexGrow={1} onClick={handleSave} disabled={!formIsValid} primary>
						{t('Save')}
					</Button>
				</ButtonGroup>
			</VerticalBar.Footer>
		</>
	);
}

export default RoomEdit;


	<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" />

test

Talk to us.

changing page title

	page 0

	page 1

	page 2

	page 3

without changing page title

	page 4

	page 5

	page 6

	page 7


import { Meteor } from 'meteor/meteor';

import { Subscriptions } from '../../../app/models/server';
import { subscriptionFields } from '../../modules/watchers/watchers.module';

Meteor.methods({
	'subscriptions/get'(updatedAt) {
		if (!Meteor.userId()) {
			return [];
		}

		const options = { fields: subscriptionFields };

		const records = Subscriptions.findByUserId(Meteor.userId(), options).fetch();

		if (updatedAt instanceof Date) {
			return {
				update: records.filter(function (record) {
					return record._updatedAt > updatedAt;
				}),
				remove: Subscriptions.trashFindDeletedAfter(
					updatedAt,
					{
						'u._id': Meteor.userId(),
					},
					{
						fields: {
							_id: 1,
							_deletedAt: 1,
						},
					},
				).fetch(),
			};
		}

		return records;
	},
});