<commit_before>;(function (diff_match_patch) {
  function cledit (contentElt, scrollElt, windowParam) {
    scrollElt = scrollElt || contentElt
    var editor = {
      $contentElt: contentElt,
      $scrollElt: scrollElt,
      $window: windowParam || window,
      $keystrokes: [],
      $markers: {}
    }
    editor.$document = editor.$window.document
    cledit.Utils.createEventHooks(editor)
    var debounce = cledit.Utils.debounce

    editor.toggleEditable = function (isEditable) {
      if (isEditable === undefined) {
        isEditable = !contentElt.contentEditable
      }
      contentElt.contentEditable = isEditable
    }
    editor.toggleEditable(true)

    function getTextContent () {
      var textContent = contentElt.textContent.replace(/\r\n?/g, '\n') // Mac/DOS to Unix
      if (textContent.slice(-1) !== '\n') {
        textContent += '\n'
      }
      return textContent
    }

    var lastTextContent = getTextContent()
    var highlighter = new cledit.Highlighter(editor)

    var sectionList

    function parseSections (content, isInit) {
      sectionList = highlighter.parseSections(content, isInit)
      editor.$allElements = Array.prototype.slice.call(contentElt.querySelectorAll('.cledit-section *'))
      return sectionList
    }

    // Used to detect editor changes
    var watcher = new cledit.Watcher(editor, checkContentChange)
    watcher.startWatching()

    /* eslint-disable new-cap */
    var diffMatchPatch = new window.diff_match_patch()
    /* eslint-enable new-cap */
    var selectionMgr = new cledit.SelectionMgr(editor)

    function adjustCursorPosition (force) {
      selectionMgr.saveSelectionState(true, true, force)
    }

    function replaceContent (selectionStart, selectionEnd, replacement) {
      var min = Math.min(selectionStart, selectionEnd)
      var max = Math.max(selectionStart, selectionEnd)
      var range = selectionMgr.createRange(min, max)
      var rangeText = '' + range
      // Range can contain a br element, which is not taken into account in rangeText
      if (rangeText.length === max - min && rangeText === replacement) {
        return
      }
      range.deleteContents()
      range.insertNode(editor.$document.createTextNode(replacement))
      return range
    }

    var ignoreUndo = false
    var noContentFix = false

    function setContent (value, noUndo, maxStartOffset) {
      var textContent = getTextContent()
      maxStartOffset = maxStartOffset !== undefined && maxStartOffset < textContent.length ? maxStartOffset : textContent.length - 1
      var startOffset = Math.min(
        diffMatchPatch.diff_commonPrefix(textContent, value),
        maxStartOffset
      )
      var endOffset = Math.min(
        diffMatchPatch.diff_commonSuffix(textContent, value),
        textContent.length - startOffset,
        value.length - startOffset
      )
      var replacement = value.substring(startOffset, value.length - endOffset)
      var range = replaceContent(startOffset, textContent.length - endOffset, replacement)
      if (range) {
        ignoreUndo = noUndo
        noContentFix = true
      }
      return {
        start: startOffset,
        end: value.length - endOffset,
        range: range
      }
    }

    function replace (selectionStart, selectionEnd, replacement) {
      undoMgr.setDefaultMode('single')
      replaceContent(selectionStart, selectionEnd, replacement)
      var endOffset = selectionStart + replacement.length
      selectionMgr.setSelectionStartEnd(endOffset, endOffset)
      selectionMgr.updateCursorCoordinates(true)
    }

    function replaceAll (search, replacement) {
      undoMgr.setDefaultMode('single')
      var textContent = getTextContent()
      var value = textContent.replace(search, replacement)
      if (value !== textContent) {
        var offset = editor.setContent(value)
        selectionMgr.setSelectionStartEnd(offset.end, offset.end)
        selectionMgr.updateCursorCoordinates(true)
      }
    }

    function focus () {
      selectionMgr.restoreSelection()
    }

    var undoMgr = new cledit.UndoMgr(editor)

    function addMarker (marker) {
      editor.$markers[marker.id] = marker
    }

    function removeMarker (marker) {
      delete editor.$markers[marker.id]
    }

    var triggerSpellCheck = debounce(function () {
      var selection = editor.$window.getSelection()
      if (!selectionMgr.hasFocus || highlighter.isComposing || selectionMgr.selectionStart !== selectionMgr.selectionEnd || !selection.modify) {
        return
      }
      // Hack for Chrome to trigger the spell checker
      if (selectionMgr.selectionStart) {
        selection.modify('move', 'backward', 'character')
        selection.modify('move', 'forward', 'character')
      } else {
        selection.modify('move', 'forward', 'character')
        selection.modify('move', 'backward', 'character')
      }
    }, 10)

    function checkContentChange (mutations) {
      watcher.noWatch(function () {
        var removedSections = []
        var modifiedSections = []

        function markModifiedSection (node) {
          while (node && node !== contentElt) {
            if (node.section) {
              var array = node.parentNode ? modifiedSections : removedSections
              return array.indexOf(node.section) === -1 && array.push(node.section)
            }
            node = node.parentNode
          }
        }

        mutations.cl_each(function (mutation) {
          markModifiedSection(mutation.target)
          mutation.addedNodes.cl_each(markModifiedSection)
          mutation.removedNodes.cl_each(markModifiedSection)
        })
        highlighter.fixContent(modifiedSections, removedSections, noContentFix)
        noContentFix = false
      })

      var newTextContent = getTextContent()
      var diffs = diffMatchPatch.diff_main(lastTextContent, newTextContent)
      editor.$markers.cl_each(function (marker) {
        marker.adjustOffset(diffs)
      })

      selectionMgr.saveSelectionState()
      var sectionList = parseSections(newTextContent)
      editor.$trigger('contentChanged', newTextContent, diffs, sectionList)
      if (!ignoreUndo) {
        undoMgr.addDiffs(lastTextContent, newTextContent, diffs)
        undoMgr.setDefaultMode('typing')
        undoMgr.saveState()
      }
      ignoreUndo = false
      lastTextContent = newTextContent
      triggerSpellCheck()
    }

    function setSelection (start, end) {
      end = end === undefined ? start : end
      selectionMgr.setSelectionStartEnd(start, end)
      selectionMgr.updateCursorCoordinates()
    }

    function keydownHandler (handler) {
      return function (evt) {
        if (
          evt.which !== 17 && // Ctrl
          evt.which !== 91 && // Cmd
          evt.which !== 18 && // Alt
          evt.which !== 16 // Shift
        ) {
          handler(evt)
        }
      }
    }

    function tryDestroy () {
      if (!editor.$window.document.contains(contentElt)) {
        watcher.stopWatching()
        editor.$window.removeEventListener('keydown', windowKeydownListener)
        editor.$window.removeEventListener('mouseup', windowMouseupListener)
        editor.$trigger('destroy')
        return true
      }
    }

    // In case of Ctrl/Cmd+A outside the editor element
    function windowKeydownListener (evt) {
      if (!tryDestroy()) {
        keydownHandler(function () {
          adjustCursorPosition()
        })(evt)
      }
    }
    editor.$window.addEventListener('keydown', windowKeydownListener, false)

    // Mouseup can happen outside the editor element
    function windowMouseupListener () {
      if (!tryDestroy()) {
        selectionMgr.saveSelectionState(true, false)
      }
    }
    editor.$window.addEventListener('mouseup', windowMouseupListener)
    // This can also provoke selection changes and does not fire mouseup event on Chrome/OSX
    contentElt.addEventListener('contextmenu', selectionMgr.saveSelectionState.cl_bind(selectionMgr, true, false))

    contentElt.addEventListener('keydown', keydownHandler(function (evt) {
      selectionMgr.saveSelectionState()
      adjustCursorPosition()

      // Perform keystroke
      var textContent = getTextContent()
      var min = Math.min(selectionMgr.selectionStart, selectionMgr.selectionEnd)
      var max = Math.max(selectionMgr.selectionStart, selectionMgr.selectionEnd)
      var state = {
        before: textContent.slice(0, min),
        after: textContent.slice(max),
        selection: textContent.slice(min, max),
        isBackwardSelection: selectionMgr.selectionStart > selectionMgr.selectionEnd
      }
      editor.$keystrokes.cl_some(function (keystroke) {
        if (keystroke.handler(evt, state, editor)) {
          editor.setContent(state.before + state.selection + state.after, false, min)
          min = state.before.length
          max = min + state.selection.length
          selectionMgr.setSelectionStartEnd(
            state.isBackwardSelection ? max : min,
            state.isBackwardSelection ? min : max
          )
          return true
        }
      })
    }), false)

    contentElt.addEventListener('compositionstart', function () {
      highlighter.isComposing++
    }, false)

    contentElt.addEventListener('compositionend', function () {
      setTimeout(function () {
        highlighter.isComposing && highlighter.isComposing--
      }, 0)
    }, false)

    contentElt.addEventListener('paste', function (evt) {
      undoMgr.setCurrentMode('single')
      evt.preventDefault()
      var data
      var clipboardData = evt.clipboardData
      if (clipboardData) {
        data = clipboardData.getData('text/plain')
      } else {
        clipboardData = editor.$window.clipboardData
        data = clipboardData && clipboardData.getData('Text')
      }
      if (!data) {
        return
      }
      replace(selectionMgr.selectionStart, selectionMgr.selectionEnd, data)
      adjustCursorPosition()
    }, false)

    contentElt.addEventListener('cut', function () {
      undoMgr.setCurrentMode('single')
      adjustCursorPosition()
    }, false)

    contentElt.addEventListener('focus', function () {
      selectionMgr.hasFocus = true
      editor.$trigger('focus')
    }, false)

    contentElt.addEventListener('blur', function () {
      selectionMgr.hasFocus = false
      editor.$trigger('blur')
    }, false)

    function addKeystroke (keystrokes) {
      if (!Array.isArray(keystrokes)) {
        keystrokes = [keystrokes]
      }
      editor.$keystrokes = editor.$keystrokes.concat(keystrokes).sort(function (keystroke1, keystroke2) {
        return keystroke1.priority - keystroke2.priority
      })
    }
    addKeystroke(cledit.defaultKeystrokes)

    editor.selectionMgr = selectionMgr
    editor.undoMgr = undoMgr
    editor.highlighter = highlighter
    editor.watcher = watcher
    editor.adjustCursorPosition = adjustCursorPosition
    editor.setContent = setContent
    editor.replace = replace
    editor.replaceAll = replaceAll
    editor.getContent = getTextContent
    editor.focus = focus
    editor.setSelection = setSelection
    editor.addKeystroke = addKeystroke
    editor.addMarker = addMarker
    editor.removeMarker = removeMarker

    editor.init = function (options) {
      options = ({
        cursorFocusRatio: 0.5,
        highlighter: function (text) {
          return text.replace(/&/g, '&').replace(/</g, '<').replace(/\u00a0/g, ' ')
        },
        sectionDelimiter: ''
      }).cl_extend(options || {})
      editor.options = options

      if (options.content !== undefined) {
        lastTextContent = options.content.toString()
        if (lastTextContent.slice(-1) !== '\n') {
          lastTextContent += '\n'
        }
      }

      var sectionList = parseSections(lastTextContent, true)
      editor.$trigger('contentChanged', lastTextContent, [0, lastTextContent], sectionList)
      if (options.selectionStart !== undefined && options.selectionEnd !== undefined) {
        editor.setSelection(options.selectionStart, options.selectionEnd)
      } else {
        selectionMgr.saveSelectionState()
      }
      undoMgr.init(options)

      if (options.scrollTop !== undefined) {
        scrollElt.scrollTop = options.scrollTop
      }
    }

    return editor
  }

  window.cledit = cledit
})(window.diff_match_patch)
<commit_msg>Better line breaks sanitization<commit_after>;(function (diff_match_patch) {
  function cledit (contentElt, scrollElt, windowParam) {
    scrollElt = scrollElt || contentElt
    var editor = {
      $contentElt: contentElt,
      $scrollElt: scrollElt,
      $window: windowParam || window,
      $keystrokes: [],
      $markers: {}
    }
    editor.$document = editor.$window.document
    cledit.Utils.createEventHooks(editor)
    var debounce = cledit.Utils.debounce

    editor.toggleEditable = function (isEditable) {
      if (isEditable === undefined) {
        isEditable = !contentElt.contentEditable
      }
      contentElt.contentEditable = isEditable
    }
    editor.toggleEditable(true)

    function getTextContent () {
      var textContent = contentElt.textContent.replace(/\r[\n\u0085]?|[\u2424\u2028\u0085]/g, '\n') // Markdown-it sanitization (Mac/DOS to Unix)
      if (textContent.slice(-1) !== '\n') {
        textContent += '\n'
      }
      return textContent
    }

    var lastTextContent = getTextContent()
    var highlighter = new cledit.Highlighter(editor)

    var sectionList

    function parseSections (content, isInit) {
      sectionList = highlighter.parseSections(content, isInit)
      editor.$allElements = Array.prototype.slice.call(contentElt.querySelectorAll('.cledit-section *'))
      return sectionList
    }

    // Used to detect editor changes
    var watcher = new cledit.Watcher(editor, checkContentChange)
    watcher.startWatching()

    /* eslint-disable new-cap */
    var diffMatchPatch = new window.diff_match_patch()
    /* eslint-enable new-cap */
    var selectionMgr = new cledit.SelectionMgr(editor)

    function adjustCursorPosition (force) {
      selectionMgr.saveSelectionState(true, true, force)
    }

    function replaceContent (selectionStart, selectionEnd, replacement) {
      var min = Math.min(selectionStart, selectionEnd)
      var max = Math.max(selectionStart, selectionEnd)
      var range = selectionMgr.createRange(min, max)
      var rangeText = '' + range
      // Range can contain a br element, which is not taken into account in rangeText
      if (rangeText.length === max - min && rangeText === replacement) {
        return
      }
      range.deleteContents()
      range.insertNode(editor.$document.createTextNode(replacement))
      return range
    }

    var ignoreUndo = false
    var noContentFix = false

    function setContent (value, noUndo, maxStartOffset) {
      var textContent = getTextContent()
      maxStartOffset = maxStartOffset !== undefined && maxStartOffset < textContent.length ? maxStartOffset : textContent.length - 1
      var startOffset = Math.min(
        diffMatchPatch.diff_commonPrefix(textContent, value),
        maxStartOffset
      )
      var endOffset = Math.min(
        diffMatchPatch.diff_commonSuffix(textContent, value),
        textContent.length - startOffset,
        value.length - startOffset
      )
      var replacement = value.substring(startOffset, value.length - endOffset)
      var range = replaceContent(startOffset, textContent.length - endOffset, replacement)
      if (range) {
        ignoreUndo = noUndo
        noContentFix = true
      }
      return {
        start: startOffset,
        end: value.length - endOffset,
        range: range
      }
    }

    function replace (selectionStart, selectionEnd, replacement) {
      undoMgr.setDefaultMode('single')
      replaceContent(selectionStart, selectionEnd, replacement)
      var endOffset = selectionStart + replacement.length
      selectionMgr.setSelectionStartEnd(endOffset, endOffset)
      selectionMgr.updateCursorCoordinates(true)
    }

    function replaceAll (search, replacement) {
      undoMgr.setDefaultMode('single')
      var textContent = getTextContent()
      var value = textContent.replace(search, replacement)
      if (value !== textContent) {
        var offset = editor.setContent(value)
        selectionMgr.setSelectionStartEnd(offset.end, offset.end)
        selectionMgr.updateCursorCoordinates(true)
      }
    }

    function focus () {
      selectionMgr.restoreSelection()
    }

    var undoMgr = new cledit.UndoMgr(editor)

    function addMarker (marker) {
      editor.$markers[marker.id] = marker
    }

    function removeMarker (marker) {
      delete editor.$markers[marker.id]
    }

    var triggerSpellCheck = debounce(function () {
      var selection = editor.$window.getSelection()
      if (!selectionMgr.hasFocus || highlighter.isComposing || selectionMgr.selectionStart !== selectionMgr.selectionEnd || !selection.modify) {
        return
      }
      // Hack for Chrome to trigger the spell checker
      if (selectionMgr.selectionStart) {
        selection.modify('move', 'backward', 'character')
        selection.modify('move', 'forward', 'character')
      } else {
        selection.modify('move', 'forward', 'character')
        selection.modify('move', 'backward', 'character')
      }
    }, 10)

    function checkContentChange (mutations) {
      watcher.noWatch(function () {
        var removedSections = []
        var modifiedSections = []

        function markModifiedSection (node) {
          while (node && node !== contentElt) {
            if (node.section) {
              var array = node.parentNode ? modifiedSections : removedSections
              return array.indexOf(node.section) === -1 && array.push(node.section)
            }
            node = node.parentNode
          }
        }

        mutations.cl_each(function (mutation) {
          markModifiedSection(mutation.target)
          mutation.addedNodes.cl_each(markModifiedSection)
          mutation.removedNodes.cl_each(markModifiedSection)
        })
        highlighter.fixContent(modifiedSections, removedSections, noContentFix)
        noContentFix = false
      })

      var newTextContent = getTextContent()
      var diffs = diffMatchPatch.diff_main(lastTextContent, newTextContent)
      editor.$markers.cl_each(function (marker) {
        marker.adjustOffset(diffs)
      })

      selectionMgr.saveSelectionState()
      var sectionList = parseSections(newTextContent)
      editor.$trigger('contentChanged', newTextContent, diffs, sectionList)
      if (!ignoreUndo) {
        undoMgr.addDiffs(lastTextContent, newTextContent, diffs)
        undoMgr.setDefaultMode('typing')
        undoMgr.saveState()
      }
      ignoreUndo = false
      lastTextContent = newTextContent
      triggerSpellCheck()
    }

    function setSelection (start, end) {
      end = end === undefined ? start : end
      selectionMgr.setSelectionStartEnd(start, end)
      selectionMgr.updateCursorCoordinates()
    }

    function keydownHandler (handler) {
      return function (evt) {
        if (
          evt.which !== 17 && // Ctrl
          evt.which !== 91 && // Cmd
          evt.which !== 18 && // Alt
          evt.which !== 16 // Shift
        ) {
          handler(evt)
        }
      }
    }

    function tryDestroy () {
      if (!editor.$window.document.contains(contentElt)) {
        watcher.stopWatching()
        editor.$window.removeEventListener('keydown', windowKeydownListener)
        editor.$window.removeEventListener('mouseup', windowMouseupListener)
        editor.$trigger('destroy')
        return true
      }
    }

    // In case of Ctrl/Cmd+A outside the editor element
    function windowKeydownListener (evt) {
      if (!tryDestroy()) {
        keydownHandler(function () {
          adjustCursorPosition()
        })(evt)
      }
    }
    editor.$window.addEventListener('keydown', windowKeydownListener, false)

    // Mouseup can happen outside the editor element
    function windowMouseupListener () {
      if (!tryDestroy()) {
        selectionMgr.saveSelectionState(true, false)
      }
    }
    editor.$window.addEventListener('mouseup', windowMouseupListener)
    // This can also provoke selection changes and does not fire mouseup event on Chrome/OSX
    contentElt.addEventListener('contextmenu', selectionMgr.saveSelectionState.cl_bind(selectionMgr, true, false))

    contentElt.addEventListener('keydown', keydownHandler(function (evt) {
      selectionMgr.saveSelectionState()
      adjustCursorPosition()

      // Perform keystroke
      var textContent = getTextContent()
      var min = Math.min(selectionMgr.selectionStart, selectionMgr.selectionEnd)
      var max = Math.max(selectionMgr.selectionStart, selectionMgr.selectionEnd)
      var state = {
        before: textContent.slice(0, min),
        after: textContent.slice(max),
        selection: textContent.slice(min, max),
        isBackwardSelection: selectionMgr.selectionStart > selectionMgr.selectionEnd
      }
      editor.$keystrokes.cl_some(function (keystroke) {
        if (keystroke.handler(evt, state, editor)) {
          editor.setContent(state.before + state.selection + state.after, false, min)
          min = state.before.length
          max = min + state.selection.length
          selectionMgr.setSelectionStartEnd(
            state.isBackwardSelection ? max : min,
            state.isBackwardSelection ? min : max
          )
          return true
        }
      })
    }), false)

    contentElt.addEventListener('compositionstart', function () {
      highlighter.isComposing++
    }, false)

    contentElt.addEventListener('compositionend', function () {
      setTimeout(function () {
        highlighter.isComposing && highlighter.isComposing--
      }, 0)
    }, false)

    contentElt.addEventListener('paste', function (evt) {
      undoMgr.setCurrentMode('single')
      evt.preventDefault()
      var data
      var clipboardData = evt.clipboardData
      if (clipboardData) {
        data = clipboardData.getData('text/plain')
      } else {
        clipboardData = editor.$window.clipboardData
        data = clipboardData && clipboardData.getData('Text')
      }
      if (!data) {
        return
      }
      replace(selectionMgr.selectionStart, selectionMgr.selectionEnd, data)
      adjustCursorPosition()
    }, false)

    contentElt.addEventListener('cut', function () {
      undoMgr.setCurrentMode('single')
      adjustCursorPosition()
    }, false)

    contentElt.addEventListener('focus', function () {
      selectionMgr.hasFocus = true
      editor.$trigger('focus')
    }, false)

    contentElt.addEventListener('blur', function () {
      selectionMgr.hasFocus = false
      editor.$trigger('blur')
    }, false)

    function addKeystroke (keystrokes) {
      if (!Array.isArray(keystrokes)) {
        keystrokes = [keystrokes]
      }
      editor.$keystrokes = editor.$keystrokes.concat(keystrokes).sort(function (keystroke1, keystroke2) {
        return keystroke1.priority - keystroke2.priority
      })
    }
    addKeystroke(cledit.defaultKeystrokes)

    editor.selectionMgr = selectionMgr
    editor.undoMgr = undoMgr
    editor.highlighter = highlighter
    editor.watcher = watcher
    editor.adjustCursorPosition = adjustCursorPosition
    editor.setContent = setContent
    editor.replace = replace
    editor.replaceAll = replaceAll
    editor.getContent = getTextContent
    editor.focus = focus
    editor.setSelection = setSelection
    editor.addKeystroke = addKeystroke
    editor.addMarker = addMarker
    editor.removeMarker = removeMarker

    editor.init = function (options) {
      options = ({
        cursorFocusRatio: 0.5,
        highlighter: function (text) {
          return text.replace(/&/g, '&').replace(/</g, '<').replace(/\u00a0/g, ' ')
        },
        sectionDelimiter: ''
      }).cl_extend(options || {})
      editor.options = options

      if (options.content !== undefined) {
        lastTextContent = options.content.toString()
        if (lastTextContent.slice(-1) !== '\n') {
          lastTextContent += '\n'
        }
      }

      var sectionList = parseSections(lastTextContent, true)
      editor.$trigger('contentChanged', lastTextContent, [0, lastTextContent], sectionList)
      if (options.selectionStart !== undefined && options.selectionEnd !== undefined) {
        editor.setSelection(options.selectionStart, options.selectionEnd)
      } else {
        selectionMgr.saveSelectionState()
      }
      undoMgr.init(options)

      if (options.scrollTop !== undefined) {
        scrollElt.scrollTop = options.scrollTop
      }
    }

    return editor
  }

  window.cledit = cledit
})(window.diff_match_patch)
<|endoftext|>


<commit_before>// @flow

import React, { useCallback, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { FlatList, Text, TouchableOpacity, View } from 'react-native';
import { useDispatch } from 'react-redux';

import { openDialog } from '../../../base/dialog';
import { Icon, IconArrowDown, IconArrowUp } from '../../../base/icons';

import BreakoutRoomContextMenu from './BreakoutRoomContextMenu';
import BreakoutRoomParticipantItem from './BreakoutRoomParticipantItem';
import styles from './styles';

type Props = {

    /**
     * Room to display.
     */
    room: Object
}

/**
 * Returns a key for a passed item of the list.
 *
 * @param {Object} item - The participant.
 * @returns {string} - The user ID.
 */
function _keyExtractor(item: Object) {
    return item.jid;
}

export const CollapsibleRoom = ({ room }: Props) => {
    const dispatch = useDispatch();
    const [ collapsed, setCollapsed ] = useState(false);
    const { t } = useTranslation();
    const _toggleCollapsed = useCallback(() => {
        setCollapsed(!collapsed);
    }, [ collapsed ]);
    const _openContextMenu = useCallback(() => {
        dispatch(openDialog(BreakoutRoomContextMenu, { room }));
    }, [ room ]);

    return (
        <View>
            <TouchableOpacity
                onLongPress = { _openContextMenu }
                onPress = { _toggleCollapsed }
                style = { styles.collapsibleRoom }>
                <TouchableOpacity
                    onPress = { _toggleCollapsed }
                    style = { styles.arrowIcon }>
                    <Icon
                        size = { 18 }
                        src = { collapsed ? IconArrowDown : IconArrowUp } />
                </TouchableOpacity>
                <Text style = { styles.roomName }>
                    {`${room.name || t('breakoutRooms.mainRoom')} (${Object.values(room.participants || {}).length})`}
                </Text>
            </TouchableOpacity>
            {!collapsed && <FlatList
                bounces = { false }
                data = { Object.values(room.participants || {}) }
                horizontal = { false }
                keyExtractor = { _keyExtractor }
                // eslint-disable-next-line react/jsx-no-bind
                renderItem = { item => <BreakoutRoomParticipantItem item = { item } /> }
                showsHorizontalScrollIndicator = { false }
                windowSize = { 2 } />}
        </View>
    );
};
<commit_msg>fix(rn,breakout-rooms) fix not rendering display names<commit_after>// @flow

import React, { useCallback, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { FlatList, Text, TouchableOpacity, View } from 'react-native';
import { useDispatch } from 'react-redux';

import { openDialog } from '../../../base/dialog';
import { Icon, IconArrowDown, IconArrowUp } from '../../../base/icons';

import BreakoutRoomContextMenu from './BreakoutRoomContextMenu';
import BreakoutRoomParticipantItem from './BreakoutRoomParticipantItem';
import styles from './styles';

type Props = {

    /**
     * Room to display.
     */
    room: Object
}

/**
 * Returns a key for a passed item of the list.
 *
 * @param {Object} item - The participant.
 * @returns {string} - The user ID.
 */
function _keyExtractor(item: Object) {
    return item.jid;
}

export const CollapsibleRoom = ({ room }: Props) => {
    const dispatch = useDispatch();
    const [ collapsed, setCollapsed ] = useState(false);
    const { t } = useTranslation();
    const _toggleCollapsed = useCallback(() => {
        setCollapsed(!collapsed);
    }, [ collapsed ]);
    const _openContextMenu = useCallback(() => {
        dispatch(openDialog(BreakoutRoomContextMenu, { room }));
    }, [ room ]);

    return (
        <View>
            <TouchableOpacity
                onLongPress = { _openContextMenu }
                onPress = { _toggleCollapsed }
                style = { styles.collapsibleRoom }>
                <TouchableOpacity
                    onPress = { _toggleCollapsed }
                    style = { styles.arrowIcon }>
                    <Icon
                        size = { 18 }
                        src = { collapsed ? IconArrowDown : IconArrowUp } />
                </TouchableOpacity>
                <Text style = { styles.roomName }>
                    {`${room.name || t('breakoutRooms.mainRoom')} (${Object.values(room.participants || {}).length})`}
                </Text>
            </TouchableOpacity>
            {!collapsed && <FlatList
                bounces = { false }
                data = { Object.values(room.participants || {}) }
                horizontal = { false }
                keyExtractor = { _keyExtractor }
                // eslint-disable-next-line react/jsx-no-bind
                renderItem = { ({ item: participant }) => <BreakoutRoomParticipantItem item = { participant } /> }
                showsHorizontalScrollIndicator = { false }
                windowSize = { 2 } />}
        </View>
    );
};
<|endoftext|>


<commit_before>// 获取数据并绑定
;
var api_url = 'http://localhost/wfp'
    , request_url = {
        'r' : api_url + '/rural/{m}/{q}', // 乡村
        's' : api_url + '/staplefood/{m}/{q}', // 主食
        'v' : api_url + '/vegetables/{m}{q}', // 蔬菜
        'f' : api_url + '/fruit/{m}/{q}', // 水果
        'a' : api_url + '/activity/{m}/{q}', // 活动
        'sn' : api_url + '/token/{autosignin}/{cert}', // 登录
        'u' : api_url + '/user' // 用户相关接口
    };
var hash = window.location.hash, q = window.location.search;
if (q && q.length > 1)
{
    q = q.substring(1);
}
if (hash && hash.length > 1)
{
    hash = hash.substring(1);
}

var commentItem = '

{commenter}{date}

{content}

';

// 获取画面初始数据
if (q&&hash)
$.restGet(request_url[hash], {'m': 'init', 'q': q}, function(data){
    console.log(data);
    if (data.code != 0)
    {
        $.error(data.msg, 5000);
    }
    // 统计信息
    var statistics = data.body.statistics;
    if (statistics)
    {
        var $post_meta = $('div#post-meta');
        for (var meta in statistics)
        {
            $post_meta.find('div.' + meta + ' .count, div.' + meta + ' .count1, div.' + meta + ' .comment-count').html(statistics[meta]);
        }
    }
    // 评论列表
    var commentList = data.body.commentList;
    // 无评论
    if (!commentList || commentList.length == 0)
    {
        $('#comments>.section-title:first').html('抢沙发');
        return;
    }
    var commentlist = $('ol.commentlist'), len = commentList.length;
    $('#comments>.section-title:first').html(len + ' 评论');
    for (var i = 0; i < len; i++)
    {
        var comment = commentList[i];
        var _item = null;
        for ( var opt in comment )
        {
            _item = (_item||commentItem).replace('{' + opt + '}', comment[opt]);
        }
        commentlist.append(_item, true);
    }
});

// 登录
$('#btn_Login').click(function(){
    // TODO 空判断
    var _form = document.forms['form_sign'];
    $.signIn(request_url.sn, {
        's_u': _form['s_u'].value,
        's_p': _form['s_p'].value,
        'autosignin': _form['autosignin'].checked
    }, function(data) {
        if (data.code != 0)
        {
            // 10次登录失败则清除本地缓存数据及cookie
            if (sessionStorage.signerror)
            {
                sessionStorage.signerror = Number(sessionStorage.signerror) + 1;
            } else sessionStorage.signerror = 1;
            if (sessionStorage.signerror >= 10)
            {
                clearStorage();
            }
            $.error(data.msg, 3000);
        }
        if (sessionStorage.signerror) sessionStorage.signerror = 0;
        // 缓存用户信息
        localStorage.removeItem('_user');
        localStorage.setItem('s_u', document.forms['form_sign']['s_u'].value);
        // 不自动登录的情况
        if (data.body.expires == 0)
        {
            sessionStorage.setItem('_user', JSON.stringify(data.body.user));
            // 自动登录永久保存
        } else {
            localStorage.setItem('_user', JSON.stringify(data.body.user));
        }
        // 转回到原画面,未指定画面时回主画面
        var from = window.location.search, _f;
        if (from && from.indexOf('?from=') === 0)
        {
            _f = from.substring(6);
        }
        if (!_f || _f.indexOf('login.html') >= 0) {
            _f = 'index.shtml';
        }
        window.location.href = _f;
    });
});

// 退出登录
$('#signout').click(function(){
	// TODO 二期加入未付款订单提示
	// Clear local data
	sessionStorage.clear();
	clearCookie();
	window.location.reload(); // 从缓存中装载文档
	//window.location.reload(true); // 绕过缓存从服务器上重新下载该文档
});

var getUser = function() {
    var _user = sessionStorage._user||localStorage._user;
    if (!_user)
    {
        var _tk__ = $.cookie('_tk__');
        if (_tk__)
        {
            // 同步获取
            $.ajaxSetup('async', false);
            var data = $.restGet(request_url.u + '/getInfoByToken');
            if (data.code != 0)
            {
                clearCookie();
                $.info('未登录或登录已过期.', 3000);
                var pathname = window.location.pathname;
                window.location.href = 'login.html?from=' + pathname.substring(pathname.lastIndexOf('/')+1);
            }
            sessionStorage.setItem('_user', JSON.stringify(data.body));
            _user = data.body.user;
            $.ajaxSetup('async', true);
        } else {
            $.info('未登录或登录已过期.', 3000);
            var pathname = window.location.pathname;
            window.location.href = 'login.html?from=' + pathname.substring(pathname.lastIndexOf('/')+1);
        }

    } else {
        _user = JSON.parse(_user);
    }
    return _user;
};

var clearStorage = function() {
    sessionStorage.clear();
    localStorage.clear();
	clearCookie();
};

var clearCookie = function(k) {
	if (k)
	{
		$.removeCookie(k);
	}
	else
	{
		var $c = $.cookie();
		for (var c in $c )
		{
			$.removeCookie(c.name);
		}
	}
};
<commit_msg>update<commit_after>// 获取数据并绑定
;
var api_url = 'http://localhost/wfp'
    , request_url = {
        'r' : api_url + '/rural/{m}/{q}', // 乡村
        's' : api_url + '/staplefood/{m}/{q}', // 主食
        'v' : api_url + '/vegetables/{m}{q}', // 蔬菜
        'f' : api_url + '/fruit/{m}/{q}', // 水果
        'a' : api_url + '/activity/{m}/{q}', // 活动
        'sn' : api_url + '/token/{autosignin}/{cert}', // 登录
        'u' : api_url + '/user' // 用户相关接口
    };
var hash = window.location.hash, q = window.location.search;
if (q && q.length > 1)
{
    q = q.substring(1);
}
if (hash && hash.length > 1)
{
    hash = hash.substring(1);
}

var commentItem = '

{commenter}{date}

{content}

';

// 获取画面初始数据
if (q&&hash)
$.restGet(request_url[hash], {'m': 'init', 'q': q}, function(data){
    console.log(data);
    if (data.code != 0)
    {
        $.error(data.msg, 5000);
    }
    // 统计信息
    var statistics = data.body.statistics;
    if (statistics)
    {
        var $post_meta = $('div#post-meta');
        for (var meta in statistics)
        {
            $post_meta.find('div.' + meta + ' .count, div.' + meta + ' .count1, div.' + meta + ' .comment-count').html(statistics[meta]);
        }
    }
    // 评论列表
    var commentList = data.body.commentList;
    // 无评论
    if (!commentList || commentList.length == 0)
    {
        $('#comments>.section-title:first').html('抢沙发');
        return;
    }
    var commentlist = $('ol.commentlist'), len = commentList.length;
    $('#comments>.section-title:first').html(len + ' 评论');
    for (var i = 0; i < len; i++)
    {
        var comment = commentList[i];
        var _item = null;
        for ( var opt in comment )
        {
            _item = (_item||commentItem).replace('{' + opt + '}', comment[opt]);
        }
        commentlist.append(_item, true);
    }
});

// 登录
$('#btn_Login').click(function(){
    // TODO 空判断
    var _form = document.forms['form_sign'];
    $.signIn(request_url.sn, {
        's_u': _form['s_u'].value,
        's_p': _form['s_p'].value,
        'autosignin': _form['autosignin'].checked
    }, function(data) {
        if (data.code != 0)
        {
            // 10次登录失败则清除本地缓存数据及cookie
            if (sessionStorage.signerror)
            {
                sessionStorage.signerror = Number(sessionStorage.signerror) + 1;
            } else sessionStorage.signerror = 1;
            if (sessionStorage.signerror >= 10)
            {
                clearStorage();
            }
            $.error(data.msg, 3000);
        }
        if (sessionStorage.signerror) sessionStorage.signerror = 0;
        // 缓存用户信息
        localStorage.removeItem('_user');
        localStorage.setItem('s_u', document.forms['form_sign']['s_u'].value);
        // 不自动登录的情况
        if (data.body.expires == 0)
        {
            sessionStorage.setItem('_user', JSON.stringify(data.body.user));
            // 自动登录永久保存
        } else {
            localStorage.setItem('_user', JSON.stringify(data.body.user));
        }
        // 转回到原画面,未指定画面时回主画面
        var from = window.location.search, _f;
        if (from && from.indexOf('?from=') === 0)
        {
            _f = from.substring(6);
        }
        if (!_f || _f.indexOf('login.html') >= 0) {
            _f = 'index.shtml';
        }
        window.location.href = _f;
    });
});

// 退出登录
$('#signout').click(function(){
	// TODO 二期加入未付款订单提示
	// Clear local data
	sessionStorage.clear();
	clearCookie();
	window.location.reload(); // 从缓存中装载文档
	//window.location.reload(true); // 绕过缓存从服务器上重新下载该文档
});

var getUser = function() {
    var _user = sessionStorage._user||localStorage._user;
    /*if (!_user)
    {
        var _tk__ = $.cookie('_tk__');
        if (_tk__)
        {
            // 同步获取
            $.ajaxSetup('async', false);
            var data = $.restGet(request_url.u + '/getInfoByToken');
            if (data.code != 0)
            {
                clearCookie();
                $.info('未登录或登录已过期.', 3000);
                var pathname = window.location.pathname;
                window.location.href = 'login.html?from=' + pathname.substring(pathname.lastIndexOf('/')+1);
            }
            sessionStorage.setItem('_user', JSON.stringify(data.body));
            _user = data.body.user;
            $.ajaxSetup('async', true);
        } else {
            $.info('未登录或登录已过期.', 3000);
            var pathname = window.location.pathname;
            window.location.href = 'login.html?from=' + pathname.substring(pathname.lastIndexOf('/')+1);
        }

    } else {
        _user = JSON.parse(_user);
    }*/
	if (_user)
	{
		_user = JSON.parse(_user);
	}
    return _user;
};

var clearStorage = function() {
    sessionStorage.clear();
    localStorage.clear();
	clearCookie();
};

var clearCookie = function(k) {
	if (k)
	{
		$.removeCookie(k);
	}
	else
	{
		var $c = $.cookie();
		for (var c in $c )
		{
			$.removeCookie(c.name);
		}
	}
};
<|endoftext|>


<commit_before>/*
 * Copyright (c) 2017-2018 Digital Bazaar, Inc. All rights reserved.
 */
'use strict';

const constants = require('../constants');
const util = require('../util');
const Helper = require('../Helper');

// TODO: reorganize this class further and make it more obvious which
// methods need to be extended in proof plugins

// TODO: make signature and verification code (and potentially other code)
// more DRY, especially wrt. plugins having reimplement functionality

module.exports = class LinkedDataSignature {
  constructor(injector, algorithm) {
    this.injector = injector;
    this.algorithm = algorithm;
    this.helper = new Helper(injector);
  }

  async canonize(input, options) {
    const jsonld = this.injector.use('jsonld');
    const opts = {
      algorithm: 'URDNA2015',
      format: 'application/n-quads',
      expansionMap: options.expansionMap,
      skipExpansion: options.skipExpansion === true
    };
    if(options.documentLoader) {
      opts.documentLoader = options.documentLoader;
    }
    return jsonld.canonize(input, opts);
  }

  async createVerifyData(input, options) {
    // TODO: frame before getting signature, not just compact? considerations:
    // should the assumption be (for this library) that the signature is on
    // the top-level object and thus framing is unnecessary?
    const jsonld = this.injector.use('jsonld');
    const opts = {expansionMap: options.expansionMap};
    if(options.documentLoader) {
      opts.documentLoader = options.documentLoader;
    }
    const [expanded = {}] = await jsonld.expand(input, opts);

    // TODO: will need to preserve `proof` when chained signature
    // option is used and implemented in the future

    // delete the existing proofs(s) prior to canonicalization
    delete expanded[constants.SECURITY_PROOF_URL];

    // ensure signature values are removed from proof node
    const proof = await this.sanitizeProofNode(options.proof, options);

    // concatenate hash of c14n proof options and hash of c14n document
    const c14nProofOptions = await this.canonize(proof, options);
    const canonizeOptions = {...options, skipExpansion: true};
    const c14nDocument = await this.canonize(expanded, canonizeOptions);
    return {
      data: this._sha256(c14nProofOptions).getBytes() +
        this._sha256(c14nDocument).getBytes(),
      encoding: 'binary'
    };
  }

  async sanitizeProofNode(proof, options) {
    // `jws`,`signatureValue`,`proofValue` must not be included in the proof
    // options
    proof = util.deepClone(proof);
    delete proof.jws;
    delete proof.signatureValue;
    delete proof.proofValue;
    return proof;
  }

  async sign(input, options = {}) {
    // copy options for setting defaults
    options = {...options};

    // validate common options
    if(options.creator !== undefined && typeof options.creator !== 'string') {
      throw new TypeError('"options.creator" must be a URL string.');
    }
    if(options.domain !== undefined && typeof options.domain !== 'string') {
      throw new TypeError('"options.domain" must be a string.');
    }
    if(options.nonce !== undefined && typeof options.nonce !== 'string') {
      throw new TypeError('"options.nonce" must be a string.');
    }

    // disallow dropping properties when expanding by default
    if(options.expansionMap !== false) {
      options.expansionMap = info => {
        if(info.unmappedProperty) {
          throw new Error('The property "' +
             info.unmappedProperty + '" in the input ' +
            'was not defined in the context.');
        }
      };
    }

    // build proof (aka signature options)
    let proof;
    if(options.proof) {
      // use proof JSON-LD document passed to API
      const jsonld = this.injector.use('jsonld');
      const opts = {expansionMap: options.expansionMap};
      if(options.documentLoader) {
        opts.documentLoader = options.documentLoader;
      }
      proof = await jsonld.compact(
        options.proof, constants.SECURITY_CONTEXT_URL, opts);
    } else {
      // create proof JSON-LD document
      proof = {'@context': constants.SECURITY_CONTEXT_URL};
    }

    // set default `now` date if not given in `proof` or `options`
    if(proof.created === undefined && options.date === undefined) {
      options.date = new Date();
    }

    // ensure date is in string format
    if(options.date !== undefined && typeof options.date !== 'string') {
      // TODO: parse non-string date and force to w3c format?
      options.date = util.w3cDate(options.date);
    }

    // ensure algorithm is set
    proof.type = options.algorithm;

    // add API overrides
    if(options.date !== undefined) {
      proof.created = options.date;
    }
    if(options.creator !== undefined) {
      proof.creator = options.creator;
    }
    if(options.domain !== undefined) {
      proof.domain = options.domain;
    }
    if(options.nonce !== undefined) {
      proof.nonce = options.nonce;
    }

    // add fields from proofPurpose
    // the proof going into the handler is compacted in the SECURITY_CONTEXT
    // and the handler *must* maintain this form. Handlers that introduce terms
    // that are not in the SECURITY_CONTEXT *must* compact the proof using the
    // SECURITY_CONTEXT before returning.
    const {proofPurposeHandler, purposeParameters = {}} = options;
    if(proofPurposeHandler) {
      if(!(purposeParameters && typeof purposeParameters === 'object')) {
        throw new TypeError('"options.purposeParameters" must be an object.');
      }
      proof = await proofPurposeHandler.createProof({
        input, proof, purposeParameters,
        documentLoader: options.documentLoader});
    }

    // produce data to sign
    options.proof = proof;
    const verifyData = await this.createVerifyData(input, options);
    // create proof node
    const proofNode = await this.createProofNode(verifyData, options);
    // attach proof node
    return this.attachProofNode(input, proofNode, options);
  }

  async createProofNode(verifyData, options) {
    const proof = options.proof;
    proof.jws = await this.createSignatureValue(verifyData, options);
    return proof;
  }

  async attachProofNode(input, proofNode, options) {
    // compact proof node to match input context
    const tmp = {
      'https://w3id.org/security#proof': {
        '@graph': proofNode
      }
    };
    const jsonld = this.injector.use('jsonld');
    const ctx = jsonld.getValues(input, '@context');
    const opts = {expansionMap: options.expansionMap};
    if(options.documentLoader) {
      opts.documentLoader = options.documentLoader;
    }
    const compactProofNode = await jsonld.compact(tmp, ctx, opts);

    // TODO: it is unclear how the signature would be easily added without
    // reshaping the input... so perhaps this library should just require
    // the caller to accept that the signature will be added to the top
    // level of the input

    // attach signature node to cloned input and return it
    const output = util.deepClone(input);
    delete compactProofNode['@context'];
    const proofKey = Object.keys(compactProofNode)[0];
    jsonld.addValue(output, proofKey, compactProofNode[proofKey]);
    return output;
  }

  async verify(framed, options) {
    options = {...options};

    const proof = framed.signature || framed.proof;
    proof['@context'] = framed['@context'];

    const maxTimestampDelta = (15 * 60);
    // destructure options
    let {
      checkNonce = () => (
        proof.nonce === null || proof.nonce === undefined),
      checkDomain = () => (
        proof.domain === null || proof.domain === undefined),
      checkTimestamp = () => {
        const now = Date.now();
        const delta = maxTimestampDelta * 1000;
        const created = Date.parse(proof.created);
        if(created < (now - delta) || created > (now + delta)) {
          throw new Error('The digital signature timestamp is out of range.');
        }
        return true;
      },
      checkKey = this.helper.checkKey.bind(this.helper),
      publicKey: getPublicKey = this.helper.getPublicKey.bind(this.helper)
    } = options;

    // normalize function options
    if(checkNonce === false) {
      // not checking nonce, so return true
      checkNonce = () => true;
    }
    if(checkDomain === false) {
      // not checking domain, so return true
      checkDomain = () => true;
    }
    if(checkTimestamp === false) {
      // not checking timestamp, so return true
      checkTimestamp = () => true;
    }
    if(typeof getPublicKey !== 'function') {
      const key = getPublicKey;
      getPublicKey = keyId => {
        if(keyId !== key.id) {
          throw new Error('Public key not found.');
        }
        return key;
      };
    }
    checkNonce = util.normalizeAsyncFn(checkNonce, 2);
    checkDomain = util.normalizeAsyncFn(checkDomain, 2);
    checkTimestamp = util.normalizeAsyncFn(checkTimestamp, 2);
    checkKey = util.normalizeAsyncFn(checkKey, 2);
    getPublicKey = util.normalizeAsyncFn(getPublicKey, 2);
    // run nonce, domain, and timestamp checks in parallel
    const checks = await Promise.all([
      checkNonce(proof.nonce, options),
      checkDomain(proof.domain, options),
      checkTimestamp(proof.date, options)
    ]);

    if(!checks[0]) {
      throw new Error('The nonce is invalid.');
    }
    if(!checks[1]) {
      throw new Error('The domain is invalid.');
    }
    if(!checks[2]) {
      throw new Error('The timestamp is invalid.');
    }
    const keyOptions = {
      ...options,
      proof,
      keyType: this.requiredKeyType
    };
    // get public key
    const publicKey = await getPublicKey(proof.creator, keyOptions);
    // TODO: should be able to override revocation check to ensure that
    // signatures made prior to the revocation check could potentially still
    // be verified

    // ensure key is not revoked
    if(publicKey.revoked !== undefined) {
      throw new Error(
        'The document was signed with a key that has been revoked.');
    }

    // ensure key is trusted before proceeding
    const isKeyTrusted = await checkKey(publicKey, keyOptions);
    if(!isKeyTrusted) {
      throw new Error('The document was not signed with a trusted key.');
    }

    // validate key
    await this.validateKey(publicKey, keyOptions);
    // verify input
    const verifyData = await this.createVerifyData(
      framed, {
        ...options,
        date: proof.created,
        nonce: proof.nonce,
        domain: proof.domain,
        proof
      });

    // Check that the proofPurpose is valid
    const {purpose} = options;
    if(purpose) {
      const {proofPurposeHandler, purposeParameters, documentLoader} = options;
      const result = await proofPurposeHandler.validate(
        {document: framed, proof, purposeParameters, documentLoader});
      if(!result.verified) {
        throw result.error;
      }
    }

    // All other proof validation checks
    return this.verifyProofNode(
      verifyData, proof,
      {...options, publicKey});
  }

  async verifyProofNode(verifyData, proof, options) {
    throw new Error(
      '"verifyProofNode" must be implemented in a derived class.');
  }

  // TODO: use node `crypto` and Buffers in node environment
  // returns a forge buffer
  _sha256(str, encoding) {
    // browser or other environment
    const forge = this.injector.use('forge');
    const md = forge.md.sha256.create();
    md.update(str, encoding || 'utf8');
    return md.digest();
  }
};
<commit_msg>Verify proof node before verifying proof purpose.<commit_after>/*
 * Copyright (c) 2017-2018 Digital Bazaar, Inc. All rights reserved.
 */
'use strict';

const constants = require('../constants');
const util = require('../util');
const Helper = require('../Helper');

// TODO: reorganize this class further and make it more obvious which
// methods need to be extended in proof plugins

// TODO: make signature and verification code (and potentially other code)
// more DRY, especially wrt. plugins having reimplement functionality

module.exports = class LinkedDataSignature {
  constructor(injector, algorithm) {
    this.injector = injector;
    this.algorithm = algorithm;
    this.helper = new Helper(injector);
  }

  async canonize(input, options) {
    const jsonld = this.injector.use('jsonld');
    const opts = {
      algorithm: 'URDNA2015',
      format: 'application/n-quads',
      expansionMap: options.expansionMap,
      skipExpansion: options.skipExpansion === true
    };
    if(options.documentLoader) {
      opts.documentLoader = options.documentLoader;
    }
    return jsonld.canonize(input, opts);
  }

  async createVerifyData(input, options) {
    // TODO: frame before getting signature, not just compact? considerations:
    // should the assumption be (for this library) that the signature is on
    // the top-level object and thus framing is unnecessary?
    const jsonld = this.injector.use('jsonld');
    const opts = {expansionMap: options.expansionMap};
    if(options.documentLoader) {
      opts.documentLoader = options.documentLoader;
    }
    const [expanded = {}] = await jsonld.expand(input, opts);

    // TODO: will need to preserve `proof` when chained signature
    // option is used and implemented in the future

    // delete the existing proofs(s) prior to canonicalization
    delete expanded[constants.SECURITY_PROOF_URL];

    // ensure signature values are removed from proof node
    const proof = await this.sanitizeProofNode(options.proof, options);

    // concatenate hash of c14n proof options and hash of c14n document
    const c14nProofOptions = await this.canonize(proof, options);
    const canonizeOptions = {...options, skipExpansion: true};
    const c14nDocument = await this.canonize(expanded, canonizeOptions);
    return {
      data: this._sha256(c14nProofOptions).getBytes() +
        this._sha256(c14nDocument).getBytes(),
      encoding: 'binary'
    };
  }

  async sanitizeProofNode(proof, options) {
    // `jws`,`signatureValue`,`proofValue` must not be included in the proof
    // options
    proof = util.deepClone(proof);
    delete proof.jws;
    delete proof.signatureValue;
    delete proof.proofValue;
    return proof;
  }

  async sign(input, options = {}) {
    // copy options for setting defaults
    options = {...options};

    // validate common options
    if(options.creator !== undefined && typeof options.creator !== 'string') {
      throw new TypeError('"options.creator" must be a URL string.');
    }
    if(options.domain !== undefined && typeof options.domain !== 'string') {
      throw new TypeError('"options.domain" must be a string.');
    }
    if(options.nonce !== undefined && typeof options.nonce !== 'string') {
      throw new TypeError('"options.nonce" must be a string.');
    }

    // disallow dropping properties when expanding by default
    if(options.expansionMap !== false) {
      options.expansionMap = info => {
        if(info.unmappedProperty) {
          throw new Error('The property "' +
             info.unmappedProperty + '" in the input ' +
            'was not defined in the context.');
        }
      };
    }

    // build proof (aka signature options)
    let proof;
    if(options.proof) {
      // use proof JSON-LD document passed to API
      const jsonld = this.injector.use('jsonld');
      const opts = {expansionMap: options.expansionMap};
      if(options.documentLoader) {
        opts.documentLoader = options.documentLoader;
      }
      proof = await jsonld.compact(
        options.proof, constants.SECURITY_CONTEXT_URL, opts);
    } else {
      // create proof JSON-LD document
      proof = {'@context': constants.SECURITY_CONTEXT_URL};
    }

    // set default `now` date if not given in `proof` or `options`
    if(proof.created === undefined && options.date === undefined) {
      options.date = new Date();
    }

    // ensure date is in string format
    if(options.date !== undefined && typeof options.date !== 'string') {
      // TODO: parse non-string date and force to w3c format?
      options.date = util.w3cDate(options.date);
    }

    // ensure algorithm is set
    proof.type = options.algorithm;

    // add API overrides
    if(options.date !== undefined) {
      proof.created = options.date;
    }
    if(options.creator !== undefined) {
      proof.creator = options.creator;
    }
    if(options.domain !== undefined) {
      proof.domain = options.domain;
    }
    if(options.nonce !== undefined) {
      proof.nonce = options.nonce;
    }

    // add fields from proofPurpose
    // the proof going into the handler is compacted in the SECURITY_CONTEXT
    // and the handler *must* maintain this form. Handlers that introduce terms
    // that are not in the SECURITY_CONTEXT *must* compact the proof using the
    // SECURITY_CONTEXT before returning.
    const {proofPurposeHandler, purposeParameters = {}} = options;
    if(proofPurposeHandler) {
      if(!(purposeParameters && typeof purposeParameters === 'object')) {
        throw new TypeError('"options.purposeParameters" must be an object.');
      }
      proof = await proofPurposeHandler.createProof({
        input, proof, purposeParameters,
        documentLoader: options.documentLoader});
    }

    // produce data to sign
    options.proof = proof;
    const verifyData = await this.createVerifyData(input, options);
    // create proof node
    const proofNode = await this.createProofNode(verifyData, options);
    // attach proof node
    return this.attachProofNode(input, proofNode, options);
  }

  async createProofNode(verifyData, options) {
    const proof = options.proof;
    proof.jws = await this.createSignatureValue(verifyData, options);
    return proof;
  }

  async attachProofNode(input, proofNode, options) {
    // compact proof node to match input context
    const tmp = {
      'https://w3id.org/security#proof': {
        '@graph': proofNode
      }
    };
    const jsonld = this.injector.use('jsonld');
    const ctx = jsonld.getValues(input, '@context');
    const opts = {expansionMap: options.expansionMap};
    if(options.documentLoader) {
      opts.documentLoader = options.documentLoader;
    }
    const compactProofNode = await jsonld.compact(tmp, ctx, opts);

    // TODO: it is unclear how the signature would be easily added without
    // reshaping the input... so perhaps this library should just require
    // the caller to accept that the signature will be added to the top
    // level of the input

    // attach signature node to cloned input and return it
    const output = util.deepClone(input);
    delete compactProofNode['@context'];
    const proofKey = Object.keys(compactProofNode)[0];
    jsonld.addValue(output, proofKey, compactProofNode[proofKey]);
    return output;
  }

  async verify(framed, options) {
    options = {...options};

    const proof = framed.signature || framed.proof;
    proof['@context'] = framed['@context'];

    const maxTimestampDelta = (15 * 60);
    // destructure options
    let {
      checkNonce = () => (
        proof.nonce === null || proof.nonce === undefined),
      checkDomain = () => (
        proof.domain === null || proof.domain === undefined),
      checkTimestamp = () => {
        const now = Date.now();
        const delta = maxTimestampDelta * 1000;
        const created = Date.parse(proof.created);
        if(created < (now - delta) || created > (now + delta)) {
          throw new Error('The digital signature timestamp is out of range.');
        }
        return true;
      },
      checkKey = this.helper.checkKey.bind(this.helper),
      publicKey: getPublicKey = this.helper.getPublicKey.bind(this.helper)
    } = options;

    // normalize function options
    if(checkNonce === false) {
      // not checking nonce, so return true
      checkNonce = () => true;
    }
    if(checkDomain === false) {
      // not checking domain, so return true
      checkDomain = () => true;
    }
    if(checkTimestamp === false) {
      // not checking timestamp, so return true
      checkTimestamp = () => true;
    }
    if(typeof getPublicKey !== 'function') {
      const key = getPublicKey;
      getPublicKey = keyId => {
        if(keyId !== key.id) {
          throw new Error('Public key not found.');
        }
        return key;
      };
    }
    checkNonce = util.normalizeAsyncFn(checkNonce, 2);
    checkDomain = util.normalizeAsyncFn(checkDomain, 2);
    checkTimestamp = util.normalizeAsyncFn(checkTimestamp, 2);
    checkKey = util.normalizeAsyncFn(checkKey, 2);
    getPublicKey = util.normalizeAsyncFn(getPublicKey, 2);
    // run nonce, domain, and timestamp checks in parallel
    const checks = await Promise.all([
      checkNonce(proof.nonce, options),
      checkDomain(proof.domain, options),
      checkTimestamp(proof.date, options)
    ]);

    if(!checks[0]) {
      throw new Error('The nonce is invalid.');
    }
    if(!checks[1]) {
      throw new Error('The domain is invalid.');
    }
    if(!checks[2]) {
      throw new Error('The timestamp is invalid.');
    }
    const keyOptions = {
      ...options,
      proof,
      keyType: this.requiredKeyType
    };
    // get public key
    const publicKey = await getPublicKey(proof.creator, keyOptions);
    // TODO: should be able to override revocation check to ensure that
    // signatures made prior to the revocation check could potentially still
    // be verified

    // ensure key is not revoked
    if(publicKey.revoked !== undefined) {
      throw new Error(
        'The document was signed with a key that has been revoked.');
    }

    // ensure key is trusted before proceeding
    const isKeyTrusted = await checkKey(publicKey, keyOptions);
    if(!isKeyTrusted) {
      throw new Error('The document was not signed with a trusted key.');
    }

    // validate key
    await this.validateKey(publicKey, keyOptions);
    // verify input
    const verifyData = await this.createVerifyData(
      framed, {
        ...options,
        date: proof.created,
        nonce: proof.nonce,
        domain: proof.domain,
        proof
      });

    // verify proof node (i.e. check signature)
    const verified = await this.verifyProofNode(
      verifyData, proof,
      {...options, publicKey});
    if(!verified) {
      return false;
    }

    // Check that the proofPurpose is valid
    const {purpose} = options;
    if(purpose) {
      const {proofPurposeHandler, purposeParameters, documentLoader} = options;
      const result = await proofPurposeHandler.validate(
        {document: framed, proof, purposeParameters, documentLoader});
      if(!result.verified) {
        throw result.error;
      }
    }

    return true;
  }

  async verifyProofNode(verifyData, proof, options) {
    throw new Error(
      '"verifyProofNode" must be implemented in a derived class.');
  }

  // TODO: use node `crypto` and Buffers in node environment
  // returns a forge buffer
  _sha256(str, encoding) {
    // browser or other environment
    const forge = this.injector.use('forge');
    const md = forge.md.sha256.create();
    md.update(str, encoding || 'utf8');
    return md.digest();
  }
};
<|endoftext|>


<commit_before>function Definition(canvasID) {

    var self = this;
    var canvas = document.getElementById(canvasID);
    var context = canvas.getContext("2d");
    var $canvas = $(canvas);

    $canvas.mousedown(function (e) {

        if (self.center == undefined) {
            self.center = { x: e.offsetX, y: e.offsetY };
        }
        else if (self.circle==undefined) {

            self.radius = Math.round(distance(self.center.x, self.center.y, e.offsetX, e.offsetY));
            self.circle = { center: self.center, radius: self.radius };

        }
        else {

            self.posX = e.offsetX - self.center.x;
            self.posY = e.offsetY - self.center.y;

            $canvas.trigger("defined");

            self.center = undefined;
            self.circle = undefined;
        }

    });

    $canvas.mousemove(function (e) {

        if (self.center != undefined && self.circle==undefined) {

            context.clearCanvas();
            context.drawCircle(self.center.x, self.center.y, Math.round(distance(self.center.x, self.center.y, e.offsetX, e.offsetY)));

        }

        else if (self.circle != undefined) {

            context.clearCanvas();
            context.drawCircle(self.circle.center.x, self.circle.center.y, self.circle.radius);
            context.beginPath();
            context.moveTo(e.offsetX, self.center.y)
            context.lineTo(self.center.x, self.center.y)
            context.lineTo(self.center.x, e.offsetY)
            context.stroke();

        }

    });

}<commit_msg>Fixed mouse position for mozilla events<commit_after>function Definition(canvasID) {

    var self = this;
    var canvas = document.getElementById(canvasID);
    var context = canvas.getContext("2d");
    var $canvas = $(canvas);

    $canvas.mousedown(function (e) {

        if (e.offsetX == undefined) {

            e.offsetX = e.pageX - e.target.offsetLeft;
            e.offsetY = e.pageY - e.target.offsetTop;

        }

        if (self.center == undefined) {
            self.center = { x: e.offsetX, y: e.offsetY };
        }
        else if (self.circle == undefined) {

            self.radius = Math.round(distance(self.center.x, self.center.y, e.offsetX, e.offsetY));
            self.circle = { center: self.center, radius: self.radius };

        }
        else {

            self.posX = e.offsetX - self.center.x;
            self.posY = e.offsetY - self.center.y;

            $canvas.trigger("defined");

            self.center = undefined;
            self.circle = undefined;
        }

    });

    $canvas.mousemove(function (e) {

        if (e.offsetX == undefined) {

            e.offsetX = e.pageX - e.target.offsetLeft;
            e.offsetY = e.pageY - e.target.offsetTop;

        }

        if (self.center != undefined && self.circle == undefined) {

            context.clearCanvas();
            context.drawCircle(self.center.x, self.center.y, Math.round(distance(self.center.x, self.center.y, e.offsetX, e.offsetY)));

        }

        else if (self.circle != undefined) {

            context.clearCanvas();
            context.drawCircle(self.circle.center.x, self.circle.center.y, self.circle.radius);
            context.beginPath();
            context.moveTo(e.offsetX, self.center.y)
            context.lineTo(self.center.x, self.center.y)
            context.lineTo(self.center.x, e.offsetY)
            context.stroke();

        }

    });

}<|endoftext|>


<commit_before>var pagination = require('hexo-pagination');
var _ = require('lodash');

hexo.extend.generator.register('category', function(locals) {
	var config = this.config;
	var categories = locals.data.categories;
	var result = [];

	_.each(categories, function(category, title) {
		_.each(category, function(data, lang) {
			result = result.concat(
				pagination(lang + '/' + data.slug, getCategoryByName(
					locals.categories,
					data.category).posts, {
					perPage: _c('category_generator.per_page', lang, config, locals) || _c('per_page', lang, config, locals),
					layout: ['category', 'archive', 'index'],
					format: _c('pagination_dir', lang, config, locals) + '/%d/',
					data: {
						lang: lang,
						title: data.name,
						category: data.category,
						alternates: getAlternateCategories(category)
					}
				})
			);
		});
	});

	return result;
});

hexo.extend.generator.register('posts', function(locals) {
	var posts = locals.posts.sort('-date').toArray();
	var length = posts.length;

	return posts.map(function(post, i) {
		var layout = post.layout;
		var path = post.path;

		if (!layout || layout === 'false') {
			return {
				path: path,
				data: post.content
			};
		} else {
			if (i) post.prev = posts[i - 1];
			if (i < length - 1) post.next = posts[i + 1];

			var layouts = ['post', 'page', 'index'];
			if (layout !== 'post') layouts.unshift(layout);

			if (post.label && post.lang) {
				post.alternates = getAlternatePosts(posts, post.label)
			}

			return {
				path: path,
				layout: layouts,
				data: post
			};
		}
	});
});

hexo.extend.generator.register('index', function(locals) {
	var result = [];
	var config = this.config;
	_.forEach(config.language, function(lang, n) {
		if (n == 0) {
			result = result.concat(
				{
					path: '',
					data: {
						redirect: lang
					},
					layout: ['redirect']
				}
			);
		}
		if (lang != 'default') {
			result = result.concat(
				pagination(lang, locals.posts.sort('-date').toArray().filter(
					function(post) {
						return post.lang == lang;
					}), {
					perPage: _c('per_page', lang, config, locals),
					layout: ['index'],
					format: _c('pagination_dir', lang, config, locals) + '/%d/',
					data: {
						lang: lang,
						title: _c('title', lang, config, locals),
						alternates: getAlternateIndices(config, locals)
					}
				})
			);
		}
	});
	return result;
});

hexo.extend.generator.register('feed', function(locals) {
	var result = [];
	var config = this.config;
	_.forEach(config.language, function(lang) {
		if (lang != 'default') {
			result.push(
				{
					path: lang + '/feed.xml',
					data: {
						title: _c('title', lang, config, locals),
						lang: lang,
						posts: locals.posts.sort('-updated').filter(function(post) {
							return post.lang == lang;
						})
					},
					layout: ['rss2']
				}
			)
		}
	});
	return result;
});

function getCategoryByName(categories, name) {
	return categories.toArray().filter(function(category) {
		return category.name == name;
	})[0];
}

function getAlternateCategories(category) {
	var result = [];
	_.each(category, function(data, lang) {
		result.push({
			title: data.name,
			lang: lang,
			path: lang + '/' + data.slug
		});
	});
	return result;
}

function getAlternatePosts(posts, label) {
	var alternates = posts.filter(function(post) {
		return post.label == label;
	});
	var result = [];
	_.each(alternates, function(post) {
		result.push({
			title: post.title,
			lang: post.lang,
			path: post.path
		});
	});
	return result;
}

function getAlternateIndices(config, locals) {
	var result = [];
	_.each(config.language, function(lang) {
		if (lang != 'default') {
			result.push({
				title: _c('title', lang, config, locals),
				lang: lang,
				path: lang
			});
		}
	});
	return result;
}

function _c(string, lang, config, locals) {
	if (locals.data['config_' + lang] != null) {
		return path(locals.data['config_' + lang], string) || path(config, string);
	}
	return path(config, string);
}

/**
 * Retrieve nested item from object/array (http://stackoverflow.com/a/16190716)
 * @param {Object|Array} obj
 * @param {String} path dot separated
 * @param {*} def default value ( if result undefined )
 * @returns {*}
 */
function path(obj, path, def) {
	var i, len;
	for (i = 0, path = path.split('.'), len = path.length; i < len; i++) {
		if (!obj || typeof obj !== 'object') return def;
		obj = obj[path[i]];
	}
	if (obj === undefined) return def;
	return obj;
}
<commit_msg>Set is_post boolean variable to true in posts<commit_after>var pagination = require('hexo-pagination');
var _ = require('lodash');

hexo.extend.generator.register('category', function(locals) {
	var config = this.config;
	var categories = locals.data.categories;
	var result = [];

	_.each(categories, function(category, title) {
		_.each(category, function(data, lang) {
			result = result.concat(
				pagination(lang + '/' + data.slug, getCategoryByName(
					locals.categories,
					data.category).posts, {
					perPage: _c('category_generator.per_page', lang, config, locals) || _c('per_page', lang, config, locals),
					layout: ['category', 'archive', 'index'],
					format: _c('pagination_dir', lang, config, locals) + '/%d/',
					data: {
						lang: lang,
						title: data.name,
						category: data.category,
						alternates: getAlternateCategories(category)
					}
				})
			);
		});
	});

	return result;
});

hexo.extend.generator.register('posts', function(locals) {
	var posts = locals.posts.sort('-date').toArray();
	var length = posts.length;

	return posts.map(function(post, i) {
		var layout = post.layout;
		var path = post.path;

		if (!layout || layout === 'false') {
			return {
				path: path,
				data: post.content
			};
		} else {
			if (i) post.prev = posts[i - 1];
			if (i < length - 1) post.next = posts[i + 1];

			var layouts = ['post', 'page', 'index'];
			if (layout !== 'post') layouts.unshift(layout);

			if (post.label && post.lang) {
				post.alternates = getAlternatePosts(posts, post.label)
			}

			post.is_post = true;

			return {
				path: path,
				layout: layouts,
				data: post
			};
		}
	});
});

hexo.extend.generator.register('index', function(locals) {
	var result = [];
	var config = this.config;
	_.forEach(config.language, function(lang, n) {
		if (n == 0) {
			result = result.concat(
				{
					path: '',
					data: {
						redirect: lang
					},
					layout: ['redirect']
				}
			);
		}
		if (lang != 'default') {
			result = result.concat(
				pagination(lang, locals.posts.sort('-date').toArray().filter(
					function(post) {
						return post.lang == lang;
					}), {
					perPage: _c('per_page', lang, config, locals),
					layout: ['index'],
					format: _c('pagination_dir', lang, config, locals) + '/%d/',
					data: {
						lang: lang,
						title: _c('title', lang, config, locals),
						alternates: getAlternateIndices(config, locals)
					}
				})
			);
		}
	});
	return result;
});

hexo.extend.generator.register('feed', function(locals) {
	var result = [];
	var config = this.config;
	_.forEach(config.language, function(lang) {
		if (lang != 'default') {
			result.push(
				{
					path: lang + '/feed.xml',
					data: {
						title: _c('title', lang, config, locals),
						lang: lang,
						posts: locals.posts.sort('-updated').filter(function(post) {
							return post.lang == lang;
						})
					},
					layout: ['rss2']
				}
			)
		}
	});
	return result;
});

function getCategoryByName(categories, name) {
	return categories.toArray().filter(function(category) {
		return category.name == name;
	})[0];
}

function getAlternateCategories(category) {
	var result = [];
	_.each(category, function(data, lang) {
		result.push({
			title: data.name,
			lang: lang,
			path: lang + '/' + data.slug
		});
	});
	return result;
}

function getAlternatePosts(posts, label) {
	var alternates = posts.filter(function(post) {
		return post.label == label;
	});
	var result = [];
	_.each(alternates, function(post) {
		result.push({
			title: post.title,
			lang: post.lang,
			path: post.path
		});
	});
	return result;
}

function getAlternateIndices(config, locals) {
	var result = [];
	_.each(config.language, function(lang) {
		if (lang != 'default') {
			result.push({
				title: _c('title', lang, config, locals),
				lang: lang,
				path: lang
			});
		}
	});
	return result;
}

function _c(string, lang, config, locals) {
	if (locals.data['config_' + lang] != null) {
		return path(locals.data['config_' + lang], string) || path(config, string);
	}
	return path(config, string);
}

/**
 * Retrieve nested item from object/array (http://stackoverflow.com/a/16190716)
 * @param {Object|Array} obj
 * @param {String} path dot separated
 * @param {*} def default value ( if result undefined )
 * @returns {*}
 */
function path(obj, path, def) {
	var i, len;
	for (i = 0, path = path.split('.'), len = path.length; i < len; i++) {
		if (!obj || typeof obj !== 'object') return def;
		obj = obj[path[i]];
	}
	if (obj === undefined) return def;
	return obj;
}
<|endoftext|>


<commit_before>// -*- coding: utf-8 -*-
//
// (c) 2014 Bjoern Ricks <bjoern.ricks@gmail.com>
//
// See LICENSE comming with the source of 'trex' for details.
//
'use strict';

var trexServices = angular.module('trex.services', ['ngResource']);

trexServices.factory('Conf', function($location) {
    function getRootUrl() {
        var rootUrl = $location.protocol() + '://' + $location.host();
        if ($location.port())
            rootUrl += ':' + $location.port();
        return rootUrl;
    };

    return {
        'apiBase': '/api/1',
        'rootUrl': getRootUrl()
    };
});

trexServices.factory('Project', ['$resource', 'Conf',
    function($resource, Conf) {
        return $resource(Conf.apiBase + '/projects/:projectId',
            {projectId: '@id'},
            {entries: {method: 'GET', isArray: true,
                       url: Conf.apiBase + '/projects/:projectId/entries',
                       params: {projectId: '@id'}
                      },
             tags: {method: 'GET', isArray: true,
                    url: Conf.apiBase + '/projects/:projectId/tags',
                    params: {projectId: '@id'}
                   }
           }
        );
    }
]);

trexServices.factory('Entry', ['$resource', 'Conf',
    function($resource, Conf) {
        return $resource(Conf.apiBase + '/entries/:entryId', {entryId: '@id'}, {
        });
    }
]);
<commit_msg>Add users to Project angular ressource<commit_after>// -*- coding: utf-8 -*-
//
// (c) 2014 Bjoern Ricks <bjoern.ricks@gmail.com>
//
// See LICENSE comming with the source of 'trex' for details.
//
'use strict';

var trexServices = angular.module('trex.services', ['ngResource']);

trexServices.factory('Conf', function($location) {
    function getRootUrl() {
        var rootUrl = $location.protocol() + '://' + $location.host();
        if ($location.port())
            rootUrl += ':' + $location.port();
        return rootUrl;
    };

    return {
        'apiBase': '/api/1',
        'rootUrl': getRootUrl()
    };
});

trexServices.factory('Project', ['$resource', 'Conf',
    function($resource, Conf) {
        return $resource(Conf.apiBase + '/projects/:projectId',
            {projectId: '@id'},
            {entries: {method: 'GET', isArray: true,
                       url: Conf.apiBase + '/projects/:projectId/entries',
                       params: {projectId: '@id'}
                      },
             tags: {method: 'GET', isArray: true,
                    url: Conf.apiBase + '/projects/:projectId/tags',
                    params: {projectId: '@id'}
                   },
             users: {method: 'GET', isArray: true,
                     url: Conf.apiBase + '/projects/:projectId/users',
                     params: {projectId: '@id'}
                    }
           }
        );
    }
]);

trexServices.factory('Entry', ['$resource', 'Conf',
    function($resource, Conf) {
        return $resource(Conf.apiBase + '/entries/:entryId', {entryId: '@id'}, {
        });
    }
]);
<|endoftext|>