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

%body
  #mocha
  #test-container
    #split-test-base<>
      %b<> Bold
    #split-test-text<>
      %b<> Bold
    #split-test-node
      %b<>
        %i<> Italic
        %s<> Strike
    #split-test-both
      %b<>
        %i<> Italic
    #split-test-complex
      %b<>
        %i<>
          %s<>
            %u<> One
            %u<> Two
          %s<> Three
    #split-test-lines
      .line<>
        %b<> 123
        %i<> 456

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


# TODO this is incorrect, we need to search for dirty lines and mark them, normalize only operates on dirty lines

keep keep keep keep replace keep keep keep keep

#= require underscore
#= require linked_list
#= require jetsync
#= require tandem/line

class TandemDocument
  @INDENT_PREFIX: 'indent-'

  @fixListNumbering: (root) ->
    ols = root.querySelectorAll('ol')
    _.each(ols, (ol) ->
      indent = Tandem.Utils.getIndent(ol)
      previous = ol.previousSibling
      while true
        if !previous? || previous.tagName != 'OL'
          return ol.setAttribute('start', 1)
        else
          prevIndent = Tandem.Utils.getIndent(previous)
          if prevIndent < indent
            return ol.setAttribute('start', 1)
          else if prevIndent == indent
            return ol.setAttribute('start', parseInt(previous.getAttribute('start')) + 1)
          else
            previous = previous.previousSibling
    )

  @normalizeHtml: (root, options = {}) ->
    if root.childNodes.length == 0
      div = root.ownerDocument.createElement('div')
      return root.appendChild(div)
    _.each(_.clone(root.childNodes), (child) =>
      if child.nodeType != child.ELEMENT_NODE
        child.parentNode.removeChild(child)
      else
        if options.ignoreDirty || child.classList.contains(Tandem.Line.DIRTY_CLASS)
          lineNodes = Tandem.Utils.breakBlocks(child)
          _.each(lineNodes, (lineNode) ->
            Tandem.Line.normalizeHtml(lineNode)
          )
    )
    TandemDocument.fixListNumbering(root)

  constructor: (@editor, @root) ->
    this.buildLines()

  appendLine: (lineNode) ->
    return this.insertLineBefore(lineNode, null)

  buildLines: ->
    this.reset()
    TandemDocument.normalizeHtml(@root, {ignoreDirty: true})
    this.rebuild()

  # Makes sure our data structures are consistent
  checkConsistency: (output = false) ->
    nodesByLine = _.map(@root.childNodes, (lineNode) ->
      nodes = lineNode.querySelectorAll('*')
      return _.filter(nodes, (node) ->
        return node.nodeType == node.ELEMENT_NODE && (node.nodeName == 'BR' || node.firstChild.nodeType == node.TEXT_NODE)
      )
    )
    lines = @lines.toArray()

    isConsistent = =>
      # @lines and @lineMap should match
      if lines.length != _.values(@lineMap).length
        console.error "@lines and @lineMap differ in length", lines.length, _.values(@lineMap).length
        return false
      return false if _.any(lines, (line) =>
        if !line? || @lineMap[line.id] != line
          if line?
            console.error line, "does not match", @lineMap[line.id]
          else
            console.error "null line"
          return true
        return false
      )
      # @lines should match nodesByLine
      if lines.length != nodesByLine.length
        console.error "@lines and nodesByLine differ in length"
        return false
      return false if _.any(lines, (line, index) =>
        if line.length != line.node.textContent.length
          console.error line, line.length, line.node.textContent.length, 'differ in length'
          return true
        leaves = line.leaves.toArray()
        leafNodes = _.map(leaves, (leaf) -> return leaf.node)
        if !_.isEqual(leafNodes, nodesByLine[index])
          console.error leafNodes, 'nodes differ from', nodesByLine[index]
          return true
        leaves = _.map(leaves, (leaf) ->
          return _.pick(leaf, 'attributes', 'length', 'line', 'node', 'text')
        )
        line.rebuild()
        rebuiltLeaves = _.map(line.leaves.toArray(), (leaf) ->
          return _.pick(leaf, 'attributes', 'length', 'line', 'node', 'text')
        )
        if !_.isEqual(leaves, rebuiltLeaves)
          console.error leaves, 'leaves differ from', rebuiltLeaves
          return true
        return false
      )
      return true

    if isConsistent()
      return true
    else
      if output
        console.error @root
        console.error nodesByLine
        console.error lines
        console.error @lineMap
      return false

  detectChange: ->
    # Go through HTML (which should be normalized)
    # Make sure non are different from their innerHTML, if so record change
    # Returns changes made

  findLeaf: (node) ->
    lineNode = node.parentNode
    while lineNode? && !Tandem.Line.isLineNode(lineNode)
      lineNode = lineNode.parentNode
    return null if !lineNode?
    line = this.findLine(lineNode)
    return line.findLeaf(node)

  findLine: (node) ->
    return @lineMap[node.id]

  findLineAtOffset: (index) ->
    [lineNode, offset] = Tandem.Utils.getChildAtOffset(@root, index)
    line = this.findLine(lineNode)
    return [line, offset]

  findLineNode: (node) ->
    while node? && !Tandem.Line.isLineNode(node)
      node = node.parentNode
    return node

  insertLineBefore: (newLineNode, refLine) ->
    line = new Tandem.Line(this, newLineNode)
    if refLine != null
      @lines.insertAfter(refLine.prev, line)
    else
      @lines.append(line)
    @lineMap[line.id] = line
    @length += line.length
    @length += 1 if @lines.length > 1
    return line

  printLines: ->
    lines = @lines.toArray()
    console.info lines.length
    _.each(lines, (line) ->
      console.info line, line.id, line.node.textContent
    )

  rebuild: ->
    this.reset()
    _.each(@root.childNodes, (node) =>
      this.appendLine(node)
    )

  rebuildDirty: ->
    dirtyNodes = @root.getElementsByClassName(Tandem.Line.DIRTY_CLASS)
    _.each(_.clone(dirtyNodes), (lineNode) =>
      lineNode.classList.remove(Tandem.Line.DIRTY_CLASS)
      line = this.findLine(lineNode)
      this.updateLine(line) if line?
    )

  removeLine: (line) ->
    delete @lineMap[line.id]
    @lines.remove(line)
    @length -= line.length + 1

  reset: ->
    @lines = new LinkedList()
    @lineMap = {}
    @length = 0

  splitLine: (line, offset) ->
    [lineNode1, lineNode2] = Tandem.Utils.splitNode(line.node, offset, true)
    line.node = lineNode1
    this.updateLine(line)
    this.insertLineBefore(lineNode2, line.next)

  toDelta: ->
    lines = @lines.toArray()
    deltas = _.flatten(_.map(lines, (line, index) ->
      lineDeltas = JetDelta.copy(line.delta).deltas
      if index < lines.length - 1
        lineDeltas.push(new JetInsert("\n"))
      return lineDeltas
    ), true)
    delta = new JetDelta(0, @length, deltas)
    delta.compact()
    return delta

  updateLine: (line) ->
    @length -= line.length
    line.rebuild()
    @length += line.length

window.Tandem ||= {}
window.Tandem.Document = TandemDocument


!!! 5
%html
  %head
    %title Stypi iPhone Editor Demo
    %link{:rel => 'stylesheet', :type => 'text/css', :href => '../lib/reset.css'}
    %link{:rel => 'stylesheet', :type => 'text/css', :href => 'styles/iphone-style.css'}
    %meta{:name=> 'viewport', :content => 'initial-scale=1.0, width=device-width, user-scalable=no'}

  %body
    #content-container
      .editor-wrapper
        #editor-container.editor-container

    %script{:type => 'text/javascript', :src => '../lib/jquery.js'}
    %script{:type => 'text/javascript', :src => '../lib/underscore.js'}
    %script{:type => 'text/javascript', :src => '../lib/eventemitter2.js'}
    %script{:type => 'text/javascript', :src => '../lib/rangy/rangy-core.js'}
    %script{:type => 'text/javascript', :src => '../lib/linked_list.js'}
    %script{:type => 'text/javascript', :src => '../lib/jetsync.js'}
    %script{:type => 'text/javascript', :src => '../src/constants.js'}
    %script{:type => 'text/javascript', :src => '../src/utils.js'}
    %script{:type => 'text/javascript', :src => '../src/leaf.js'}
    %script{:type => 'text/javascript', :src => '../src/line.js'}
    %script{:type => 'text/javascript', :src => '../src/document.js'}
    %script{:type => 'text/javascript', :src => '../src/position.js'}
    %script{:type => 'text/javascript', :src => '../src/range.js'}
    %script{:type => 'text/javascript', :src => '../src/keyboard.js'}
    %script{:type => 'text/javascript', :src => '../src/editor.js'}
    %script{:type => 'text/javascript', :src => '../src/toolbar.js'}
    %script{:type => 'text/javascript', :src => '../src/debug.js'}

    %script{:type => 'text/javascript', :src => '../lib/diff_match_patch.js'}
    %script{:type => 'text/javascript', :src => '../lib/async.js'}
    %script{:type => 'text/javascript', :src => '../lib/socket.io.js'}
    %script{:type => 'text/javascript', :src => 'scripts/stypi/network.js'}
    %script{:type => 'text/javascript', :src => 'scripts/stypi/ot/jetclient.js'}
    %script{:type => 'text/javascript', :src => 'scripts/stypi/ot/jetstate.js'}

    %script{:type => 'text/javascript', :src => 'scripts/iphone-ot.js'}


GIT
  remote: git://github.com/manufaktor/guard-haml.git
  revision: ee19e704c5afface900d18ba7f9ad90993712d02
  branch: development
  specs:
    guard-haml (0.4)
      guard (>= 1.1)
      haml (>= 3.0)

GEM
  remote: http://rubygems.org/
  specs:
    coffee-script (2.2.0)
      coffee-script-source
      execjs
    coffee-script-source (1.3.3)
    execjs (1.4.0)
      multi_json (~> 1.0)
    ffi (1.1.5)
    guard (1.3.0)
      listen (>= 0.4.2)
      thor (>= 0.14.6)
    guard-coffeescript (1.2.0)
      coffee-script (>= 2.2.0)
      guard (>= 1.1.0)
    guard-sass (1.0.0)
      guard (>= 1.1.0)
      sass (>= 3.1)
    haml (3.1.7)
    listen (0.4.7)
      rb-fchange (~> 0.0.5)
      rb-fsevent (~> 0.9.1)
      rb-inotify (~> 0.8.8)
    multi_json (1.3.6)
    rb-fchange (0.0.5)
      ffi
    rb-fsevent (0.9.1)
    rb-inotify (0.8.8)
      ffi (>= 0.5.0)
    sass (3.1.20)
    thor (0.15.4)

PLATFORMS
  ruby

DEPENDENCIES
  guard
  guard-coffeescript
  guard-haml!
  guard-sass


body
  font-family: 'Helvetica', 'Arial', san-serif
  font-size: 13px
  padding: 25px

#content-container
  margin: auto
  width: 960px

.formatting-container
  background-color: #f5f5f5
  border-bottom: 1px solid #ccc
  height: 32px
  line-height: 32px
  padding: 0px 5px
  .active
    color: green
    font-weight: bold

.editor-container
  height: 600px

.editor-wrapper
  border: 1px solid #aaa
  box-shadow: 0 0 2px 2px #ddd


// Mixins
import Routable from '../../mixins/routable'
import {
  inject as RegistrableInject
} from '../../mixins/registrable'

// Utilities
import { getObjectValueByPath } from '../../util/helpers'

export default {
  name: 'v-tab',

  mixins: [
    RegistrableInject('tabs', 'v-tab', 'v-tabs'),
    Routable
  ],

  inject: ['tabClick'],

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

  props: {
    activeClass: {
      type: String,
      default: 'tabs__item--active'
    },
    ripple: {
      type: [Boolean, Object],
      default: true
    }
  },

  computed: {
    classes () {
      return {
        'tabs__item': true,
        'tabs__item--disabled': this.disabled,
        [this.activeClass]: !this.to && this.isActive
      }
    },
    action () {
      const to = this.to || this.href

      if (typeof to === 'string') return to.replace('#', '')
      if (to === Object(to) &&
        (to.hasOwnProperty('name') ||
          to.hasOwnProperty('path'))
      ) return to.name || to.path

      return this
    }
  },

  watch: {
    $route: 'onRouteChange'
  },

  mounted () {
    this.tabs.register(this)
    this.onRouteChange()
  },

  beforeDestroy () {
    this.tabs.unregister(this)
  },

  methods: {
    click (e) {
      // If user provides an
      // actual link, do not
      // prevent default
      if (this.href &&
        this.href.indexOf('#') > -1
      ) e.preventDefault()

      this.$emit('click', e)

      this.to || this.tabClick(this)
    },
    onRouteChange () {
      if (!this.to || !this.$refs.link) return

      const path = `_vnode.data.class.${this.activeClass}`

      this.$nextTick(() => {
        if (getObjectValueByPath(this.$refs.link, path)) {
          this.tabClick(this)
        }
      })
    },
    toggle (action) {
      this.isActive = (action === this) || (action === this.action)
    }
  },

  render (h) {
    const link = this.generateRouteLink()
    const { data } = link

    // If disabled, use div as anchor tags do not support
    // being disabled
    const tag = this.disabled ? 'div' : link.tag

    data.ref = 'link'

    return h('div', {
      staticClass: 'tabs__div'
    }, [h(tag, data, this.$slots.default)])
  }
}


import '../../stylus/components/_app.styl'

// Component level mixins
import AppTheme from './mixins/app-theme'
import AppBreakpoint from './mixins/app-breakpoint'

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

export default {
  name: 'v-app',

  mixins: [
    AppBreakpoint,
    AppTheme
  ],

  directives: {
    Resize
  },

  props: {
    id: {
      type: String,
      default: 'app'
    },
    dark: Boolean
  },

  computed: {
    classes () {
      return {
        [`theme--${this.dark ? 'dark' : 'light'}`]: true
      }
    }
  },

  mounted () {
    this.$vuetify.dark = this.dark
  },

  watch: {
    dark () {
      this.$vuetify.dark = this.dark
    }
  },

  render (h) {
    const data = {
      staticClass: 'application',
      'class': this.classes,
      attrs: { 'data-app': true },
      domProps: { id: this.id },
      directives: [{
        name: 'resize',
        value: this.onResize
      }]
    }

    const wrapper = h('div', { staticClass: 'application--wrap' }, this.$slots.default)

    return h('div', data, [wrapper])
  }
}


import '../../stylus/components/_expansion-panel.styl'

import Themeable from '../../mixins/themeable'
import { provide as RegistrableProvide } from '../../mixins/registrable'

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

  mixins: [Themeable, RegistrableProvide('expansionPanel')],

  provide () {
    return {
      panelClick: this.panelClick,
      focusable: this.focusable
    }
  },

  data () {
    return {
      items: []
    }
  },

  props: {
    expand: Boolean,
    focusable: Boolean,
    inset: Boolean,
    popout: Boolean
  },

  methods: {
    panelClick (uid) {
      if (!this.expand) {
        for (let i = 0; i < this.items.length; i++) {
          this.items[i].toggle(uid)
        }
        return
      }

      for (let i = 0; i < this.items.length; i++) {
        if (this.items[i].uid === uid) {
          this.items[i].toggle(uid)
          return
        }
      }
    },
    register (uid, toggle) {
      this.items.push({ uid, toggle })
    },
    unregister (uid) {
      this.items = this.items.filter(i => i.uid !== uid)
    }
  },

  render (h) {
    return h('ul', {
      staticClass: 'expansion-panel',
      'class': {
        'expansion-panel--focusable': this.focusable,
        'expansion-panel--popout': this.popout,
        'expansion-panel--inset': this.inset,
        ...this.themeClasses
      }
    }, this.$slots.default)
  }
}


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

exports[`VPicker.js should render component with title and match snapshot 1`] = `

      title

      default

`;

exports[`VPicker.js should render component without title and match snapshot 1`] = `

      default

`;

exports[`VPicker.js should render dark component and match snapshot 1`] = `

      title

      default

`;


if (typeof to === 'string') return to.replace('#', '')

keep replace replace replace replace replace keep

const resolve = this.$router.resolve(this.to)


import '../../stylus/components/_icons.styl'

import Themeable from '../../mixins/themeable'
import Colorable from '../../mixins/colorable'

const SIZE_MAP = {
  small: '16px',
  default: '24px',
  medium: '28px',
  large: '36px',
  xLarge: '40px'
}

function isFontAwesome5 (iconType) {
  return ['fas', 'far', 'fal', 'fab'].some(val => iconType.includes(val))
}

export default {
  name: 'v-icon',

  functional: true,

  mixins: [Colorable, Themeable],

  props: {
    disabled: Boolean,
    large: Boolean,
    left: Boolean,
    medium: Boolean,
    right: Boolean,
    size: {
      type: [Number, String]
    },
    small: Boolean,
    xLarge: Boolean
  },

  render (h, { props, data, children = [] }) {
    const { small, medium, large, xLarge } = props
    const sizes = { small, medium, large, xLarge }
    const explicitSize = Object.keys(sizes).find(key => sizes[key] && key)
    const fontSize = (explicitSize && SIZE_MAP[explicitSize]) || props.size

    if (fontSize) data.style = { fontSize, ...data.style }

    let iconName = ''
    if (children.length) iconName = children.pop().text
    // Support usage of v-text and v-html
    else if (data.domProps) {
      iconName = data.domProps.textContent ||
        data.domProps.innerHTML ||
        iconName

      // Remove nodes so it doesn't
      // overwrite our changes
      delete data.domProps.textContent
      delete data.domProps.innerHTML
    }

    let iconType = 'material-icons'
    // Material Icon delimiter is _
    // https://material.io/icons/
    const delimiterIndex = iconName.indexOf('-')
    const isCustomIcon = delimiterIndex > -1

    if (isCustomIcon) {
      iconType = iconName.slice(0, delimiterIndex)

      if (isFontAwesome5(iconType)) iconType = ''
    // Assume if not a custom icon
    // is Material Icon font
    } else children.push(iconName)

    data.attrs = data.attrs || {}
    if (!('aria-hidden' in data.attrs)) {
      data.attrs['aria-hidden'] = true
    }

    const classes = Object.assign({
      'icon--disabled': props.disabled,
      'icon--left': props.left,
      'icon--right': props.right,
      'theme--dark': props.dark,
      'theme--light': props.light
    }, props.color ? Colorable.methods.addTextColorClassChecks.call(props, {}, props.color) : {})

    // Order classes
    // * Component class
    // * Vuetify classes
    // * Icon Classes
    data.staticClass = [
      'icon',
      data.staticClass,
      Object.keys(classes).filter(k => classes[k]).join(' '),
      iconType,
      isCustomIcon ? iconName : null
    ].reduce((prev, curr) => curr ? `${prev} ${curr}` : prev)
      .trim()

    return h('i', data, children)
  }
}


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

exports[`VSwitch.js should render component with error 1`] = `

`;


$shadow-key-umbra-opacity := rgba(0,0,0, .2)
$shadow-key-penumbra-opacity := rgba(0,0,0, .14)
$shadow-key-ambient-opacity := rgba(0,0,0, .12)

$shadow-key-umbra := 0px 0px 0px 0px $shadow-key-umbra-opacity,
                    0px 2px 1px -1px $shadow-key-umbra-opacity,
                    0px 3px 1px -2px $shadow-key-umbra-opacity,
                    0px 3px 3px -2px $shadow-key-umbra-opacity,
                    0px 2px 4px -1px $shadow-key-umbra-opacity,
                    0px 3px 5px -1px $shadow-key-umbra-opacity,
                    0px 3px 5px -1px $shadow-key-umbra-opacity,
                    0px 4px 5px -2px $shadow-key-umbra-opacity,
                    0px 5px 5px -3px $shadow-key-umbra-opacity,
                    0px 5px 6px -3px $shadow-key-umbra-opacity,
                    0px 6px 6px -3px $shadow-key-umbra-opacity,
                    0px 6px 7px -4px $shadow-key-umbra-opacity,
                    0px 7px 8px -4px $shadow-key-umbra-opacity,
                    0px 7px 8px -4px $shadow-key-umbra-opacity,
                    0px 7px 9px -4px $shadow-key-umbra-opacity,
                    0px 8px 9px -5px $shadow-key-umbra-opacity,
                    0px 8px 10px -5px $shadow-key-umbra-opacity,
                    0px 8px 11px -5px $shadow-key-umbra-opacity,
                    0px 9px 11px -5px $shadow-key-umbra-opacity,
                    0px 9px 12px -6px $shadow-key-umbra-opacity,
                    0px 10px 13px -6px $shadow-key-umbra-opacity,
                    0px 10px 13px -6px $shadow-key-umbra-opacity,
                    0px 10px 14px -6px $shadow-key-umbra-opacity,
                    0px 11px 14px -7px $shadow-key-umbra-opacity,
                    0px 11px 15px -7px $shadow-key-umbra-opacity

$shadow-key-penumbra := 0px 0px 0px 0px $shadow-key-penumbra-opacity,
                        0px 1px 1px 0px $shadow-key-penumbra-opacity,
                        0px 2px 2px 0px $shadow-key-penumbra-opacity,
                        0px 3px 4px 0px $shadow-key-penumbra-opacity,
                        0px 4px 5px 0px $shadow-key-penumbra-opacity,
                        0px 5px 8px 0px $shadow-key-penumbra-opacity,
                        0px 6px 10px 0px $shadow-key-penumbra-opacity,
                        0px 7px 10px 1px $shadow-key-penumbra-opacity,
                        0px 8px 10px 1px $shadow-key-penumbra-opacity,
                        0px 9px 12px 1px $shadow-key-penumbra-opacity,
                        0px 10px 14px 1px $shadow-key-penumbra-opacity,
                        0px 11px 15px 1px $shadow-key-penumbra-opacity,
                        0px 12px 17px 2px $shadow-key-penumbra-opacity,
                        0px 13px 19px 2px $shadow-key-penumbra-opacity,
                        0px 14px 21px 2px $shadow-key-penumbra-opacity,
                        0px 15px 22px 2px $shadow-key-penumbra-opacity,
                        0px 16px 24px 2px $shadow-key-penumbra-opacity,
                        0px 17px 26px 2px $shadow-key-penumbra-opacity,
                        0px 18px 28px 2px $shadow-key-penumbra-opacity,
                        0px 19px 29px 2px $shadow-key-penumbra-opacity,
                        0px 20px 31px 3px $shadow-key-penumbra-opacity,
                        0px 21px 33px 3px $shadow-key-penumbra-opacity,
                        0px 22px 35px 3px $shadow-key-penumbra-opacity,
                        0px 23px 36px 3px $shadow-key-penumbra-opacity,
                        0px 24px 38px 3px $shadow-key-penumbra-opacity

$shadow-key-ambient := 0px 0px 0px 0px $shadow-key-ambient-opacity,
                      0px 1px 3px 0px $shadow-key-ambient-opacity,
                      0px 1px 5px 0px $shadow-key-ambient-opacity,
                      0px 1px 8px 0px $shadow-key-ambient-opacity,
                      0px 1px 10px 0px $shadow-key-ambient-opacity,
                      0px 1px 14px 0px $shadow-key-ambient-opacity,
                      0px 1px 18px 0px $shadow-key-ambient-opacity,
                      0px 2px 16px 1px $shadow-key-ambient-opacity,
                      0px 3px 14px 2px $shadow-key-ambient-opacity,
                      0px 3px 16px 2px $shadow-key-ambient-opacity,
                      0px 4px 18px 3px $shadow-key-ambient-opacity,
                      0px 4px 20px 3px $shadow-key-ambient-opacity,
                      0px 5px 22px 4px $shadow-key-ambient-opacity,
                      0px 5px 24px 4px $shadow-key-ambient-opacity,
                      0px 5px 26px 4px $shadow-key-ambient-opacity,
                      0px 6px 28px 5px $shadow-key-ambient-opacity,
                      0px 6px 30px 5px $shadow-key-ambient-opacity,
                      0px 6px 32px 5px $shadow-key-ambient-opacity,
                      0px 7px 34px 6px $shadow-key-ambient-opacity,
                      0px 7px 36px 6px $shadow-key-ambient-opacity,
                      0px 8px 38px 7px $shadow-key-ambient-opacity,
                      0px 8px 40px 7px $shadow-key-ambient-opacity,
                      0px 8px 42px 7px $shadow-key-ambient-opacity,
                      0px 9px 44px 8px $shadow-key-ambient-opacity,
                      0px 9px 46px 8px $shadow-key-ambient-opacity

// MIXINS
elevation($z, important = false)
  box-shadow: $shadow-key-umbra[$z],
              $shadow-key-penumbra[$z],
              $shadow-key-ambient[$z] (important ? !important : )

elevationTransition($duration = 280ms, $easing = cubic-bezier(.4, 0, .2, 1))
  transition: box-shadow $duration $easing
  will-change: box-shadow


return this
}
},
watch: {
$route: 'onRouteChange'

to = resolve.href
}
return typeof to === 'string'
? to.replace('#', '')
: this


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

divider($material)
  background-color: $material.dividers

theme(divider, "divider")

.divider
  border: none
  display: block
  height: 1px
  min-height: 1px
  flex: 1
  width: 100%

  &--inset
    margin-left: 72px
    width: calc(100% - 72px)


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

exports[`VTimePicker.js should accept a date object for a value 1`] = `

          12

          :

          00

          am

          pm

            12

            1

            2

            3

            4

            5

            6

            7

            8

            9

            10

            11

`;

exports[`VTimePicker.js should accept a value 1`] = `

          9

          :

          12

          am

          pm

            12

            1

            2

            3

            4

            5

            6

            7

            8

            9

            10

            11

`;

exports[`VTimePicker.js should change am/pm when updated from model 1`] = `

          9

          :

          00

          am

          pm

            12

            1

            2

            3

            4

            5

            6

            7

            8

            9

            10

            11

`;

exports[`VTimePicker.js should render colored time picker 1`] = `

          9

          :

          00

          am

          pm

            12

            1

            2

            3

            4

            5

            6

            7

            8

            9

            10

            11

`;

exports[`VTimePicker.js should render colored time picker 2`] = `

          9

          :

          00

          am

          pm

            12

            1

            2

            3

            4

            5

            6

            7

            8

            9

            10

            11

`;

exports[`VTimePicker.js should render landscape component 1`] = `

          9

          :

          12

          am

          pm

            12

            1

            2

            3

            4

            5

            6

            7

            8

            9

            10

            11

`;


.display-4
  font-size: $headings.h1.size !important
  font-weight: $headings.h1.weight
  line-height: $headings.h1.line-height !important
  letter-spacing: $headings.h1.letter-spacing !important

.display-3
  font-size: $headings.h2.size !important
  font-weight: $headings.h2.weight
  line-height: $headings.h2.line-height !important
  letter-spacing: $headings.h2.letter-spacing !important

.display-2
  font-size: $headings.h3.size !important
  font-weight: $headings.h3.weight
  line-height: $headings.h3.line-height !important
  letter-spacing: $headings.h3.letter-spacing !important

.display-1
  font-size: $headings.h4.size !important
  font-weight: $headings.h4.weight
  line-height: $headings.h4.line-height !important
  letter-spacing: $headings.h4.letter-spacing !important

.headline
  font-size: $headings.h5.size !important
  font-weight: $headings.h5.weight
  line-height: $headings.h5.line-height !important
  letter-spacing: $headings.h5.letter-spacing !important

.title
  font-size: $headings.h6.size !important
  font-weight: $headings.h6.weight
  line-height: $headings.h6.line-height !important
  letter-spacing: $headings.h6.letter-spacing !important

.subheading
  font-size: $headings.subheading.size !important
  font-weight: $headings.subheading.weight

.body-2
  font-size: $headings.body-2.size !important
  font-weight: $headings.body-2.weight

.body-1
  font-size: $headings.body-1.size !important
  font-weight: $headings.body-1.weight

.caption
  font-size: $headings.caption.size !important
  font-weight: $headings.caption.weight

.btn
  font-size: $headings.button.size
  font-weight: $headings.button.weight

p
  margin-bottom: $spacers.three.y


wrapper.setProps({ href: null, to: '/foo' })

wrapper.setProps({ to: { path: '/bar' }})

keep keep keep replace replace keep keep keep


<!--
MAKE SURE TO READ THE CONTRIBUTING GUIDE BEFORE CREATING A PR
https://github.com/vuetifyjs/vuetify/blob/master/.github/CONTRIBUTING.md
-->

<!--- Provide a general summary of your changes in the Title above -->

## Description
<!--- Describe your changes in detail -->

## Motivation and Context
<!--- Why is this change required? What problem does it solve? -->
<!--- If it fixes an open issue, please link to the issue here. -->

## How Has This Been Tested?
<!--- Please describe how you tested your changes. -->
<!--- Have you created new tests or updated existing ones? -->

## Markup:
<!--- Paste markup that showcases your contribution --->

## Types of changes
<!--- What types of changes does your code introduce? Put an `x` in all the boxes that apply: -->
- [ ] Bug fix (non-breaking change which fixes an issue)
- [ ] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
- [ ] Improvement/refactoring (non-breaking change that doesn't add any feature but make things better)

## Checklist:
<!--- Go over all the following points, and put an `x` in all the boxes that apply. -->
<!--- If you're unsure about any of these, don't hesitate to ask. We're here to help! -->
- [ ] The PR title is no longer than 64 characters.
- [ ] The PR is submitted to the correct branch (`master` for bug fixes, `dev` for new features and breaking changes).
- [ ] My code follows the code style of this project.
- [ ] My change requires a change to the documentation.
- [ ] I have created a PR in the [documentation](https://github.com/vuetifyjs/vuetifyjs.com) with the necessary changes.


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

exports[`VDatePicker.js should match snapshot with allowed dates as array 1`] = `

      <button type="button"
              class="btn btn--flat"
      >

          Jan

      </button>

      <button type="button"
              class="btn btn--flat btn--disabled"
              disabled
      >

          Feb

      </button>

      <button type="button"
              class="btn btn--flat"
      >

          Mar

      </button>

      <button type="button"
              class="btn btn--flat btn--disabled"
              disabled
      >

          Apr

      </button>

      <button type="button"
              class="btn btn--active btn--depressed accent"
      >

          May

      </button>

      <button type="button"
              class="btn btn--flat btn--disabled"
              disabled
      >

          Jun

      </button>

      <button type="button"
              class="btn btn--flat"
      >

          Jul

      </button>

      <button type="button"
              class="btn btn--flat btn--disabled"
              disabled
      >

          Aug

      </button>

      <button type="button"
              class="btn btn--flat btn--disabled"
              disabled
      >

          Sep

      </button>

      <button type="button"
              class="btn btn--flat btn--disabled"
              disabled
      >

          Oct

      </button>

      <button type="button"
              class="btn btn--flat btn--disabled"
              disabled
      >

          Nov

      </button>

      <button type="button"
              class="btn btn--flat btn--disabled"
              disabled
      >

          Dec

      </button>

`;

exports[`VDatePicker.js should match snapshot with colored picker 1`] = `

        2005

          November

        <button type="button"
                class="btn btn--icon"
        >

              chevron_left

        </button>

            2005

        <button type="button"
                class="btn btn--icon"
        >

              chevron_right

        </button>

                <button type="button"
                        class="btn btn--flat"
                >

                    Jan

                </button>

                <button type="button"
                        class="btn btn--flat"
                >

                    Feb

                </button>

                <button type="button"
                        class="btn btn--flat"
                >

                    Mar

                </button>

                <button type="button"
                        class="btn btn--flat"
                >

                    Apr

                </button>

                <button type="button"
                        class="btn btn--flat"
                >

                    May

                </button>

                <button type="button"
                        class="btn btn--flat"
                >

                    Jun

                </button>

                <button type="button"
                        class="btn btn--flat"
                >

                    Jul

                </button>

                <button type="button"
                        class="btn btn--flat"
                >

                    Aug

                </button>

                <button type="button"
                        class="btn btn--flat"
                >

                    Sep

                </button>

                <button type="button"
                        class="btn btn--flat"
                >

                    Oct

                </button>

                <button type="button"
                        class="btn btn--flat"
                >

                    Nov

                </button>

                <button type="button"
                        class="btn btn--flat"
                >

                    Dec

                </button>

`;

exports[`VDatePicker.js should match snapshot with colored picker 2`] = `

        2005

          November

        <button type="button"
                class="btn btn--icon"
        >

              chevron_left

        </button>

            2005

        <button type="button"
                class="btn btn--icon"
        >

              chevron_right

        </button>

                <button type="button"
                        class="btn btn--flat"
                >

                    Jan

                </button>

                <button type="button"
                        class="btn btn--flat"
                >

                    Feb

                </button>

                <button type="button"
                        class="btn btn--flat"
                >

                    Mar

                </button>

                <button type="button"
                        class="btn btn--flat"
                >

                    Apr

                </button>

                <button type="button"
                        class="btn btn--flat"
                >

                    May

                </button>

                <button type="button"
                        class="btn btn--flat"
                >

                    Jun

                </button>

                <button type="button"
                        class="btn btn--flat"
                >

                    Jul

                </button>

                <button type="button"
                        class="btn btn--flat"
                >

                    Aug

                </button>

                <button type="button"
                        class="btn btn--flat"
                >

                    Sep

                </button>

                <button type="button"
                        class="btn btn--flat"
                >

                    Oct

                </button>

                <button type="button"
                        class="btn btn--flat"
                >

                    Nov

                </button>

                <button type="button"
                        class="btn btn--flat"
                >

                    Dec

                </button>

`;

exports[`VDatePicker.js should match snapshot with month formatting functions 1`] = `

      <button type="button"
              class="btn btn--flat"
      >

          (01)

      </button>

      <button type="button"
              class="btn btn--flat"
      >

          (02)

      </button>

      <button type="button"
              class="btn btn--flat"
      >

          (03)

      </button>

      <button type="button"
              class="btn btn--flat"
      >

          (04)

      </button>

      <button type="button"
              class="btn btn--flat"
      >

          (05)

      </button>

      <button type="button"
              class="btn btn--flat"
      >

          (06)

      </button>

      <button type="button"
              class="btn btn--flat"
      >

          (07)

      </button>

      <button type="button"
              class="btn btn--flat"
      >

          (08)

      </button>

      <button type="button"
              class="btn btn--flat"
      >

          (09)

      </button>

      <button type="button"
              class="btn btn--flat"
      >

          (10)

      </button>

      <button type="button"
              class="btn btn--flat"
      >

          (11)

      </button>

      <button type="button"
              class="btn btn--flat"
      >

          (12)

      </button>

`;

exports[`VDatePicker.js should match snapshot with pick-month prop 1`] = `

        2013

          May

        <button type="button"
                class="btn btn--icon"
        >

              chevron_left

        </button>

            2013

        <button type="button"
                class="btn btn--icon"
        >

              chevron_right

        </button>

                <button type="button"
                        class="btn btn--flat"
                >

                    Jan

                </button>

                <button type="button"
                        class="btn btn--flat"
                >

                    Feb

                </button>

                <button type="button"
                        class="btn btn--flat"
                >

                    Mar

                </button>

                <button type="button"
                        class="btn btn--flat"
                >

                    Apr

                </button>

                <button type="button"
                        class="btn btn--flat"
                >

                    May

                </button>

                <button type="button"
                        class="btn btn--flat"
                >

                    Jun

                </button>

                <button type="button"
                        class="btn btn--flat"
                >

                    Jul

                </button>

                <button type="button"
                        class="btn btn--flat"
                >

                    Aug

                </button>

                <button type="button"
                        class="btn btn--flat"
                >

                    Sep

                </button>

                <button type="button"
                        class="btn btn--flat"
                >

                    Oct

                </button>

                <button type="button"
                        class="btn btn--flat"
                >

                    Nov

                </button>

                <button type="button"
                        class="btn btn--flat"
                >

                    Dec

                </button>

`;


heading($material)
  color: $material.text.primary

theme(heading, "heading")


this.add('CAS_Sync_User_Data_FieldMap', '{}', { type: 'string' });

this.section('CAS_Login_Layout', function () {

this.add('CAS_popup_width', '810', { type: 'int', group: 'CAS', public: true });

this.add('CAS_popup_height', '610', { type: 'int', group: 'CAS', public: true });

this.add('CAS_button_label_text', 'CAS', { type: 'string', group: 'CAS' });

this.add('CAS_button_label_color', '#FFFFFF', { type: 'color', group: 'CAS' });

this.add('CAS_button_color', '#1d74f5', { type: 'color', group: 'CAS' });

this.add('CAS_autoclose', true, { type: 'boolean', group: 'CAS' });

keep keep keep keep replace replace keep keep keep keep keep

this.add('CAS_popup_width', 810, { type: 'int', group: 'CAS', public: true });

this.add('CAS_popup_height', 610, { type: 'int', group: 'CAS', public: true });

import { Meteor } from 'meteor/meteor';
import { ServiceConfiguration } from 'meteor/service-configuration';

import { settings, settingsRegistry } from '../../../settings/server';
import { SystemLogger } from '../../../../server/lib/logger/system';
import type { IServiceProviderOptions } from '../definition/IServiceProviderOptions';
import { SAMLUtils } from './Utils';
import {
	defaultAuthnContextTemplate,
	defaultAuthRequestTemplate,
	defaultLogoutResponseTemplate,
	defaultLogoutRequestTemplate,
	defaultNameIDTemplate,
	defaultIdentifierFormat,
	defaultAuthnContext,
	defaultMetadataTemplate,
	defaultMetadataCertificateTemplate,
} from './constants';

export const getSamlConfigs = function (service: string): Record<string, any> {
	const configs = {
		buttonLabelText: settings.get(`${service}_button_label_text`),
		buttonLabelColor: settings.get(`${service}_button_label_color`),
		buttonColor: settings.get(`${service}_button_color`),
		clientConfig: {
			provider: settings.get(`${service}_provider`),
		},
		entryPoint: settings.get(`${service}_entry_point`),
		idpSLORedirectURL: settings.get(`${service}_idp_slo_redirect_url`),
		usernameNormalize: settings.get(`${service}_username_normalize`),
		immutableProperty: settings.get(`${service}_immutable_property`),
		generateUsername: settings.get(`${service}_generate_username`),
		debug: settings.get(`${service}_debug`),
		nameOverwrite: settings.get(`${service}_name_overwrite`),
		mailOverwrite: settings.get(`${service}_mail_overwrite`),
		issuer: settings.get(`${service}_issuer`),
		logoutBehaviour: settings.get(`${service}_logout_behaviour`),
		defaultUserRole: settings.get(`${service}_default_user_role`),
		secret: {
			privateKey: settings.get(`${service}_private_key`),
			publicCert: settings.get(`${service}_public_cert`),
			// People often overlook the instruction to remove the header and footer of the certificate on this specific setting, so let's do it for them.
			cert: SAMLUtils.normalizeCert((settings.get(`${service}_cert`) as string) || ''),
		},
		signatureValidationType: settings.get(`${service}_signature_validation_type`),
		userDataFieldMap: settings.get(`${service}_user_data_fieldmap`),
		allowedClockDrift: settings.get(`${service}_allowed_clock_drift`),
		customAuthnContext: defaultAuthnContext,
		authnContextComparison: 'exact',
		identifierFormat: defaultIdentifierFormat,
		nameIDPolicyTemplate: defaultNameIDTemplate,
		authnContextTemplate: defaultAuthnContextTemplate,
		authRequestTemplate: defaultAuthRequestTemplate,
		logoutResponseTemplate: defaultLogoutResponseTemplate,
		logoutRequestTemplate: defaultLogoutRequestTemplate,
		metadataCertificateTemplate: defaultMetadataCertificateTemplate,
		metadataTemplate: defaultMetadataTemplate,
		channelsAttributeUpdate: settings.get(`${service}_channels_update`),
		includePrivateChannelsInUpdate: settings.get(`${service}_include_private_channels_update`),
	};

	SAMLUtils.events.emit('loadConfigs', service, configs);
	return configs;
};

export const configureSamlService = function (samlConfigs: Record<string, any>): IServiceProviderOptions {
	let privateCert = null;
	let privateKey = null;

	if (samlConfigs.secret.privateKey && samlConfigs.secret.publicCert) {
		privateKey = samlConfigs.secret.privateKey;
		privateCert = samlConfigs.secret.publicCert;
	} else if (samlConfigs.secret.privateKey || samlConfigs.secret.publicCert) {
		SAMLUtils.error('SAML Service: You must specify both cert and key files.');
	}

	SAMLUtils.updateGlobalSettings(samlConfigs);

	return {
		provider: samlConfigs.clientConfig.provider,
		entryPoint: samlConfigs.entryPoint,
		idpSLORedirectURL: samlConfigs.idpSLORedirectURL,
		issuer: samlConfigs.issuer,
		cert: samlConfigs.secret.cert,
		privateCert,
		privateKey,
		customAuthnContext: samlConfigs.customAuthnContext,
		authnContextComparison: samlConfigs.authnContextComparison,
		defaultUserRole: samlConfigs.defaultUserRole,
		allowedClockDrift: parseInt(samlConfigs.allowedClockDrift) || 0,
		signatureValidationType: samlConfigs.signatureValidationType,
		identifierFormat: samlConfigs.identifierFormat,
		nameIDPolicyTemplate: samlConfigs.nameIDPolicyTemplate,
		authnContextTemplate: samlConfigs.authnContextTemplate,
		authRequestTemplate: samlConfigs.authRequestTemplate,
		logoutResponseTemplate: samlConfigs.logoutResponseTemplate,
		logoutRequestTemplate: samlConfigs.logoutRequestTemplate,
		metadataCertificateTemplate: samlConfigs.metadataCertificateTemplate,
		metadataTemplate: samlConfigs.metadataTemplate,
		callbackUrl: Meteor.absoluteUrl(`_saml/validate/${samlConfigs.clientConfig.provider}`),
	};
};

export const loadSamlServiceProviders = function (): void {
	const serviceName = 'saml';
	const services = settings.getByRegexp(/^(SAML_Custom_)[a-z]+$/i);

	if (!services) {
		return SAMLUtils.setServiceProvidersList([]);
	}

	const providers = services
		.map(([key, value]) => {
			if (value === true) {
				const samlConfigs = getSamlConfigs(key);
				SAMLUtils.log(key);
				ServiceConfiguration.configurations.upsert(
					{
						service: serviceName.toLowerCase(),
					},
					{
						$set: samlConfigs,
					},
				);
				return configureSamlService(samlConfigs);
			}
			ServiceConfiguration.configurations.remove({
				service: serviceName.toLowerCase(),
			});
			return false;
		})
		.filter((e) => e) as IServiceProviderOptions[];

	SAMLUtils.setServiceProvidersList(providers);
};

export const addSamlService = function (name: string): void {
	SystemLogger.warn(`Adding ${name} is deprecated`);
};

export const addSettings = function (name: string): void {
	settingsRegistry.addGroup('SAML', function () {
		this.with(
			{
				tab: 'SAML_Connection',
			},
			function () {
				this.add(`SAML_Custom_${name}`, false, {
					type: 'boolean',
					i18nLabel: 'Accounts_OAuth_Custom_Enable',
				});
				this.add(`SAML_Custom_${name}_provider`, 'provider-name', {
					type: 'string',
					i18nLabel: 'SAML_Custom_Provider',
				});
				this.add(`SAML_Custom_${name}_entry_point`, 'https://example.com/simplesaml/saml2/idp/SSOService.php', {
					type: 'string',
					i18nLabel: 'SAML_Custom_Entry_point',
				});
				this.add(`SAML_Custom_${name}_idp_slo_redirect_url`, 'https://example.com/simplesaml/saml2/idp/SingleLogoutService.php', {
					type: 'string',
					i18nLabel: 'SAML_Custom_IDP_SLO_Redirect_URL',
				});
				this.add(`SAML_Custom_${name}_issuer`, 'https://your-rocket-chat/_saml/metadata/provider-name', {
					type: 'string',
					i18nLabel: 'SAML_Custom_Issuer',
				});
				this.add(`SAML_Custom_${name}_debug`, false, {
					type: 'boolean',
					i18nLabel: 'SAML_Custom_Debug',
				});

				this.section('SAML_Section_2_Certificate', function () {
					this.add(`SAML_Custom_${name}_cert`, '', {
						type: 'string',
						i18nLabel: 'SAML_Custom_Cert',
						multiline: true,
						secret: true,
					});
					this.add(`SAML_Custom_${name}_public_cert`, '', {
						type: 'string',
						multiline: true,
						i18nLabel: 'SAML_Custom_Public_Cert',
					});
					this.add(`SAML_Custom_${name}_signature_validation_type`, 'All', {
						type: 'select',
						values: [
							{ key: 'Response', i18nLabel: 'SAML_Custom_signature_validation_response' },
							{ key: 'Assertion', i18nLabel: 'SAML_Custom_signature_validation_assertion' },
							{ key: 'Either', i18nLabel: 'SAML_Custom_signature_validation_either' },
							{ key: 'All', i18nLabel: 'SAML_Custom_signature_validation_all' },
						],
						i18nLabel: 'SAML_Custom_signature_validation_type',
						i18nDescription: 'SAML_Custom_signature_validation_type_description',
					});
					this.add(`SAML_Custom_${name}_private_key`, '', {
						type: 'string',
						multiline: true,
						i18nLabel: 'SAML_Custom_Private_Key',
						secret: true,
					});
				});
			},
		);

		this.with(
			{
				tab: 'SAML_General',
			},
			function () {
				this.section('SAML_Section_1_User_Interface', function () {
					this.add(`SAML_Custom_${name}_button_label_text`, 'SAML', {
						type: 'string',
						i18nLabel: 'Accounts_OAuth_Custom_Button_Label_Text',
					});
					this.add(`SAML_Custom_${name}_button_label_color`, '#FFFFFF', {
						type: 'string',
						i18nLabel: 'Accounts_OAuth_Custom_Button_Label_Color',
					});
					this.add(`SAML_Custom_${name}_button_color`, '#1d74f5', {
						type: 'string',
						i18nLabel: 'Accounts_OAuth_Custom_Button_Color',
					});
				});

				this.section('SAML_Section_3_Behavior', function () {
					// Settings to customize behavior
					this.add(`SAML_Custom_${name}_generate_username`, false, {
						type: 'boolean',
						i18nLabel: 'SAML_Custom_Generate_Username',
					});
					this.add(`SAML_Custom_${name}_username_normalize`, 'None', {
						type: 'select',
						values: [
							{ key: 'None', i18nLabel: 'SAML_Custom_Username_Normalize_None' },
							{ key: 'Lowercase', i18nLabel: 'SAML_Custom_Username_Normalize_Lowercase' },
						],
						i18nLabel: 'SAML_Custom_Username_Normalize',
					});
					this.add(`SAML_Custom_${name}_immutable_property`, 'EMail', {
						type: 'select',
						values: [
							{ key: 'Username', i18nLabel: 'SAML_Custom_Immutable_Property_Username' },
							{ key: 'EMail', i18nLabel: 'SAML_Custom_Immutable_Property_EMail' },
						],
						i18nLabel: 'SAML_Custom_Immutable_Property',
					});
					this.add(`SAML_Custom_${name}_name_overwrite`, false, {
						type: 'boolean',
						i18nLabel: 'SAML_Custom_name_overwrite',
					});
					this.add(`SAML_Custom_${name}_mail_overwrite`, false, {
						type: 'boolean',
						i18nLabel: 'SAML_Custom_mail_overwrite',
					});
					this.add(`SAML_Custom_${name}_logout_behaviour`, 'SAML', {
						type: 'select',
						values: [
							{ key: 'SAML', i18nLabel: 'SAML_Custom_Logout_Behaviour_Terminate_SAML_Session' },
							{ key: 'Local', i18nLabel: 'SAML_Custom_Logout_Behaviour_End_Only_RocketChat' },
						],
						i18nLabel: 'SAML_Custom_Logout_Behaviour',
					});
					this.add(`SAML_Custom_${name}_channels_update`, false, {
						type: 'boolean',
						i18nLabel: 'SAML_Custom_channels_update',
						i18nDescription: 'SAML_Custom_channels_update_description',
					});
					this.add(`SAML_Custom_${name}_include_private_channels_update`, false, {
						type: 'boolean',
						i18nLabel: 'SAML_Custom_include_private_channels_update',
						i18nDescription: 'SAML_Custom_include_private_channels_update_description',
					});

					this.add(`SAML_Custom_${name}_default_user_role`, 'user', {
						type: 'string',
						i18nLabel: 'SAML_Default_User_Role',
						i18nDescription: 'SAML_Default_User_Role_Description',
					});

					this.add(`SAML_Custom_${name}_allowed_clock_drift`, false, {
						type: 'int',
						invalidValue: 0,
						i18nLabel: 'SAML_Allowed_Clock_Drift',
						i18nDescription: 'SAML_Allowed_Clock_Drift_Description',
					});
				});

				this.section('SAML_Section_5_Mapping', function () {
					// Data Mapping Settings
					this.add(`SAML_Custom_${name}_user_data_fieldmap`, '{"username":"username", "email":"email", "name": "cn"}', {
						type: 'string',
						i18nLabel: 'SAML_Custom_user_data_fieldmap',
						i18nDescription: 'SAML_Custom_user_data_fieldmap_description',
						multiline: true,
					});
				});
			},
		);

		SAMLUtils.events.emit('addSettings', name);
	});
};


import { parse } from 'cookie';

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

API.helperMethods.set('isWidget', function _isWidget() {
	// @ts-expect-error 'this' implicitly has type 'any' because it does not have a type annotation.
	const { headers } = this.request;

	const { rc_room_type: roomType, rc_is_widget: isWidget } = parse(headers.cookie || '');

	const isLivechatRoom = roomType && roomType === 'l';
	return !!(isLivechatRoom && isWidget === 't');
});


import { Meteor } from 'meteor/meteor';
import { Random } from 'meteor/random';
import { TAPi18n } from 'meteor/rocketchat:tap-i18n';

import { slashCommands } from '../../utils/lib/slashCommand';
import { settings } from '../../settings/server';
import { Users } from '../../models/server';
import { api } from '../../../server/sdk/api';

/*
 * Msg is a named function that will replace /msg commands
 */

slashCommands.add({
	command: 'msg',
	callback: function Msg(_command: 'msg', params, item): void {
		const trimmedParams = params.trim();
		const separator = trimmedParams.indexOf(' ');
		const userId = Meteor.userId() as string;
		if (separator === -1) {
			api.broadcast('notify.ephemeralMessage', userId, item.rid, {
				msg: TAPi18n.__('Username_and_message_must_not_be_empty', { lng: settings.get('Language') || 'en' }),
			});
			return;
		}
		const message = trimmedParams.slice(separator + 1);
		const targetUsernameOrig = trimmedParams.slice(0, separator);
		const targetUsername = targetUsernameOrig.replace('@', '');
		const targetUser = Users.findOneByUsernameIgnoringCase(targetUsername);
		if (targetUser == null) {
			const user = Users.findOneById(userId, { fields: { language: 1 } });
			api.broadcast('notify.ephemeralMessage', userId, item.rid, {
				msg: TAPi18n.__('Username_doesnt_exist', {
					postProcess: 'sprintf',
					sprintf: [targetUsernameOrig],
					lng: user?.language || settings.get('Language') || 'en',
				}),
			});
			return;
		}
		const { rid } = Meteor.call('createDirectMessage', targetUsername);
		const msgObject = {
			_id: Random.id(),
			rid,
			msg: message,
		};
		Meteor.call('sendMessage', msgObject);
	},
	options: {
		description: 'Direct_message_someone',
		params: '@username <message>',
		permission: 'create-d',
	},
});


نام

آیا شما می خواید به این مکالمه پایان دهید؟

آیا از پاک شدن تمام اطلاعات شخصی خود اطمینان دارید؟

ایا مطمئن هستید که میخواهید بخش را عوض کنید؟

گفتگو پایان یافت

یک بخش را انتخاب کنید ...

یک بخش را انتخاب کنید

یک گزینه را انتخاب کنید

مکالمه به پایان رسید

{{count}} پیام جدید از {{val, datetime}} قبل

غیرفعال کردن ناتیفیکیشن ها

نادیده گرفتن این هشدار

فایل را اینجا رها کنید

فعال کردن ناتیفیکیشن ها

خطا در بستن گفتگو.

خطا در حذف اطلاعات کاربر.

خطا در ایجاد گفتگوی جدید: {{reason}}

حجم فایل بیشتر از حد مجاز ({{size}}) است.

خطا در آپلود فایل

پایان این گفتگو

حذف اطلاعات من

به <1>menu options → Forget/Remove my personal data</1> بروید تا درخواست حذف بلافاصله ی اطلاعات خود را بدهید

نیاز به کمک در بخش زیر را دارم

اگر هرگونه سوال دیگری دارید، بر روی دکمه ی زیر کلیک کنید تا یک گفتگو آغاز شود

یک پیغام بگذارید

این نوع فایل قابل قبول نیست.

پیام

کوچک کردن گفتگو

نیاز به کمک دارید?

هیچ پشتیبانی برای انتقال در دسترس نیست

لطفاً برای شروع گپ اطلاعاتی را به ما بدهید

لطفا تا در دسترس قرار گرفتن پشتیبان متنظر بمانید..

قدرت گرفته از Rocket.Chat

بازگردانی گفتگو

نام اتاق عوض شد

متشکر از تماس شما با ما

کنترل اطلاعات شما در دست شرکت [Company Name], به آدرس [Company Address] است. برای شروع گفتگو لازم از مقررات عمومی حفاظت از داده اتحادیه اروپا (GDPR) را تایید کنید.

پیام خود را اینجا تایپ کنید

پیام های خوانده نشده

کاربر اضافه شد، توسط

کاربر ترک کرد

کاربر حذف شد، توسط

صف انتظار ...

ما در حال حاضر آنلاین نیستیم. لطفا پیام بگذارید

پیام خود را بنویسید ...

مرورگر شما صوت را پشتیبانی نمی کند

مرورگر شما ویدئو را پشتیبانی نمی کند


keep keep keep replace keep keep keep keep

import { Meteor } from 'meteor/meteor';
import { ServiceConfiguration } from 'meteor/service-configuration';

import { Logger } from '../../logger';
import { settings, settingsRegistry } from '../../settings/server';

export const logger = new Logger('CAS');

Meteor.startup(function () {
	settingsRegistry.addGroup('CAS', function () {
		this.add('CAS_enabled', false, { type: 'boolean', group: 'CAS', public: true });
		this.add('CAS_base_url', '', { type: 'string', group: 'CAS', public: true });
		this.add('CAS_login_url', '', { type: 'string', group: 'CAS', public: true });
		this.add('CAS_version', '1.0', {
			type: 'select',
			values: [
				{ key: '1.0', i18nLabel: '1.0' },
				{ key: '2.0', i18nLabel: '2.0' },
			],
			group: 'CAS',
		});
		this.add('CAS_trust_username', false, {
			type: 'boolean',
			group: 'CAS',
			public: true,
			i18nDescription: 'CAS_trust_username_description',
		});
		// Enable/disable user creation
		this.add('CAS_Creation_User_Enabled', true, { type: 'boolean', group: 'CAS' });

		this.section('Attribute_handling', function () {
			// Enable/disable sync
			this.add('CAS_Sync_User_Data_Enabled', true, { type: 'boolean' });
			// Attribute mapping table
			this.add('CAS_Sync_User_Data_FieldMap', '{}', { type: 'string' });
		});

		this.section('CAS_Login_Layout', function () {
			this.add('CAS_popup_width', '810', { type: 'int', group: 'CAS', public: true });
			this.add('CAS_popup_height', '610', { type: 'int', group: 'CAS', public: true });
			this.add('CAS_button_label_text', 'CAS', { type: 'string', group: 'CAS' });
			this.add('CAS_button_label_color', '#FFFFFF', { type: 'color', group: 'CAS' });
			this.add('CAS_button_color', '#1d74f5', { type: 'color', group: 'CAS' });
			this.add('CAS_autoclose', true, { type: 'boolean', group: 'CAS' });
		});
	});
});

let timer;

function updateServices(/* record*/) {
	if (typeof timer !== 'undefined') {
		Meteor.clearTimeout(timer);
	}

	timer = Meteor.setTimeout(function () {
		const data = {
			// These will pe passed to 'node-cas' as options
			enabled: settings.get('CAS_enabled'),
			base_url: settings.get('CAS_base_url'),
			login_url: settings.get('CAS_login_url'),
			// Rocketchat Visuals
			buttonLabelText: settings.get('CAS_button_label_text'),
			buttonLabelColor: settings.get('CAS_button_label_color'),
			buttonColor: settings.get('CAS_button_color'),
			width: settings.get('CAS_popup_width'),
			height: settings.get('CAS_popup_height'),
			autoclose: settings.get('CAS_autoclose'),
		};

		// Either register or deregister the CAS login service based upon its configuration
		if (data.enabled) {
			logger.info('Enabling CAS login service');
			ServiceConfiguration.configurations.upsert({ service: 'cas' }, { $set: data });
		} else {
			logger.info('Disabling CAS login service');
			ServiceConfiguration.configurations.remove({ service: 'cas' });
		}
	}, 2000);
}

settings.watchByRegex(/^CAS_.+/, (key, value) => {
	updateServices(value);
});


export { default as SwitchDepartment } from './component';
export { default } from './connector';


.chatpal-admin-link {
	text-decoration: underline !important;

	color: red !important;
}

.chatpalProvider .search-form {
	padding: 0 24px;
}

.chatpal_search-loader {
	position: relative;

	margin-top: 50px;
}

.chatpal-search-container {
	display: flex;
	flex-direction: column;
	flex: 1;
}

.chatpal-search-result {
	position: relative;

	overflow: auto;
	overflow-x: hidden;

	flex: 1;

	padding: 0 24px 24px;

	background-color: #f1f1f1;
}

.chatpal-search-typefilter {
	display: flex;
}

.chatpal-search-typefilter li {
	display: flex;
	flex: 0 0 33%;

	height: 35px;
	padding-top: 10px;

	cursor: pointer;

	border-bottom: 4px solid white;
	justify-content: center;
}

.chatpal-search-typefilter li.selected {
	border-bottom: 4px solid #125074;
}

.chatpal-admin-header {
	margin-bottom: 20px;

	font-size: 18px;
}

.chatpal-search-result-header {
	margin: 20px 0;

	font-weight: bold;
}

.chatpal-search-result-single {
	position: relative;

	min-height: 92px;

	margin-bottom: 10px;

	padding: 10px;

	border: 1px solid #e6e6e6;
	background-color: white;
}

.chatpal-search-result-user {
	position: relative;

	min-height: 40px;

	margin-bottom: 10px;

	padding: 10px;

	border: 1px solid #e6e6e6;
	background-color: white;
}

.chatpal-search-result-user .chatpal-avatar {
	position: absolute;

	width: 36px;
	height: 36px;
}

.chatpal-search-result-user .chatpal-avatar .chatpal-avatar-image {
	width: 100%;
	height: 100%;

	background-repeat: no-repeat;
	background-size: contain;
}

.chatpal-search-result-user h2 {
	margin-bottom: 5px;
	padding-left: 45px;
}

.chatpal-search-result-user .direct-message {
	padding-left: 45px;

	font-size: 14px;
	font-weight: bold;
}

.chatpal-channel {
	flex: 1 1 auto;
}

.chatpal-search-result-user .chatpal-channel {
	font-weight: bold;
}

.chatpal-show-more-messages {
	margin-bottom: 20px;

	cursor: pointer;

	text-align: center;

	color: #125074;

	font-size: 14px;
	font-weight: bold;
}

.chatpal-show-more-messages:hover {
	text-decoration: underline;
}

.chatpal-search-result-user .direct-message a {
	color: #125074;
}

.chatpal-search-result-user .direct-message a:hover {
	text-decoration: underline;
}

.chatpal-search-result-single h2 {
	display: flex;

	margin-bottom: 20px;
	align-items: center;
}

.chatpal-search-result-single .chatpal-avatar {
	position: absolute;

	width: 36px;
	height: 36px;
}

.chatpal-search-result-single .chatpal-avatar .chatpal-avatar-image {
	width: 100%;
	height: 100%;

	background-repeat: no-repeat;
	background-size: contain;
}

.chatpal-search-result-single .chatpal-name {
	padding: 0 0 0 46px;

	color: #444444;

	font-size: 14px;

	font-weight: bold;
}

.chatpal-search-result-single .chatpal-date {
	color: #a0a0a0;

	font-size: 12px;

	font-weight: normal;
}

.chatpal-search-result-single .chatpal-time {
	margin-left: 3px;

	color: #a0a0a0;

	font-size: 12px;
}

.chatpal-search-result-single .chatpal-message {
	overflow-x: hidden;

	margin-top: 5px;
	padding: 0 0 0 46px;

	line-height: 20px;
}

.chatpal-search-result-single .chatpal-message em {
	background-color: #faf9c8;

	font-style: normal;
}

.chatpal-search-result-single .chatpal-link {
	display: none;
	flex: 20;

	color: #125074;
}

.chatpal-search-result-single .chatpal-link:hover {
	text-decoration: underline;
}

.chatpal-search-result-single:hover .chatpal-link {
	display: inline-block;
}

.chatpal-paging {
	margin: 30px 0 50px;

	text-align: center;
}

.chatpal-paging-text {
	position: relative;
	top: -2px;

	display: inline-block;
}

.chatpal-paging .chatpal-paging-button {
	display: inline-block;

	cursor: pointer;

	color: #125074;

	font-size: 20px;
}

.chatpal-paging .chatpal-paging-prev {
	padding-right: 10px;
}

.chatpal-paging .chatpal-paging-next {
	padding-left: 10px;
}

.chatpal-search-info {
	margin-top: 40px;
	padding: 10px;
}

.chatpal-search-welcome {
	padding-top: 40px;

	text-align: center;
}

.chatpal-search-result-room h2 {
	padding-left: 0 !important;
}

.chatpal-search-result-list em {
	background-color: #faf9c8;

	font-style: normal;
}

.chatpal-search-result-room .chatpal-link {
	display: none;

	margin-left: 3px;

	color: #125074;
}

.chatpal-search-result-room:hover .chatpal-link {
	display: inline-block;
}

.chatpal-search-result-room .chatpal-link:hover {
	text-decoration: underline;
}

.chatpal-search-pills div {
	display: inline-block;

	margin-top: 5px;

	margin-right: 5px;

	padding: 2px 5px;

	border-radius: 3px;
	background-color: #f1f1f1;
}

.apikey h2 {
	margin-top: 20px;
}

.apikey .key {
	position: relative;

	margin: 20px 0;

	padding: 10px;

	text-align: center;

	background-color: #f1f1f1;

	font-size: 20px;
}

.apikey a {
	text-decoration: underline;
}

.chatpal-suggestion {
	display: flex;

	padding: 10px;
	justify-items: center;
}

.chatpal-suggestion-text {
	flex: 1 1 auto;
}

.chatpal-suggestion-hint {
	display: none;
}

.rocket-search-suggestion-item.active .chatpal-suggestion-hint {
	display: block;
}

.chatpalProvider .rc-input__icon {
	top: 3px;

	left: 10px;

	width: 26px;
}

.chatpalProvider .rc-input__icon-svg {
	font-size: 26px !important;
}

.chatpalProvider .rocket-search-suggestion-item.active,
.chatpalProvider .rocket-search-suggestion-item:hover {
	color: white;
	background-color: #125074;
}


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

const style = {
	minHeight: '250px',
};
const Chart = forwardRef(function Chart(props, ref) {
	return (
		<Box padding='x20' height='x300' {...props}>
			<canvas ref={ref} style={style}></canvas>
		</Box>
	);
});

export default Chart;


this.add('LDAP_Port', 389, { type: 'int', enableQuery });

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

settingsRegistry.addGroup('LDAP', function () {
	const enableQuery = { _id: 'LDAP_Enable', value: true };
	const adOnly = { _id: 'LDAP_Server_Type', value: 'ad' };
	const ldapOnly = { _id: 'LDAP_Server_Type', value: '' };

	this.with({ tab: 'LDAP_Connection' }, function () {
		this.add('LDAP_Enable', false, { type: 'boolean', public: true });

		this.add('LDAP_Server_Type', 'ad', {
			type: 'select',
			public: true,
			enableQuery,
			values: [
				{ key: 'ad', i18nLabel: 'LDAP_Server_Type_AD' },
				{ key: '', i18nLabel: 'LDAP_Server_Type_Other' },
			],
		});

		this.add('LDAP_Host', '', { type: 'string', enableQuery });
		this.add('LDAP_Port', '389', { type: 'int', enableQuery });
		this.add('LDAP_Reconnect', false, { type: 'boolean', enableQuery });

		this.add('LDAP_Login_Fallback', false, { type: 'boolean', enableQuery });

		this.section('LDAP_Connection_Authentication', function () {
			const enableAuthentication = [enableQuery, { _id: 'LDAP_Authentication', value: true }];

			this.add('LDAP_Authentication', false, { type: 'boolean', enableQuery, invalidValue: false });
			this.add('LDAP_Authentication_UserDN', '', {
				type: 'string',
				enableQuery: enableAuthentication,
				secret: true,
				invalidValue: '',
			});
			this.add('LDAP_Authentication_Password', '', {
				type: 'password',
				enableQuery: enableAuthentication,
				secret: true,
				invalidValue: '',
			});
		});

		this.section('LDAP_Connection_Encryption', function () {
			this.add('LDAP_Encryption', 'plain', {
				type: 'select',
				values: [
					{ key: 'plain', i18nLabel: 'No_Encryption' },
					{ key: 'tls', i18nLabel: 'StartTLS' },
					{ key: 'ssl', i18nLabel: 'SSL/LDAPS' },
				],
				enableQuery,
			});

			const enableTLSQuery = [enableQuery, { _id: 'LDAP_Encryption', value: { $in: ['tls', 'ssl'] } }];

			this.add('LDAP_CA_Cert', '', {
				type: 'string',
				multiline: true,
				enableQuery: enableTLSQuery,
				secret: true,
			});
			this.add('LDAP_Reject_Unauthorized', true, { type: 'boolean', enableQuery: enableTLSQuery });
		});

		this.section('LDAP_Connection_Timeouts', function () {
			this.add('LDAP_Timeout', 60000, { type: 'int', enableQuery });
			this.add('LDAP_Connect_Timeout', 1000, { type: 'int', enableQuery });
			this.add('LDAP_Idle_Timeout', 1000, { type: 'int', enableQuery });
		});
	});

	this.with({ tab: 'LDAP_UserSearch' }, function () {
		this.add('LDAP_Find_User_After_Login', true, { type: 'boolean', enableQuery });

		this.section('LDAP_UserSearch_Filter', function () {
			this.add('LDAP_BaseDN', '', {
				type: 'string',
				enableQuery,
			});

			this.add('LDAP_User_Search_Filter', '(objectclass=*)', {
				type: 'string',
				enableQuery,
			});

			this.add('LDAP_User_Search_Scope', 'sub', {
				type: 'string',
				enableQuery,
			});

			this.add('LDAP_AD_User_Search_Field', 'sAMAccountName', {
				i18nLabel: 'LDAP_User_Search_Field',
				i18nDescription: 'LDAP_User_Search_Field_Description',
				type: 'string',
				enableQuery,
				displayQuery: adOnly,
			});
			this.add('LDAP_User_Search_Field', 'uid', {
				type: 'string',
				enableQuery,
				displayQuery: ldapOnly,
			});
			this.add('LDAP_Search_Page_Size', 250, {
				type: 'int',
				enableQuery,
			});

			this.add('LDAP_Search_Size_Limit', 1000, {
				type: 'int',
				enableQuery,
			});
		});

		this.section('LDAP_UserSearch_GroupFilter', function () {
			const groupFilterQuery = [enableQuery, { _id: 'LDAP_Group_Filter_Enable', value: true }];

			this.add('LDAP_Group_Filter_Enable', false, { type: 'boolean', enableQuery });
			this.add('LDAP_Group_Filter_ObjectClass', 'groupOfUniqueNames', {
				type: 'string',
				enableQuery: groupFilterQuery,
			});
			this.add('LDAP_Group_Filter_Group_Id_Attribute', 'cn', {
				type: 'string',
				enableQuery: groupFilterQuery,
			});
			this.add('LDAP_Group_Filter_Group_Member_Attribute', 'uniqueMember', {
				type: 'string',
				enableQuery: groupFilterQuery,
			});
			this.add('LDAP_Group_Filter_Group_Member_Format', '', {
				type: 'string',
				enableQuery: groupFilterQuery,
			});
			this.add('LDAP_Group_Filter_Group_Name', 'ROCKET_CHAT', {
				type: 'string',
				enableQuery: groupFilterQuery,
			});
		});
	});

	this.with({ tab: 'LDAP_DataSync' }, function () {
		this.add('LDAP_Unique_Identifier_Field', 'objectGUID,ibm-entryUUID,GUID,dominoUNID,nsuniqueId,uidNumber,uid', {
			type: 'string',
			enableQuery,
		});

		this.add('LDAP_Merge_Existing_Users', false, {
			type: 'boolean',
			enableQuery,
		});

		this.add('LDAP_Update_Data_On_Login', true, {
			type: 'boolean',
			enableQuery,
		});

		this.add('LDAP_Default_Domain', '', {
			type: 'string',
			enableQuery,
		});

		this.section('LDAP_DataSync_DataMap', function () {
			this.add('LDAP_AD_Username_Field', 'sAMAccountName', {
				type: 'string',
				enableQuery,
				i18nLabel: 'LDAP_Username_Field',
				i18nDescription: 'LDAP_Username_Field_Description',
				displayQuery: adOnly,
			});

			this.add('LDAP_Username_Field', 'uid', {
				type: 'string',
				enableQuery,
				displayQuery: ldapOnly,
			});

			this.add('LDAP_AD_Email_Field', 'mail', {
				i18nLabel: 'LDAP_Email_Field',
				i18nDescription: 'LDAP_Email_Field_Description',
				type: 'string',
				enableQuery,
				displayQuery: adOnly,
			});

			this.add('LDAP_Email_Field', 'mail', {
				type: 'string',
				enableQuery,
				displayQuery: ldapOnly,
			});

			this.add('LDAP_AD_Name_Field', 'cn', {
				i18nLabel: 'LDAP_Name_Field',
				i18nDescription: 'LDAP_Name_Field_Description',
				type: 'string',
				enableQuery,
				displayQuery: adOnly,
			});

			this.add('LDAP_Name_Field', 'cn', {
				type: 'string',
				enableQuery,
				displayQuery: ldapOnly,
			});
		});

		this.section('LDAP_DataSync_Avatar', function () {
			this.add('LDAP_Sync_User_Avatar', true, {
				type: 'boolean',
				enableQuery,
			});

			this.add('LDAP_Avatar_Field', '', {
				type: 'string',
				enableQuery,
			});
		});
	});
});


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

import { useRecordList } from '../../../../hooks/lists/useRecordList';
import { AsyncStatePhase } from '../../../../hooks/useAsyncState';
import { useTabBarClose } from '../../providers/ToolboxProvider';
import { useDiscussionsList } from './useDiscussionsList';

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

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

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

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

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

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

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

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

	return WrappedComponent;
}


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

import { OmnichannelSidenav } from './fragments';

export class OmnichannelDepartaments {
	private readonly page: Page;

	readonly sidenav: OmnichannelSidenav;

	constructor(page: Page) {
		this.page = page;
		this.sidenav = new OmnichannelSidenav(page);
	}

	get inputSearch() {
		return this.page.locator('[placeholder="Search"]');
	}

	get btnNew() {
		return this.page.locator('button.rcx-button >> text="New"');
	}

	get btnEnabled() {
		return this.page.locator('[data-qa="DepartmentEditToggle-Enabled"] span label');
	}

	get inputName() {
		return this.page.locator('[data-qa="DepartmentEditTextInput-Name"]');
	}

	get inputEmail() {
		return this.page.locator('[data-qa="DepartmentEditTextInput-Email"]');
	}

	get btnSave() {
		return this.page.locator('button.rcx-button--primary.rcx-button >> text="Save"');
	}

	get firstRowInTable() {
		return this.page.locator('table tr:first-child td:first-child');
	}

	get btnDeletefirstRowInTable() {
		return this.page.locator('table tr:first-child td:nth-child(6) button');
	}

	get btnModalConfirmDelete() {
		return this.page.locator('#modal-root .rcx-modal .rcx-modal__footer .rcx-button--danger');
	}
}


import type { ILivechatVisitor, ISetting, RocketChatRecordDeleted } from '@rocket.chat/core-typings';
import type { FindPaginated, ILivechatVisitorsModel } from '@rocket.chat/model-typings';
import type {
	AggregationCursor,
	Collection,
	FindCursor,
	Db,
	Document,
	Filter,
	FindOptions,
	UpdateResult,
	IndexDescription,
	DeleteResult,
	UpdateFilter,
} from 'mongodb';
import { Settings } from '@rocket.chat/models';
import { escapeRegExp } from '@rocket.chat/string-helpers';

import { BaseRaw } from './BaseRaw';

export class LivechatVisitorsRaw extends BaseRaw<ILivechatVisitor> implements ILivechatVisitorsModel {
	constructor(db: Db, trash?: Collection<RocketChatRecordDeleted<ILivechatVisitor>>) {
		super(db, 'livechat_visitor', trash);
	}

	protected modelIndexes(): IndexDescription[] {
		return [
			{ key: { token: 1 } },
			{ key: { 'phone.phoneNumber': 1 }, sparse: true },
			{ key: { 'visitorEmails.address': 1 }, sparse: true },
			{ key: { name: 1 }, sparse: true },
			{ key: { username: 1 } },
			{ key: { 'contactMananger.username': 1 }, sparse: true },
		];
	}

	findOneVisitorByPhone(phone: string): Promise<ILivechatVisitor | null> {
		const query = {
			'phone.phoneNumber': phone,
		};

		return this.findOne(query);
	}

	findOneGuestByEmailAddress(emailAddress: string): Promise<ILivechatVisitor | null> {
		const query = {
			'visitorEmails.address': String(emailAddress).toLowerCase(),
		};

		return this.findOne(query);
	}

	/**
	 * Find visitors by _id
	 * @param {string} token - Visitor token
	 */
	findById(_id: string, options: FindOptions<ILivechatVisitor>): FindCursor<ILivechatVisitor> {
		const query = {
			_id,
		};

		return this.find(query, options);
	}

	findVisitorByToken(token: string): FindCursor<ILivechatVisitor> {
		const query = {
			token,
		};

		return this.find(query);
	}

	getVisitorByToken(token: string, options: FindOptions<ILivechatVisitor>): Promise<ILivechatVisitor | null> {
		const query = {
			token,
		};

		return this.findOne(query, options);
	}

	getVisitorsBetweenDate({ start, end, department }: { start: Date; end: Date; department: string }): FindCursor<ILivechatVisitor> {
		const query = {
			_updatedAt: {
				$gte: new Date(start),
				$lt: new Date(end),
			},
			...(department && department !== 'undefined' && { department }),
		};

		return this.find(query, { projection: { _id: 1 } });
	}

	async getNextVisitorUsername(): Promise<string> {
		const query = {
			_id: 'Livechat_guest_count',
		};

		const update: UpdateFilter<ISetting> = {
			$inc: {
				// @ts-expect-error looks like the typings of ISetting.value conflict with this type of update
				value: 1,
			},
		};

		// TODO remove dependency from another model - this logic should be inside a service/function
		const livechatCount = await Settings.findOneAndUpdate(query, update, { returnDocument: 'after' });

		if (!livechatCount.value) {
			throw new Error("Can't find Livechat_guest_count setting");
		}

		return `guest-${livechatCount.value.value}`;
	}

	findByNameRegexWithExceptionsAndConditions<P = ILivechatVisitor>(
		searchTerm: string,
		exceptions: string[] = [],
		conditions: Filter<ILivechatVisitor> = {},
		options: FindOptions<P extends ILivechatVisitor ? ILivechatVisitor : P> = {},
	): AggregationCursor<
		P & {
			custom_name: string;
		}
	> {
		if (!Array.isArray(exceptions)) {
			exceptions = [exceptions];
		}

		const nameRegex = new RegExp(`^${escapeRegExp(searchTerm).trim()}`, 'i');

		const match = {
			$match: {
				name: nameRegex,
				_id: {
					$nin: exceptions,
				},
				...conditions,
			},
		};

		const { projection, sort, skip, limit } = options;
		const project = {
			$project: {
				// TODO: move this logic to client
				custom_name: { $concat: ['$username', ' - ', '$name'] },
				...projection,
			},
		};

		const order = { $sort: sort || { name: 1 } };
		const params: Record<string, unknown>[] = [match, order, skip && { $skip: skip }, limit && { $limit: limit }, project].filter(
			Boolean,
		) as Record<string, unknown>[];

		return this.col.aggregate(params);
	}

	/**
	 * Find visitors by their email or phone or username or name
	 */
	findPaginatedVisitorsByEmailOrPhoneOrNameOrUsername(
		emailOrPhone: string,
		nameOrUsername: RegExp,
		options: FindOptions<ILivechatVisitor>,
	): FindPaginated<FindCursor<ILivechatVisitor>> {
		const query = {
			$or: [
				{
					'visitorEmails.address': emailOrPhone,
				},
				{
					'phone.phoneNumber': emailOrPhone,
				},
				{
					name: nameOrUsername,
				},
				{
					username: nameOrUsername,
				},
			],
		};

		return this.findPaginated(query, options);
	}

	async updateLivechatDataByToken(
		token: string,
		key: string,
		value: unknown,
		overwrite = true,
	): Promise<UpdateResult | Document | boolean> {
		const query = {
			token,
		};

		if (!overwrite) {
			const user = await this.getVisitorByToken(token, { projection: { livechatData: 1 } });
			if (user?.livechatData && typeof user.livechatData[key] !== 'undefined') {
				return true;
			}
		}

		const update = {
			$set: {
				[`livechatData.${key}`]: value,
			},
		};

		return this.updateOne(query, update);
	}

	updateLastAgentByToken(token: string, lastAgent: ILivechatVisitor['lastAgent']): Promise<Document | UpdateResult> {
		const query = {
			token,
		};

		const update = {
			$set: {
				lastAgent,
			},
		};

		return this.updateOne(query, update);
	}

	updateById(_id: string, update: UpdateFilter<ILivechatVisitor>): Promise<Document | UpdateResult> {
		return this.updateOne({ _id }, update);
	}

	saveGuestById(
		_id: string,
		data: { name?: string; username?: string; email?: string; phone?: string; livechatData: { [k: string]: any } },
	): Promise<UpdateResult | Document | boolean> {
		const setData: DeepWriteable<UpdateFilter<ILivechatVisitor>['$set']> = {};
		const unsetData: DeepWriteable<UpdateFilter<ILivechatVisitor>['$unset']> = {};

		if (data.name) {
			if (data.name?.trim()) {
				setData.name = data.name.trim();
			} else {
				unsetData.name = 1;
			}
		}

		if (data.email) {
			if (data.email?.trim()) {
				setData.visitorEmails = [{ address: data.email.trim() }];
			} else {
				unsetData.visitorEmails = 1;
			}
		}

		if (data.phone) {
			if (data.phone?.trim()) {
				setData.phone = [{ phoneNumber: data.phone.trim() }];
			} else {
				unsetData.phone = 1;
			}
		}

		if (data.livechatData) {
			Object.keys(data.livechatData).forEach((key) => {
				const value = data.livechatData[key]?.trim();
				if (value) {
					setData[`livechatData.${key}`] = value;
				} else {
					unsetData[`livechatData.${key}`] = 1;
				}
			});
		}

		const update: UpdateFilter<ILivechatVisitor> = {
			...(Object.keys(setData).length && { $set: setData as UpdateFilter<ILivechatVisitor>['$set'] }),
			...(Object.keys(unsetData).length && { $unset: unsetData as UpdateFilter<ILivechatVisitor>['$unset'] }),
		};

		if (!Object.keys(update).length) {
			return Promise.resolve(true);
		}

		return this.updateOne({ _id }, update);
	}

	removeDepartmentById(_id: string): Promise<UpdateResult> {
		return this.updateOne({ _id }, { $unset: { department: 1 } });
	}

	removeById(_id: string): Promise<DeleteResult> {
		return this.deleteOne({ _id });
	}

	saveGuestEmailPhoneById(_id: string, emails: string[], phones: string[]): Promise<UpdateResult | Document | void> {
		const update: DeepWriteable<UpdateFilter<ILivechatVisitor>> = {
			$addToSet: {},
		};

		const saveEmail = ([] as string[])
			.concat(emails)
			.filter((email) => email?.trim())
			.map((email) => ({ address: email }));

		if (update.$addToSet && saveEmail.length > 0) {
			update.$addToSet.visitorEmails = { $each: saveEmail };
		}

		const savePhone = ([] as string[])
			.concat(phones)
			.filter((phone) => phone?.trim().replace(/[^\d]/g, ''))
			.map((phone) => ({ phoneNumber: phone }));

		if (update.$addToSet && savePhone.length > 0) {
			update.$addToSet.phone = { $each: savePhone };
		}

		if (!Object.keys(update).length) {
			return Promise.resolve();
		}

		return this.updateOne({ _id }, update as UpdateFilter<ILivechatVisitor>);
	}

	removeContactManagerByUsername(manager: string): Promise<Document | UpdateResult> {
		return this.updateMany(
			{
				contactManager: {
					username: manager,
				},
			},
			{
				$unset: {
					contactManager: true,
				},
			},
		);
	}
}

type DeepWriteable<T> = { -readonly [P in keyof T]: DeepWriteable<T[P]> };