<commit_before>/**
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 *
 */

import { parse, noopImporter } from '../../../tests/utils';
import isReactCloneElementCall from '../isReactCloneElementCall';

describe('isReactCloneElementCall', () => {
  function parsePath(src) {
    const root = parse(src);
    return root.get('body', root.node.body.length - 1, 'expression');
  }

  describe('built in React.createClass', () => {
    it('accepts cloneElement called on React', () => {
      const def = parsePath(`
        var React = require("React");
        React.cloneElement({});
      `);
      expect(isReactCloneElementCall(def, noopImporter)).toBe(true);
    });

    it('accepts cloneElement called on aliased React', () => {
      const def = parsePath(`
        var other = require("React");
        other.cloneElement({});
      `);
      expect(isReactCloneElementCall(def, noopImporter)).toBe(true);
    });

    it('ignores other React calls', () => {
      const def = parsePath(`
        var React = require("React");
        React.isValidElement({});
      `);
      expect(isReactCloneElementCall(def, noopImporter)).toBe(false);
    });

    it('ignores non React calls to cloneElement', () => {
      const def = parsePath(`
        var React = require("bob");
        React.cloneElement({});
      `);
      expect(isReactCloneElementCall(def, noopImporter)).toBe(false);
    });

    it('accepts cloneElement called on destructed value', () => {
      const def = parsePath(`
        var { cloneElement } = require("react");
        cloneElement({});
      `);
      expect(isReactCloneElementCall(def, noopImporter)).toBe(true);
    });

    it('accepts cloneElement called on destructed aliased value', () => {
      const def = parsePath(`
        var { cloneElement: foo } = require("react");
        foo({});
      `);
      expect(isReactCloneElementCall(def, noopImporter)).toBe(true);
    });

    it('accepts cloneElement called on imported value', () => {
      const def = parsePath(`
        import { cloneElement } from "react";
        cloneElement({});
      `);
      expect(isReactCloneElementCall(def, noopImporter)).toBe(true);
    });

    it('accepts cloneElement called on imported aliased value', () => {
      const def = parsePath(`
        import { cloneElement as foo } from "react";
        foo({});
      `);
      expect(isReactCloneElementCall(def, noopImporter)).toBe(true);
    });
  });
});
<commit_msg>Add importer tests for isReactCloneElementCall<commit_after>/**
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 *
 */

import {
  parse,
  statement,
  noopImporter,
  makeMockImporter,
} from '../../../tests/utils';
import isReactCloneElementCall from '../isReactCloneElementCall';

describe('isReactCloneElementCall', () => {
  function parsePath(src) {
    const root = parse(src);
    return root.get('body', root.node.body.length - 1, 'expression');
  }

  const mockImporter = makeMockImporter({
    foo: statement(`
      export default React.cloneElement;
      import React from 'react';
    `).get('declaration'),
  });

  describe('built in React.createClass', () => {
    it('accepts cloneElement called on React', () => {
      const def = parsePath(`
        var React = require("React");
        React.cloneElement({});
      `);
      expect(isReactCloneElementCall(def, noopImporter)).toBe(true);
    });

    it('accepts cloneElement called on aliased React', () => {
      const def = parsePath(`
        var other = require("React");
        other.cloneElement({});
      `);
      expect(isReactCloneElementCall(def, noopImporter)).toBe(true);
    });

    it('ignores other React calls', () => {
      const def = parsePath(`
        var React = require("React");
        React.isValidElement({});
      `);
      expect(isReactCloneElementCall(def, noopImporter)).toBe(false);
    });

    it('ignores non React calls to cloneElement', () => {
      const def = parsePath(`
        var React = require("bob");
        React.cloneElement({});
      `);
      expect(isReactCloneElementCall(def, noopImporter)).toBe(false);
    });

    it('accepts cloneElement called on destructed value', () => {
      const def = parsePath(`
        var { cloneElement } = require("react");
        cloneElement({});
      `);
      expect(isReactCloneElementCall(def, noopImporter)).toBe(true);
    });

    it('accepts cloneElement called on destructed aliased value', () => {
      const def = parsePath(`
        var { cloneElement: foo } = require("react");
        foo({});
      `);
      expect(isReactCloneElementCall(def, noopImporter)).toBe(true);
    });

    it('accepts cloneElement called on imported value', () => {
      const def = parsePath(`
        import { cloneElement } from "react";
        cloneElement({});
      `);
      expect(isReactCloneElementCall(def, noopImporter)).toBe(true);
    });

    it('accepts cloneElement called on imported aliased value', () => {
      const def = parsePath(`
        import { cloneElement as foo } from "react";
        foo({});
      `);
      expect(isReactCloneElementCall(def, noopImporter)).toBe(true);
    });

    it('can resolve cloneElement imported from an intermediate module', () => {
      const def = parsePath(`
        import foo from "foo";
        foo({});
      `);
      expect(isReactCloneElementCall(def, mockImporter)).toBe(true);
    });
  });
});
<|endoftext|>


<commit_before>(function() {
  'use strict';
  angular.module('wysiwyg.module')
  .directive('wysiwyg', function($timeout, wysiwgGui, $compile) {
    return {
      template: '
' +
      '' +
      '

' +
      '

' +
      '
',
      restrict: 'E',
      scope: {
        value: '=ngModel',
        textareaHeight: '@textareaHeight',
        textareaName: '@textareaName',
        textareaClass: '@textareaClass',
        textareaRequired: '@textareaRequired',
        textareaId: '@textareaId',
        textareaMenu: '=textareaMenu',
        textareaCustomMenu: '=textareaCustomMenu',
        fn: '&',
        disabled: '=?disabled',
      },
      replace: true,
      require: 'ngModel',
      link: link,
      transclude: true
    };

    function link(scope, element, attrs, ngModelController) {

      var textarea = element.find('div.wysiwyg-textarea');

      scope.isLink = false;

      scope.fontSizes = [{
        value: '1',
        size: '10px'
      }, {
        value: '2',
        size: '13px'
      }, {
        value: '3',
        size: '16px'
      }, {
        value: '4',
        size: '18px'
      }, {
        value: '5',
        size: '24px'
      }, {
        value: '6',
        size: '32px'
      }, {
        value: '7',
        size: '48px'
      }];

      scope.formatBlocks = [{
        name: 'Heading Blocks',
        value: 'div'
      }, {
        name: 'Heading 1',
        value: 'h1'
      }, {
        name: 'Heading 2',
        value: 'h2'
      }, {
        name: 'Heading 3',
        value: 'h3'
      }, {
        name: 'Heading 4',
        value: 'h4'
      }, {
        name: 'Heading 5',
        value: 'h5'
      }, {
        name: 'Heading 6',
        value: 'h6'
      }, ];
      scope.formatBlock = scope.formatBlocks[0];

      scope.fontSize = scope.fontSizes[1];

      if (angular.isArray(scope.cssClasses)) {
        scope.cssClasses.unshift('css');
        scope.cssClass = scope.cssClasses[0];
      }

      scope.fonts = [
      'Georgia',
      'Palatino Linotype',
      'Times New Roman',
      'Arial',
      'Helvetica',
      'Arial Black',
      'Comic Sans MS',
      'Impact',
      'Lucida Sans Unicode',
      'Tahoma',
      'Trebuchet MS',
      'Verdana',
      'Courier New',
      'Lucida Console',
      'Helvetica Neue'
      ].sort();

      scope.font = scope.fonts[6];

      init();

      function init() {

        compileMenu();
        configureDisabledWatch();
        configureBootstrapTitle();
        configureListeners();
      }

      function compileMenu() {
        wysiwgGui.setCustomElements(scope.textareaCustomMenu);
        var menuDiv = element.children('div.wysiwyg-menu')[0];
        menuDiv.appendChild(wysiwgGui.createMenu(scope.textareaMenu));
        $compile(menuDiv)(scope);
      }

      function configureDisabledWatch() {
        scope.$watch('disabled', function(newValue) {
          angular.element('div.wysiwyg-menu').find('button').each(function() {
            angular.element(this).attr('disabled', newValue);
          });
          angular.element('div.wysiwyg-menu').find('select').each(function() {
            angular.element(this).attr('disabled', newValue);
          });
        });
      }

      function configureBootstrapTitle() {
        if (attrs.enableBootstrapTitle === 'true' && attrs.enableBootstrapTitle !== undefined) {
          element.find('button[title]').tooltip({
            container: 'body'
          });
        }
      }

      function insertTab(html, position) {
        var begining = html.substr(0, position);
        var end = html.substr(position);
        return begining + '    ' + end;
      }

      function configureListeners() {

                    //Send message to calling controller that a button has been clicked.
                    angular.element('.wysiwyg-menu').find('button').on('click', function() {
                      var title = angular.element(this);
                      scope.$emit('wysiwyg.click', title.attr('title') || title.attr('data-original-title'));
                    });

                    textarea.on('input keyup paste mouseup', function() {
                      var html = textarea.html();

                      if (html == '
') {
                        html = '';
                      }

                      ngModelController.$setViewValue(html);
                    });

                    textarea.on('keydown', function(event) {
                      if (event.keyCode == 9) {
                        var TAB_SPACES = 4;
                        var html = textarea.html();
                        var selection = window.getSelection();
                        var position = selection.anchorOffset;

                        event.preventDefault();
                            // html = insertTab(html, position);
                            // textarea.html(html);
                            // selection.collapse(textarea[0].firstChild, position + TAB_SPACES);
                          }
                        });

                    textarea.on('click keyup focus mouseup', function() {
                      $timeout(function() {
                        scope.isBold = scope.cmdState('bold');
                        scope.isUnderlined = scope.cmdState('underline');
                        scope.isStrikethrough = scope.cmdState('strikethrough');
                        scope.isItalic = scope.cmdState('italic');
                            scope.isSuperscript = itemIs('SUP'); //scope.cmdState('superscript');
                            scope.isSubscript = itemIs('SUB'); //scope.cmdState('subscript');
                            scope.isRightJustified = scope.cmdState('justifyright');
                            scope.isLeftJustified = scope.cmdState('justifyleft');
                            scope.isCenterJustified = scope.cmdState('justifycenter');
                            scope.isPre = scope.cmdValue('formatblock') === 'pre';
                            scope.isBlockquote = scope.cmdValue('formatblock') === 'blockquote';

                            scope.isOrderedList = scope.cmdState('insertorderedlist');
                            scope.isUnorderedList = scope.cmdState('insertunorderedlist');

                            scope.fonts.forEach(function(v, k) { //works but kinda crappy.
                              if (scope.cmdValue('fontname').indexOf(v) > -1) {
                                scope.font = v;
                                return false;
                              }
                            });
                            scope.cmdValue('formatblock').toLowerCase();
                            scope.formatBlocks.forEach(function(v, k) {
                              if (scope.cmdValue('formatblock').toLowerCase() === v.value.toLowerCase()) {
                                scope.formatBlock = v;
                                return false;
                              }
                            });

                            scope.fontSizes.forEach(function(v, k) {
                              if (scope.cmdValue('fontsize') === v.value) {
                                scope.fontSize = v;
                                return false;
                              }
                            });

                            scope.hiliteColor = getHiliteColor();
                            element.find('button.wysiwyg-hiliteColor').css('background-color', scope.hiliteColor);

                            scope.fontColor = scope.cmdValue('forecolor');
                            element.find('button.wysiwyg-fontcolor').css('color', scope.fontColor);

                            scope.isLink = itemIs('A');

                          }, 0);
});
}

                //Used to detect things like A tags and others that dont work with cmdValue().
                function itemIs(tag) {
                  var selection = window.getSelection().getRangeAt(0);
                  if (selection) {
                    if (selection.startContainer.parentNode.tagName === tag.toUpperCase() || selection.endContainer.parentNode.tagName === tag.toUpperCase()) {
                      return true;
                    } else {
                      return false;
                    }
                  } else {
                    return false;
                  }
                }

                //Used to detect things like A tags and others that dont work with cmdValue().
                function getHiliteColor() {
                  var selection = window.getSelection().getRangeAt(0);
                  if (selection) {
                    var style = angular.element(selection.startContainer.parentNode).attr('style');

                    if (!angular.isDefined(style))
                      return false;

                    var a = style.split(';');
                    for (var i = 0; i  view
                ngModelController.$render = function() {
                  textarea.html(ngModelController.$viewValue);
                };

                scope.format = function(cmd, arg) {
                  document.execCommand(cmd, false, arg);
                };

                scope.cmdState = function(cmd) {
                  return document.queryCommandState(cmd);
                };

                scope.cmdValue = function(cmd) {
                  return document.queryCommandValue(cmd);
                };

                scope.createLink = function() {
                  var input = prompt('Enter the link URL');
                  if (input && input !== undefined)
                    scope.format('createlink', input);
                };

                scope.insertImage = function() {
                  var input = prompt('Enter the image URL');
                  if (input && input !== undefined)
                    scope.format('insertimage', input);
                };

                scope.setFont = function() {
                  scope.format('fontname', scope.font);
                };

                scope.setFontSize = function() {
                  scope.format('fontsize', scope.fontSize.value);
                };

                scope.setFormatBlock = function() {
                  scope.format('formatBlock', scope.formatBlock.value);
                };

                scope.setFontColor = function() {
                  scope.format('forecolor', scope.fontColor);
                };

                scope.setHiliteColor = function() {
                  scope.format('hiliteColor', scope.hiliteColor);
                };

                scope.format('enableobjectresizing', true);
                scope.format('styleWithCSS', true);
              }
            })
})();
<commit_msg>indentation fixes<commit_after>(function() {
    'use strict';
    angular.module('wysiwyg.module')
        .directive('wysiwyg', function($timeout, wysiwgGui, $compile) {

                templateUrl: 'templates/template.html',
                restrict: 'E',
                scope: {
                    value: '=ngModel',
                    textareaName: '@textareaName',
                    textareaClass: '@textareaClass',
                    textareaRequired: '@textareaRequired',
                    textareaId: '@textareaId',
                    textareaMenu: '=textareaMenu',
                    textareaCustomMenu: '=textareaCustomMenu',
                    fn: '&',
                    disabled: '=?disabled',
                },
                replace: true,
                require: 'ngModel',
                link: link,
                transclude: true
            };

            function link(scope, element, attrs, ngModelController) {

                var textarea = element.find('div.wysiwyg-textarea');

                scope.isLink = false;

                scope.fontSizes = [{
                    value: '1',
                    size: '10px'
                }, {
                    value: '2',
                    size: '13px'
                }, {
                    value: '3',
                    size: '16px'
                }, {
                    value: '4',
                    size: '18px'
                }, {
                    value: '5',
                    size: '24px'
                }, {
                    value: '6',
                    size: '32px'
                }, {
                    value: '7',
                    size: '48px'
                }];

                scope.formatBlocks = [{
                    name: 'Heading Blocks',
                    value: 'div'
                }, {
                    name: 'Heading 1',
                    value: 'h1'
                }, {
                    name: 'Heading 2',
                    value: 'h2'
                }, {
                    name: 'Heading 3',
                    value: 'h3'
                }, {
                    name: 'Heading 4',
                    value: 'h4'
                }, {
                    name: 'Heading 5',
                    value: 'h5'
                }, {
                    name: 'Heading 6',
                    value: 'h6'
                }, ];
                scope.formatBlock = scope.formatBlocks[0];

                scope.fontSize = scope.fontSizes[1];

                if (angular.isArray(scope.cssClasses)) {
                    scope.cssClasses.unshift('css');
                    scope.cssClass = scope.cssClasses[0];
                }

                scope.fonts = [
                    'Georgia',
                    'Palatino Linotype',
                    'Times New Roman',
                    'Arial',
                    'Helvetica',
                    'Arial Black',
                    'Comic Sans MS',
                    'Impact',
                    'Lucida Sans Unicode',
                    'Tahoma',
                    'Trebuchet MS',
                    'Verdana',
                    'Courier New',
                    'Lucida Console',
                    'Helvetica Neue'
                ].sort();

                scope.font = scope.fonts[6];

                init();

                function init() {

                    compileMenu();
                    configureDisabledWatch();
                    configureBootstrapTitle();
                    configureListeners();
                }

                function compileMenu() {
                    wysiwgGui.setCustomElements(scope.textareaCustomMenu);
                    var menuDiv = element.children('div.wysiwyg-menu')[0];
                    menuDiv.appendChild(wysiwgGui.createMenu(scope.textareaMenu));
                    $compile(menuDiv)(scope);
                }

                function configureDisabledWatch() {
                    scope.$watch('disabled', function(newValue) {
                        angular.element('div.wysiwyg-menu').find('button').each(function() {
                            angular.element(this).attr('disabled', newValue);
                        });
                        angular.element('div.wysiwyg-menu').find('select').each(function() {
                            angular.element(this).attr('disabled', newValue);
                        });
                    });
                }

                function configureBootstrapTitle() {
                    if (attrs.enableBootstrapTitle === 'true' && attrs.enableBootstrapTitle !== undefined) {
                        element.find('button[title]').tooltip({
                            container: 'body'
                        });
                    }
                }

                function insertTab(html, position) {
                    var begining = html.substr(0, position);
                    var end = html.substr(position);
                    return begining + '    ' + end;
                }

                function configureListeners() {

                  //Send message to calling controller that a button has been clicked.
                  angular.element('.wysiwyg-menu').find('button').on('click', function() {
                      var title = angular.element(this);
                      scope.$emit('wysiwyg.click', title.attr('title') || title.attr('data-original-title'));
                  });

                  textarea.on('input keyup paste mouseup', function() {
                      var html = textarea.html();

                      if (html == '
') {
                          html = '';
                      }

                      ngModelController.$setViewValue(html);
                  });

                  textarea.on('keydown', function(event) {
                      if (event.keyCode == 9) {
                          var TAB_SPACES = 4;
                          var html = textarea.html();
                          var selection = window.getSelection();
                          var position = selection.anchorOffset;

                          event.preventDefault();
                          // html = insertTab(html, position);
                          // textarea.html(html);
                          // selection.collapse(textarea[0].firstChild, position + TAB_SPACES);
                      }
                  });

                  textarea.on('click keyup focus mouseup', function() {
                    $timeout(function() {
                        scope.isBold = scope.cmdState('bold');
                        scope.isUnderlined = scope.cmdState('underline');
                        scope.isStrikethrough = scope.cmdState('strikethrough');
                        scope.isItalic = scope.cmdState('italic');
                        scope.isSuperscript = itemIs('SUP'); //scope.cmdState('superscript');
                        scope.isSubscript = itemIs('SUB'); //scope.cmdState('subscript');
                        scope.isRightJustified = scope.cmdState('justifyright');
                        scope.isLeftJustified = scope.cmdState('justifyleft');
                        scope.isCenterJustified = scope.cmdState('justifycenter');
                        scope.isPre = scope.cmdValue('formatblock') === 'pre';
                        scope.isBlockquote = scope.cmdValue('formatblock') === 'blockquote';

                        scope.isOrderedList = scope.cmdState('insertorderedlist');
                        scope.isUnorderedList = scope.cmdState('insertunorderedlist');

                        scope.fonts.forEach(function(v, k) { //works but kinda crappy.
                            if (scope.cmdValue('fontname').indexOf(v) > -1) {
                                scope.font = v;
                                return false;
                            }
                        });
                        scope.cmdValue('formatblock').toLowerCase();
                        scope.formatBlocks.forEach(function(v, k) {
                            if (scope.cmdValue('formatblock').toLowerCase() === v.value.toLowerCase()) {
                                scope.formatBlock = v;
                                return false;
                            }
                        });

                        scope.fontSizes.forEach(function(v, k) {
                            if (scope.cmdValue('fontsize') === v.value) {
                                scope.fontSize = v;
                                return false;
                            }
                        });

                        scope.hiliteColor = getHiliteColor();
                        element.find('button.wysiwyg-hiliteColor').css('background-color', scope.hiliteColor);

                        scope.fontColor = scope.cmdValue('forecolor');
                        element.find('button.wysiwyg-fontcolor').css('color', scope.fontColor);

                        scope.isLink = itemIs('A');

                    }, 0);
                  });
                }

                //Used to detect things like A tags and others that dont work with cmdValue().
                function itemIs(tag) {
                    var selection = window.getSelection().getRangeAt(0);
                    if (selection) {
                        if (selection.startContainer.parentNode.tagName === tag.toUpperCase() || selection.endContainer.parentNode.tagName === tag.toUpperCase()) {
                            return true;
                        } else {
                            return false;
                        }
                    } else {
                        return false;
                    }
                }

                //Used to detect things like A tags and others that dont work with cmdValue().
                function getHiliteColor() {
                    var selection = window.getSelection().getRangeAt(0);
                    if (selection) {
                        var style = angular.element(selection.startContainer.parentNode).attr('style');

                        if (!angular.isDefined(style))
                            return false;

                        var a = style.split(';');
                        for (var i = 0; i  view
                ngModelController.$render = function() {
                    textarea.html(ngModelController.$viewValue);
                };

                scope.format = function(cmd, arg) {
                    document.execCommand(cmd, false, arg);
                };

                scope.cmdState = function(cmd) {
                    return document.queryCommandState(cmd);
                };

                scope.cmdValue = function(cmd) {
                    return document.queryCommandValue(cmd);
                };

                scope.createLink = function() {
                    var input = prompt('Enter the link URL');
                    if (input && input !== undefined)
                        scope.format('createlink', input);
                };

                scope.insertImage = function() {
                    var input = prompt('Enter the image URL');
                    if (input && input !== undefined)
                        scope.format('insertimage', input);
                };

                scope.setFont = function() {
                    scope.format('fontname', scope.font);
                };

                scope.setFontSize = function() {
                    scope.format('fontsize', scope.fontSize.value);
                };

                scope.setFormatBlock = function() {
                    scope.format('formatBlock', scope.formatBlock.value);
                };

                scope.setFontColor = function() {
                    scope.format('forecolor', scope.fontColor);
                };

                scope.setHiliteColor = function() {
                    scope.format('hiliteColor', scope.hiliteColor);
                };

                scope.format('enableobjectresizing', true);
                scope.format('styleWithCSS', true);
            }
        });
})();
<|endoftext|>


<commit_before>/*
Copyright 2015, 2016 OpenMarket Ltd

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

var React = require("react");
var classNames = require('classnames');
var sdk = require("../../../index");
var Invite = require("../../../Invite");
var createRoom = require("../../../createRoom");
var MatrixClientPeg = require("../../../MatrixClientPeg");
var DMRoomMap = require('../../../utils/DMRoomMap');
var rate_limited_func = require("../../../ratelimitedfunc");
var dis = require("../../../dispatcher");
var Modal = require('../../../Modal');

const TRUNCATE_QUERY_LIST = 40;

module.exports = React.createClass({
    displayName: "ChatInviteDialog",
    propTypes: {
        title: React.PropTypes.string,
        description: React.PropTypes.oneOfType([
            React.PropTypes.element,
            React.PropTypes.string,
        ]),
        value: React.PropTypes.string,
        placeholder: React.PropTypes.string,
        roomId: React.PropTypes.string,
        button: React.PropTypes.string,
        focus: React.PropTypes.bool,
        onFinished: React.PropTypes.func.isRequired
    },

    getDefaultProps: function() {
        return {
            title: "Start a chat",
            description: "Who would you like to communicate with?",
            value: "",
            placeholder: "User ID, Name or email",
            button: "Start Chat",
            focus: true
        };
    },

    getInitialState: function() {
        return {
            error: false,
            inviteList: [],
            queryList: [],
        };
    },

    componentDidMount: function() {
        if (this.props.focus) {
            // Set the cursor at the end of the text input
            this.refs.textinput.value = this.props.value;
        }
        this._updateUserList();
    },

    onButtonClick: function() {
        var inviteList = this.state.inviteList.slice();
        // Check the text input field to see if user has an unconverted address
        // If there is and it's valid add it to the local inviteList
        var check = Invite.isValidAddress(this.refs.textinput.value);
        if (check === true || check === null) {
            inviteList.push(this.refs.textinput.value);
        } else if (this.refs.textinput.value.length > 0) {
            this.setState({ error: true });
            return;
        }

        if (inviteList.length > 0) {
            if (this._isDmChat(inviteList)) {
                // Direct Message chat
                var room = this._getDirectMessageRoom(inviteList[0]);
                if (room) {
                    // A Direct Message room already exists for this user and you
                    // so go straight to that room
                    dis.dispatch({
                        action: 'view_room',
                        room_id: room.roomId,
                    });
                    this.props.onFinished(true, inviteList[0]);
                } else {
                    this._startChat(inviteList);
                }
            } else {
                // Multi invite chat
                this._startChat(inviteList);
            }
        } else {
            // No addresses supplied
            this.setState({ error: true });
        }
    },

    onCancel: function() {
        this.props.onFinished(false);
    },

    onKeyDown: function(e) {
        if (e.keyCode === 27) { // escape
            e.stopPropagation();
            e.preventDefault();
            this.props.onFinished(false);
        } else if (e.keyCode === 38) { // up arrow
            e.stopPropagation();
            e.preventDefault();
            this.addressSelector.onKeyU();
        } else if (e.keyCode === 40) { // down arrow
            e.stopPropagation();
            e.preventDefault();
            this.addressSelector.onKeyDown();
        } else if (e.keyCode === 13 || (e.keyCode === 9 && this.state.queryList.length > 0)) { // enter or tab
            e.stopPropagation();
            e.preventDefault();
            this.addressSelector.onKeySelect();
        } else if (e.keyCode === 32 || e.keyCode === 188 || e.keyCode === 9) { // space, comma or tab
            e.stopPropagation();
            e.preventDefault();
            var check = Invite.isValidAddress(this.refs.textinput.value);
            if (check === true || check === null) {
                var inviteList = this.state.inviteList.slice();
                inviteList.push(this.refs.textinput.value);
                this.setState({
                    inviteList: inviteList,
                    queryList: [],
                });
            } else {
                this.setState({ error: true });
            }
        }
    },

    onQueryChanged: function(ev) {
        var query = ev.target.value;
        var queryList = [];

        // Only do search if there is something to search
        if (query.length > 0) {
            queryList = this._userList.filter((user) => {
                return this._matches(query, user);
            });
        }

        this.setState({
            queryList: queryList,
            error: false,
        });
    },

    onDismissed: function(index) {
        var self = this;
        return function() {
            var inviteList = self.state.inviteList.slice();
            inviteList.splice(index, 1);
            self.setState({
                inviteList: inviteList,
                queryList: [],
            });
        }
    },

    onClick: function(index) {
        var self = this;
        return function() {
            self.onSelected(index);
        };
    },

    onSelected: function(index) {
        var inviteList = this.state.inviteList.slice();
        inviteList.push(this.state.queryList[index].userId);
        this.setState({
            inviteList: inviteList,
            queryList: [],
        });
    },

    _getDirectMessageRoom: function(addr) {
        const dmRoomMap = new DMRoomMap(MatrixClientPeg.get());
        var dmRooms = dmRoomMap.getDMRoomsForUserId(addr);
        if (dmRooms.length > 0) {
            // Cycle through all the DM rooms and find the first non forgotten or parted room
            for (let i = 0; i < dmRooms.length; i++) {
                let room = MatrixClientPeg.get().getRoom(dmRooms[i]);
                if (room) {
                    const me = room.getMember(MatrixClientPeg.get().credentials.userId);
                    if (me.membership == 'join') {
                        return room;
                    }
                }
            }
        }
        return null;
    },

    _startChat: function(addrs) {
        if (this.props.roomId) {
            // Invite new user to a room
            Invite.inviteMultipleToRoom(this.props.roomId, addrs)
            .catch(function(err) {
                var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
                Modal.createDialog(ErrorDialog, {
                    title: "Failure to invite user",
                    description: err.toString()
                });
                return null;
            })
            .done();
        } else if (this._isDmChat(addrs)) {
            // Start the DM chat
            createRoom({dmUserId: addrs[0]})
            .catch(function(err) {
                var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
                Modal.createDialog(ErrorDialog, {
                    title: "Failure to invite user",
                    description: err.toString()
                });
                return null;
            })
            .done();
        } else {
            // Start multi user chat
            var self = this;
            createRoom().then(function(roomId) {
                return Invite.inviteMultipleToRoom(roomId, addrs);
            })
            .catch(function(err) {
                var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
                Modal.createDialog(ErrorDialog, {
                    title: "Failure to invite user",
                    description: err.toString()
                });
                return null;
            })
            .done();
        }

        // Close - this will happen before the above, as that is async
        this.props.onFinished(true, addrs);
    },

    _updateUserList: new rate_limited_func(function() {
        // Get all the users
        this._userList = MatrixClientPeg.get().getUsers();
    }, 500),

    // This is the search algorithm for matching users
    _matches: function(query, user) {
        var name = user.displayName.toLowerCase();
        var uid = user.userId.toLowerCase();
        query = query.toLowerCase();

        // don't match any that are already on the invite list
        if (this._isOnInviteList(uid)) {
            return false;
        }

        // ignore current user
        if (uid === MatrixClientPeg.get().credentials.userId) {
            return false;
        }

        // direct prefix matches
        if (name.indexOf(query) === 0 || uid.indexOf(query) === 0) {
            return true;
        }

        // strip @ on uid and try matching again
        if (uid.length > 1 && uid[0] === "@" && uid.substring(1).indexOf(query) === 0) {
            return true;
        }

        // split spaces in name and try matching constituent parts
        var parts = name.split(" ");
        for (var i = 0; i < parts.length; i++) {
            if (parts[i].indexOf(query) === 0) {
                return true;
            }
        }
        return false;
    },

    _isOnInviteList: function(uid) {
        for (let i = 0; i < this.state.inviteList.length; i++) {
            if (this.state.inviteList[i].toLowerCase() === uid) {
                return true;
            }
        }
        return false;
    },

    _isDmChat: function(addrs) {
        if (addrs.length === 1 && Invite.getAddressType(addrs[0]) === "mx" && !this.props.roomId) {
            return true;
        } else {
            return false;
        }
    },

    render: function() {
        var TintableSvg = sdk.getComponent("elements.TintableSvg");
        var AddressSelector = sdk.getComponent("elements.AddressSelector");
        this.scrollElement = null;

        var query = [];
        // create the invite list
        if (this.state.inviteList.length > 0) {
            var AddressTile = sdk.getComponent("elements.AddressTile");
            for (let i = 0; i < this.state.inviteList.length; i++) {
                query.push(
                    <AddressTile key={i} address={this.state.inviteList[i]} canDismiss={true} onDismissed={ this.onDismissed(i) } />
                );
            }
        }

        // Add the query at the end
        query.push(
            <textarea key={this.state.inviteList.length}
                rows="1"
                id="textinput"
                ref="textinput"
                className="mx_ChatInviteDialog_input"
                onChange={this.onQueryChanged}
                placeholder={this.props.placeholder}
                defaultValue={this.props.value}
                autoFocus={this.props.focus}>
            </textarea>
        );

        var error;
        var addressSelector;
        if (this.state.error) {
            error =
You have entered an invalid contact. Try using their Matrix ID or email address.

        } else {
            addressSelector = (
                <AddressSelector ref={(ref) => {this.addressSelector = ref}}
                    addressList={ this.state.queryList }
                    onSelected={ this.onSelected }
                    truncateAt={ TRUNCATE_QUERY_LIST } />
            );
        }

        return (

                    {this.props.title}

                    <TintableSvg src="img/icons-close-button.svg" width="35" height="35" />

                    { this.props.description }

{ query }

                    { error }
                    { addressSelector }

                    <button className="mx_Dialog_primary" onClick={this.onButtonClick}>
                        {this.props.button}
                    </button>

        );
    }
});
<commit_msg>Better error reporting for failed multi invites<commit_after>/*
Copyright 2015, 2016 OpenMarket Ltd

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

var React = require("react");
var classNames = require('classnames');
var sdk = require("../../../index");
var Invite = require("../../../Invite");
var createRoom = require("../../../createRoom");
var MatrixClientPeg = require("../../../MatrixClientPeg");
var DMRoomMap = require('../../../utils/DMRoomMap');
var rate_limited_func = require("../../../ratelimitedfunc");
var dis = require("../../../dispatcher");
var Modal = require('../../../Modal');

const TRUNCATE_QUERY_LIST = 40;

module.exports = React.createClass({
    displayName: "ChatInviteDialog",
    propTypes: {
        title: React.PropTypes.string,
        description: React.PropTypes.oneOfType([
            React.PropTypes.element,
            React.PropTypes.string,
        ]),
        value: React.PropTypes.string,
        placeholder: React.PropTypes.string,
        roomId: React.PropTypes.string,
        button: React.PropTypes.string,
        focus: React.PropTypes.bool,
        onFinished: React.PropTypes.func.isRequired
    },

    getDefaultProps: function() {
        return {
            title: "Start a chat",
            description: "Who would you like to communicate with?",
            value: "",
            placeholder: "User ID, Name or email",
            button: "Start Chat",
            focus: true
        };
    },

    getInitialState: function() {
        return {
            error: false,
            inviteList: [],
            queryList: [],
        };
    },

    componentDidMount: function() {
        if (this.props.focus) {
            // Set the cursor at the end of the text input
            this.refs.textinput.value = this.props.value;
        }
        this._updateUserList();
    },

    onButtonClick: function() {
        var inviteList = this.state.inviteList.slice();
        // Check the text input field to see if user has an unconverted address
        // If there is and it's valid add it to the local inviteList
        var check = Invite.isValidAddress(this.refs.textinput.value);
        if (check === true || check === null) {
            inviteList.push(this.refs.textinput.value);
        } else if (this.refs.textinput.value.length > 0) {
            this.setState({ error: true });
            return;
        }

        if (inviteList.length > 0) {
            if (this._isDmChat(inviteList)) {
                // Direct Message chat
                var room = this._getDirectMessageRoom(inviteList[0]);
                if (room) {
                    // A Direct Message room already exists for this user and you
                    // so go straight to that room
                    dis.dispatch({
                        action: 'view_room',
                        room_id: room.roomId,
                    });
                    this.props.onFinished(true, inviteList[0]);
                } else {
                    this._startChat(inviteList);
                }
            } else {
                // Multi invite chat
                this._startChat(inviteList);
            }
        } else {
            // No addresses supplied
            this.setState({ error: true });
        }
    },

    onCancel: function() {
        this.props.onFinished(false);
    },

    onKeyDown: function(e) {
        if (e.keyCode === 27) { // escape
            e.stopPropagation();
            e.preventDefault();
            this.props.onFinished(false);
        } else if (e.keyCode === 38) { // up arrow
            e.stopPropagation();
            e.preventDefault();
            this.addressSelector.onKeyU();
        } else if (e.keyCode === 40) { // down arrow
            e.stopPropagation();
            e.preventDefault();
            this.addressSelector.onKeyDown();
        } else if (e.keyCode === 13 || (e.keyCode === 9 && this.state.queryList.length > 0)) { // enter or tab
            e.stopPropagation();
            e.preventDefault();
            this.addressSelector.onKeySelect();
        } else if (e.keyCode === 32 || e.keyCode === 188 || e.keyCode === 9) { // space, comma or tab
            e.stopPropagation();
            e.preventDefault();
            var check = Invite.isValidAddress(this.refs.textinput.value);
            if (check === true || check === null) {
                var inviteList = this.state.inviteList.slice();
                inviteList.push(this.refs.textinput.value);
                this.setState({
                    inviteList: inviteList,
                    queryList: [],
                });
            } else {
                this.setState({ error: true });
            }
        }
    },

    onQueryChanged: function(ev) {
        var query = ev.target.value;
        var queryList = [];

        // Only do search if there is something to search
        if (query.length > 0) {
            queryList = this._userList.filter((user) => {
                return this._matches(query, user);
            });
        }

        this.setState({
            queryList: queryList,
            error: false,
        });
    },

    onDismissed: function(index) {
        var self = this;
        return function() {
            var inviteList = self.state.inviteList.slice();
            inviteList.splice(index, 1);
            self.setState({
                inviteList: inviteList,
                queryList: [],
            });
        }
    },

    onClick: function(index) {
        var self = this;
        return function() {
            self.onSelected(index);
        };
    },

    onSelected: function(index) {
        var inviteList = this.state.inviteList.slice();
        inviteList.push(this.state.queryList[index].userId);
        this.setState({
            inviteList: inviteList,
            queryList: [],
        });
    },

    _getDirectMessageRoom: function(addr) {
        const dmRoomMap = new DMRoomMap(MatrixClientPeg.get());
        var dmRooms = dmRoomMap.getDMRoomsForUserId(addr);
        if (dmRooms.length > 0) {
            // Cycle through all the DM rooms and find the first non forgotten or parted room
            for (let i = 0; i < dmRooms.length; i++) {
                let room = MatrixClientPeg.get().getRoom(dmRooms[i]);
                if (room) {
                    const me = room.getMember(MatrixClientPeg.get().credentials.userId);
                    if (me.membership == 'join') {
                        return room;
                    }
                }
            }
        }
        return null;
    },

    _startChat: function(addrs) {
        if (this.props.roomId) {
            // Invite new user to a room
            var self = this;
            Invite.inviteMultipleToRoom(this.props.roomId, addrs)
            .then(function(addrs) {
                return self._showAnyInviteErrors(addrs);
            })
            .catch(function(err) {
                var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
                Modal.createDialog(ErrorDialog, {
                    title: "Failure to invite",
                    description: err.toString()
                });
                return null;
            })
            .done();
        } else if (this._isDmChat(addrs)) {
            // Start the DM chat
            createRoom({dmUserId: addrs[0]})
            .catch(function(err) {
                var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
                Modal.createDialog(ErrorDialog, {
                    title: "Failure to invite user",
                    description: err.toString()
                });
                return null;
            })
            .done();
        } else {
            // Start multi user chat
            var self = this;
            createRoom().then(function(roomId) {
                return Invite.inviteMultipleToRoom(roomId, addrs);
            })
            .then(function(addrs) {
                return self._showAnyInviteErrors(addrs);
            })
            .catch(function(err) {
                var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
                Modal.createDialog(ErrorDialog, {
                    title: "Failure to invite",
                    description: err.toString()
                });
                return null;
            })
            .done();
        }

        // Close - this will happen before the above, as that is async
        this.props.onFinished(true, addrs);
    },

    _updateUserList: new rate_limited_func(function() {
        // Get all the users
        this._userList = MatrixClientPeg.get().getUsers();
    }, 500),

    // This is the search algorithm for matching users
    _matches: function(query, user) {
        var name = user.displayName.toLowerCase();
        var uid = user.userId.toLowerCase();
        query = query.toLowerCase();

        // don't match any that are already on the invite list
        if (this._isOnInviteList(uid)) {
            return false;
        }

        // ignore current user
        if (uid === MatrixClientPeg.get().credentials.userId) {
            return false;
        }

        // direct prefix matches
        if (name.indexOf(query) === 0 || uid.indexOf(query) === 0) {
            return true;
        }

        // strip @ on uid and try matching again
        if (uid.length > 1 && uid[0] === "@" && uid.substring(1).indexOf(query) === 0) {
            return true;
        }

        // split spaces in name and try matching constituent parts
        var parts = name.split(" ");
        for (var i = 0; i < parts.length; i++) {
            if (parts[i].indexOf(query) === 0) {
                return true;
            }
        }
        return false;
    },

    _isOnInviteList: function(uid) {
        for (let i = 0; i < this.state.inviteList.length; i++) {
            if (this.state.inviteList[i].toLowerCase() === uid) {
                return true;
            }
        }
        return false;
    },

    _isDmChat: function(addrs) {
        if (addrs.length === 1 && Invite.getAddressType(addrs[0]) === "mx" && !this.props.roomId) {
            return true;
        } else {
            return false;
        }
    },

    _showAnyInviteErrors: function(addrs) {
        // Show user any errors
        var errorList = [];
        for (var addr in addrs) {
            if (addrs.hasOwnProperty(addr) && addrs[addr] === "error") {
                errorList.push(addr);
            }
        }
        if (errorList.length > 0) {
            var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
            Modal.createDialog(ErrorDialog, {
                title: "Failed to invite the following users:",
                description: errorList.join(", "),
            });
        }
        return addrs;
    },

    render: function() {
        var TintableSvg = sdk.getComponent("elements.TintableSvg");
        var AddressSelector = sdk.getComponent("elements.AddressSelector");
        this.scrollElement = null;

        var query = [];
        // create the invite list
        if (this.state.inviteList.length > 0) {
            var AddressTile = sdk.getComponent("elements.AddressTile");
            for (let i = 0; i < this.state.inviteList.length; i++) {
                query.push(
                    <AddressTile key={i} address={this.state.inviteList[i]} canDismiss={true} onDismissed={ this.onDismissed(i) } />
                );
            }
        }

        // Add the query at the end
        query.push(
            <textarea key={this.state.inviteList.length}
                rows="1"
                id="textinput"
                ref="textinput"
                className="mx_ChatInviteDialog_input"
                onChange={this.onQueryChanged}
                placeholder={this.props.placeholder}
                defaultValue={this.props.value}
                autoFocus={this.props.focus}>
            </textarea>
        );

        var error;
        var addressSelector;
        if (this.state.error) {
            error =
You have entered an invalid contact. Try using their Matrix ID or email address.

        } else {
            addressSelector = (
                <AddressSelector ref={(ref) => {this.addressSelector = ref}}
                    addressList={ this.state.queryList }
                    onSelected={ this.onSelected }
                    truncateAt={ TRUNCATE_QUERY_LIST } />
            );
        }

        return (

                    {this.props.title}

                    <TintableSvg src="img/icons-close-button.svg" width="35" height="35" />

                    { this.props.description }

{ query }

                    { error }
                    { addressSelector }

                    <button className="mx_Dialog_primary" onClick={this.onButtonClick}>
                        {this.props.button}
                    </button>

        );
    }
});
<|endoftext|>


<commit_before>'use strict';

var commitStreamAdminControllers = angular.module('commitStreamAdminControllers', []);

var isInstanceMode = function(config) {
  if (!config.configMode) return true;
  return config.configMode.type === 'instance';
};

var isDigestMode = function(config) {
  return config.configMode && config.configMode.type === 'digest';
};

var isDigestConfigured = function(config) {
  return config.configMode.configured;
};

commitStreamAdminControllers.controller('InstancesController', [
  '$rootScope',
  '$scope',
  '$http',
  '$q',
  '$location',
  'CommitStreamApi',
  'serviceUrl',
  'configGetUrl',
  'configSaveUrl',
  'persistentOptions',
  function($rootScope, $scope, $http, $q, $location, CommitStreamApi, serviceUrl, configGetUrl, configSaveUrl, persistentOptions) {
    var config;

    $scope.loaderUrl = serviceUrl + '/ajax-loader.gif';

    var loading = true;

    $scope.loading = function() {
      return loading;
    };

    var errorHandler = function(error) {
      loading = false;
      if (error.data && error.data.errors && error.data.errors.length) {
        $scope.error.value = error.data.errors[0];
      } else {
        $scope.error.value = 'There was an unexpected error when processing your request.';
      }
    };

    $scope.error = {
      value: ''
    };

    $scope.errorActive = function() {
      return $scope.error.value !== '';
    };

    // For teamroom settings:
    var configDigestModeSave = function(configMode) {
      if (!configMode.configured) {
        if (configSaveUrl) return $http.post(configSaveUrl, configMode);
        return $q.when(true);
      }
      // TODO: do we even need this below?
      return $q.when(true);
    };

    CommitStreamApi
      .load()
      .then(function(resources) {
        $rootScope.resources = resources;
        if (!configGetUrl) return {
          data: {
            configMode: {
              type: 'instance',
              digestId: '',
              configured: false,
              enabled: false,
              useGlobalDigestId: false
            },
            serviceUrl: serviceUrl,
            instanceId: '',
            apiKey: '',
            globalDigestId: '',
            configured: false,
            enabled: false
          }
        }
        return $http.get(configGetUrl);
      })
      .then(function(configRes) {
        // TODO handle null case?
        config = configRes.data;
        $rootScope.config = config;

        if (config.configured) {
          persistentOptions.headers.Bearer = config.apiKey;
          return $rootScope.resources.$get('instance', {
            instanceId: config.instanceId
          });
        } else {
          // return $rootScope.resources.$post('instances');
          return false;
        }
      })
      .then(function(instance) {
        if (instance) {
          persistentOptions.headers.Bearer = instance.apiKey; // Ensure apiKey for NEW instance
          $rootScope.instance = instance;

          if (isInstanceMode(config)) {
            if (config.configured) {
              return $rootScope.resources.$get('digest', {
                instanceId: config.instanceId,
                digestId: config.globalDigestId
              });
            } else {
              return instance.$post('digest-create', {}, {
                description: 'Global Repositories List'
              });
            }
          } else if (isDigestMode(config)) {
            if (isDigestConfigured(config)) {
              return $rootScope.resources.$get('digest', {
                instanceId: config.instanceId,
                digestId: config.configMode.digestId
              });
            } else {
              return instance.$post('digest-create', {}, {
                description: 'Repositories List'
              });
            }
          } else {
            // Don't know what state it's in here, but probably didn't get here naturally, so just return false
            return false;
          }
        }
      })
      .then(function(digest) {
        if (digest) {
          $rootScope.digest = digest;
          // Check if we are in digestMode and need to save
          if (isDigestMode(config) && !isDigestConfigured(config)) {
            config.configMode.digestId = digest.digestId;
            configDigestModeSave(config.configMode)
              .then(function(configModeSaveResult) {
                $location.path('/inboxes');
              })
              .catch(errorHandler);
          } else {
            $location.path('/inboxes');
          }
        } else {
          $location.path('/inboxes');
        }
      })
      .catch(errorHandler);
  }
]);

commitStreamAdminControllers.controller('InboxesController', [
  '$rootScope',
  '$scope',
  '$timeout',
  'serviceUrl',
  'configSaveUrl',
  '$http',
  '$q',
  'prompt',
  '$location',
  'persistentOptions',
  function($rootScope, $scope, $timeout, serviceUrl,
    configSaveUrl, $http, $q, prompt, $location, persistentOptions) {

    $scope.inboxesVisible = function() {
      // Only display when we actually have the config in $scope!
      return $rootScope.config;
    };

    var isCustomDigest = function() {
      return $scope.digestConfig.selection === 'useCustomDigest';
    };

    $scope.magicWorks = function(value) {
      // 'disabled'
      // 'useGlobalDigest'
      // 'useCustomDigest'
      //alert(value);
    }

    $scope.inboxesVisible2 = function() {
      return $scope.config.configMode.type === 'instance' || $scope.digestConfig.selection !== 'disabled';
    };

    $scope.editAllowed = function() {
      return $scope.config.configMode.type === 'instance' || $scope.digestConfig.selection === 'useCustomDigest';
    };

    $scope.getHeading = function() {
      if ($scope.isInstanceMode()) return 'Setup Global Repositories';
      if ($scope.isDigestMode()) return isCustomDigest() ? 'Setup TeamRoom Repositories' : 'Active Global Repositories';
    };

    if (!$rootScope.config) {
      $location.path('/');
      return;
    }

    $scope.newInbox = {
      url: '',
      name: '',
      family: 'GitHub' // Until we support other systems, hard-code this.
    };

    $scope.serviceUrl = serviceUrl;
    $scope.inboxes = [];

    $scope.enabledState = {
      enabled: $rootScope.config.enabled,
      applying: false,
      onText: 'Enabled',
      offText: 'Disabled'
    };

    $scope.message = {
      value: ''
    };

    $scope.messageActive = function() {
      return $scope.message.value !== '';
    };

    $scope.error = {
      value: ''
    };

    $scope.errorActive = function() {
      return $scope.error.value !== '';
    };

    // NOTE: this is a bit of a hack to remove errors upon network request to clear
    // out the UI prompts
    persistentOptions.transformUrl = function(url) {
      $scope.error.value = '';
      return url;
    };

    $scope.urlPattern = /^https?\:\/\/.{1,}\/.{1,}$/;

    $scope.digestConfig = {
      selection: 'disabled'
    };

    $scope.inboxName = function() {
      if (!$scope.newInbox.url || $scope.newInbox.url.length < 1) return '...';
      var index = $scope.newInbox.url.lastIndexOf('/');
      if (index < 0) return '...';
      if (index === $scope.newInbox.url.length - 1) return '...';
      return $scope.newInbox.url.substr(index + 1);
    };

    var errorHandler = function(error) {
      if (error.data && error.data.errors && error.data.errors.length) {
        $scope.error.value = error.data.errors[0];
      } else {
        $scope.error.value = 'There was an unexpected error when processing your request.';
      }
    };

    var configSave = function(enabled) {
      $rootScope.config.enabled = enabled;

      if (!$rootScope.config.configured) {
        return $rootScope.resources.$post('instances')
          .then(function(instance) {
            persistentOptions.headers.Bearer = instance.apiKey; // Ensure apiKey for NEW instance
            $rootScope.instance = instance;
            return instance.$post('digest-create', {}, {
              description: 'Global Repositories List'
            });
          })
          .then(function(digest) {
            $rootScope.digest = digest;
            $rootScope.config.instanceId = $rootScope.instance.instanceId;
            $rootScope.config.globalDigestId = digest.digestId;
            $rootScope.config.apiKey = $rootScope.instance.apiKey;
            $rootScope.config.configured = true;
            if (configSaveUrl) return $http.post(configSaveUrl, $rootScope.config);
            return $q.when(true);
          });
      } else {
        if (configSaveUrl) return $http.post(configSaveUrl, $rootScope.config);
        return $q.when(true);
      }
    };

    var inboxConfigure = function(inbox) {
      var links = inbox.$links();
      inbox.addCommit = links['add-commit'].href + '?apiKey=' + persistentOptions.headers.Bearer;
      inbox.removeHref = links['self'].href + '?apiKey=' + persistentOptions.headers.Bearer;
    };

    var inboxesGet = function() {
      if ($rootScope.digest) {
        $rootScope.digest.$get('inboxes')
          .then(function(inboxesRes) {
            return inboxesRes.$get('inboxes');
          }).then(function(inboxes) {
            $scope.inboxes.length = 0;
            inboxes.forEach(function(inbox) {
              inboxConfigure(inbox);
              $scope.inboxes.unshift(inbox);
            });
          })
          .catch(errorHandler);
      }
    };

    var inboxesUpdate = function(enabled) {
      if (enabled) {
        $timeout(function() {
          $('.inbox-url').select().focus();
        });
      }
      inboxesGet();
    }

    inboxesUpdate($rootScope.config.enabled);

    $scope.isInstanceMode = function() {
      return isInstanceMode($rootScope.config);
    };

    $scope.isDigestMode = function() {
      return isDigestMode($rootScope.config);
    };

    $scope.enabledChanged = function() {
      $scope.newInbox.url = '';

      var toggle = $('.commitstream-admin .enabled');

      var enabled = toggle.prop('checked');

      $scope.enabledState.applying = true;
      toggle.bootstrapToggle('disable');

      configSave(enabled).then(function(configSaveResult) {
        // TODO need to handle configSaveResult?
        inboxesUpdate(enabled);
      })
        .catch(errorHandler)
        .finally(function() {
          $scope.enabledState.applying = false;
          toggle.bootstrapToggle('enable');
        });
    };

    $scope.applying = function() {
      return $scope.enabledState.applying;
    };

    $scope.overlayVisible = function() {
      return $scope.enabledState.applying || !$rootScope.config.enabled;
    };

    $scope.adjustOverlay = function() {
      var repolistWidth = $('.repos-list').width();
      var repolistHeight = $('.repos-list').height();
      $('.repos-section .overlay').height(repolistHeight);
      $('.repos-section .overlay').width(repolistWidth);
    };

    // A jQuery based way of handling resizing of the window to adjust the overlay
    // when CS is disabled.
    // $(window).resize(function(){
    //   $scope.adjustOverlay();
    // });

    $scope.inboxCreating = false;

    $scope.inboxCreate = function() {
      //TODO: put the first 3 lines after the try in a promise
      try {
        $scope.inboxCreating = true;
        var index = $scope.newInbox.url.lastIndexOf('/');
        $scope.newInbox.name = $scope.newInbox.url.substr(index + 1);

        $rootScope.digest.$post('inbox-create', {}, $scope.newInbox)
          .then(function(inbox) {
            inboxConfigure(inbox);
            $scope.inboxes.unshift(inbox);
            $scope.newInbox.url = '';
            $scope.inboxHighlightTop(inbox.removeHref);
          })
          .catch(errorHandler)
          .finally(function() {
            $scope.inboxCreating = false;
          });
      } catch (e) {
        $scope.inboxCreating = false;
      }
    };

    $scope.inboxRemove = function(inbox) {
      prompt({
        title: 'Remove Repository?',
        message: 'Are you sure you want to remove the repository ' + inbox.name + '?',
        buttons: [{
          label: 'Remove',
          primary: true,
          class: 'action-remove'
        }, {
          label: 'Cancel',
          cancel: true,
          class: 'action-cancel'
        }]
      }).then(function() {
        inbox.$del('self').then(function(result) {
          $scope.message.value = 'Successfully removed repository';
          var index = $scope.inboxes.indexOf(inbox);
          $scope.inboxes.splice(index, 1);
          $timeout(function() {
            $('.commitstream-admin .message').fadeOut('slow', function() {
              $scope.message.value = '';
            });
          }, 4000);
        })
          .catch(errorHandler);
      });
    };

    var inboxHighlight = function(el) {
      if ($rootScope.config.enabled) {
        el.focus();
        el.select();
      }
    };

    $scope.inboxHighlight = function(evt) {
      inboxHighlight(evt.currentTarget);
    };

    $scope.inboxHighlightTop = function() {
      $timeout(function() {
        var el = $($('.commitstream-admin .inbox')[0]);
        inboxHighlight(el);
      }, 0);
    };

  }
]);<commit_msg>S-52697<commit_after>'use strict';

var commitStreamAdminControllers = angular.module('commitStreamAdminControllers', []);

var isInstanceMode = function(config) {
  if (!config.configMode) return true;
  return config.configMode.type === 'instance';
};

var isDigestMode = function(config) {
  return config.configMode && config.configMode.type === 'digest';
};

var isDigestConfigured = function(config) {
  return config.configMode.configured;
};

commitStreamAdminControllers.controller('CommitStreamController', [
  '$rootScope',
  '$scope',
  '$http',
  '$q',
  '$location',
  'CommitStreamApi',
  'serviceUrl',
  'configGetUrl',
  'configSaveUrl',
  'persistentOptions',
  function($rootScope, $scope, $http, $q, $location, CommitStreamApi, serviceUrl, configGetUrl, configSaveUrl, persistentOptions) {
    var config;

    $scope.loaderUrl = serviceUrl + '/ajax-loader.gif';

    var loading = true;

    $scope.loading = function() {
      return loading;
    };

    var errorHandler = function(error) {
      loading = false;
      if (error.data && error.data.errors && error.data.errors.length) {
        $scope.error.value = error.data.errors[0];
      } else {
        $scope.error.value = 'There was an unexpected error when processing your request.';
      }
    };

    $scope.error = {
      value: ''
    };

    $scope.errorActive = function() {
      return $scope.error.value !== '';
    };

    // For teamroom settings:
    var configDigestModeSave = function(configMode) {
      if (!configMode.configured) {
        if (configSaveUrl) return $http.post(configSaveUrl, configMode);
        return $q.when(true);
      }
      // TODO: do we even need this below?
      return $q.when(true);
    };

    CommitStreamApi
      .load()
      .then(function(resources) {
        $rootScope.resources = resources;
        if (!configGetUrl) return {
          data: {
            configMode: {
              type: 'instance',
              digestId: '',
              configured: false,
              enabled: false,
              useGlobalDigestId: false
            },
            serviceUrl: serviceUrl,
            instanceId: '',
            apiKey: '',
            globalDigestId: '',
            configured: false,
            enabled: false
          }
        }
        return $http.get(configGetUrl);
      })
      .then(function(configRes) {
        // TODO handle null case?
        config = configRes.data;
        $rootScope.config = config;

        if (config.configured) {
          persistentOptions.headers.Bearer = config.apiKey;
          return $rootScope.resources.$get('instance', {
            instanceId: config.instanceId
          });
        } else {
          // return $rootScope.resources.$post('instances');
          return false;
        }
      })
      .then(function(instance) {
        if (instance) {
          persistentOptions.headers.Bearer = instance.apiKey; // Ensure apiKey for NEW instance
          $rootScope.instance = instance;

          if (isInstanceMode(config)) {
            if (config.configured) {
              return $rootScope.resources.$get('digest', {
                instanceId: config.instanceId,
                digestId: config.globalDigestId
              });
            } else {
              return instance.$post('digest-create', {}, {
                description: 'Global Repositories List'
              });
            }
          } else if (isDigestMode(config)) {
            if (isDigestConfigured(config)) {
              return $rootScope.resources.$get('digest', {
                instanceId: config.instanceId,
                digestId: config.configMode.digestId
              });
            } else {
              // return instance.$post('digest-create', {}, {
              //   description: 'Repositories List'
              // });
            }
          } else {
            // Don't know what state it's in here, but probably didn't get here naturally, so just return false
            return false;
          }
        }
      })
      .then(function(digest) {
        if (digest) {
          $rootScope.digest = digest;
          // Check if we are in digestMode and need to save
          if (isDigestMode(config) && !isDigestConfigured(config)) {
            //   config.configMode.digestId = digest.digestId;
            //   configDigestModeSave(config.configMode)
            //     .then(function(configModeSaveResult) {
            //       $location.path('/inboxes');
            //     })
            //     .catch(errorHandler);
          } else {
            $location.path('/inboxes');
          }
        } else {
          $location.path('/inboxes');
        }
      })
      .catch(errorHandler);
  }
]);

commitStreamAdminControllers.controller('InboxesController', [
  '$rootScope',
  '$scope',
  '$timeout',
  'serviceUrl',
  'configSaveUrl',
  '$http',
  '$q',
  'prompt',
  '$location',
  'persistentOptions',
  function($rootScope, $scope, $timeout, serviceUrl,
    configSaveUrl, $http, $q, prompt, $location, persistentOptions) {

    $scope.inboxesVisible = function() {
      // Only display when we actually have the config in $scope!
      return $rootScope.config;
    };

    var isCustomDigest = function() {
      return $scope.digestConfig.selection === 'useCustomDigest';
    };

    $scope.magicWorks = function(value) {
      // 'disabled'
      // 'useGlobalDigest'
      // 'useCustomDigest'
      //alert(value);
      if (isCustomDigest()) {
        console.log(isDigestConfigured($scope.config))
        console.log($rootScope.instance)
      }

    }

    $scope.inboxesVisible2 = function() {
      return $scope.config.configMode.type === 'instance' || $scope.digestConfig.selection !== 'disabled';
    };

    $scope.editAllowed = function() {
      return $scope.config.configMode.type === 'instance' || $scope.digestConfig.selection === 'useCustomDigest';
    };

    $scope.getHeading = function() {
      if ($scope.isInstanceMode()) return 'Setup Global Repositories';
      if ($scope.isDigestMode()) return isCustomDigest() ? 'Setup TeamRoom Repositories' : 'Active Global Repositories';
    };

    if (!$rootScope.config) {
      $location.path('/');
      return;
    }

    $scope.newInbox = {
      url: '',
      name: '',
      family: 'GitHub' // Until we support other systems, hard-code this.
    };

    $scope.serviceUrl = serviceUrl;
    $scope.inboxes = [];

    $scope.enabledState = {
      enabled: $rootScope.config.enabled,
      applying: false,
      onText: 'Enabled',
      offText: 'Disabled'
    };

    $scope.message = {
      value: ''
    };

    $scope.messageActive = function() {
      return $scope.message.value !== '';
    };

    $scope.error = {
      value: ''
    };

    $scope.errorActive = function() {
      return $scope.error.value !== '';
    };

    // NOTE: this is a bit of a hack to remove errors upon network request to clear
    // out the UI prompts
    persistentOptions.transformUrl = function(url) {
      $scope.error.value = '';
      return url;
    };

    $scope.urlPattern = /^https?\:\/\/.{1,}\/.{1,}$/;

    $scope.digestConfig = {
      selection: 'disabled'
    };

    $scope.inboxName = function() {
      if (!$scope.newInbox.url || $scope.newInbox.url.length < 1) return '...';
      var index = $scope.newInbox.url.lastIndexOf('/');
      if (index < 0) return '...';
      if (index === $scope.newInbox.url.length - 1) return '...';
      return $scope.newInbox.url.substr(index + 1);
    };

    var errorHandler = function(error) {
      if (error.data && error.data.errors && error.data.errors.length) {
        $scope.error.value = error.data.errors[0];
      } else {
        $scope.error.value = 'There was an unexpected error when processing your request.';
      }
    };

    var configSave = function(enabled) {
      $rootScope.config.enabled = enabled;

      if (!$rootScope.config.configured) {
        return $rootScope.resources.$post('instances')
          .then(function(instance) {
            persistentOptions.headers.Bearer = instance.apiKey; // Ensure apiKey for NEW instance
            $rootScope.instance = instance;
            return instance.$post('digest-create', {}, {
              description: 'Global Repositories List'
            });
          })
          .then(function(digest) {
            $rootScope.digest = digest;
            $rootScope.config.instanceId = $rootScope.instance.instanceId;
            $rootScope.config.globalDigestId = digest.digestId;
            $rootScope.config.apiKey = $rootScope.instance.apiKey;
            $rootScope.config.configured = true;
            if (configSaveUrl) return $http.post(configSaveUrl, $rootScope.config);
            return $q.when(true);
          });
      } else {
        if (configSaveUrl) return $http.post(configSaveUrl, $rootScope.config);
        return $q.when(true);
      }
    };

    var inboxConfigure = function(inbox) {
      var links = inbox.$links();
      inbox.addCommit = links['add-commit'].href + '?apiKey=' + persistentOptions.headers.Bearer;
      inbox.removeHref = links['self'].href + '?apiKey=' + persistentOptions.headers.Bearer;
    };

    var inboxesGet = function() {
      if ($rootScope.digest) {
        $rootScope.digest.$get('inboxes')
          .then(function(inboxesRes) {
            return inboxesRes.$get('inboxes');
          }).then(function(inboxes) {
            $scope.inboxes.length = 0;
            inboxes.forEach(function(inbox) {
              inboxConfigure(inbox);
              $scope.inboxes.unshift(inbox);
            });
          })
          .catch(errorHandler);
      }
    };

    var inboxesUpdate = function(enabled) {
      if (enabled) {
        $timeout(function() {
          $('.inbox-url').select().focus();
        });
      }
      inboxesGet();
    }

    inboxesUpdate($rootScope.config.enabled);

    $scope.isInstanceMode = function() {
      return isInstanceMode($rootScope.config);
    };

    $scope.isDigestMode = function() {
      return isDigestMode($rootScope.config);
    };

    $scope.enabledChanged = function() {
      $scope.newInbox.url = '';

      var toggle = $('.commitstream-admin .enabled');

      var enabled = toggle.prop('checked');

      $scope.enabledState.applying = true;
      toggle.bootstrapToggle('disable');

      configSave(enabled).then(function(configSaveResult) {
        // TODO need to handle configSaveResult?
        inboxesUpdate(enabled);
      })
        .catch(errorHandler)
        .finally(function() {
          $scope.enabledState.applying = false;
          toggle.bootstrapToggle('enable');
        });
    };

    $scope.applying = function() {
      return $scope.enabledState.applying;
    };

    $scope.overlayVisible = function() {
      return $scope.enabledState.applying || !$rootScope.config.enabled;
    };

    $scope.adjustOverlay = function() {
      var repolistWidth = $('.repos-list').width();
      var repolistHeight = $('.repos-list').height();
      $('.repos-section .overlay').height(repolistHeight);
      $('.repos-section .overlay').width(repolistWidth);
    };

    // A jQuery based way of handling resizing of the window to adjust the overlay
    // when CS is disabled.
    // $(window).resize(function(){
    //   $scope.adjustOverlay();
    // });

    $scope.inboxCreating = false;

    $scope.inboxCreate = function() {
      //TODO: put the first 3 lines after the try in a promise
      try {
        $scope.inboxCreating = true;
        var index = $scope.newInbox.url.lastIndexOf('/');
        $scope.newInbox.name = $scope.newInbox.url.substr(index + 1);

        $rootScope.digest.$post('inbox-create', {}, $scope.newInbox)
          .then(function(inbox) {
            inboxConfigure(inbox);
            $scope.inboxes.unshift(inbox);
            $scope.newInbox.url = '';
            $scope.inboxHighlightTop(inbox.removeHref);
          })
          .catch(errorHandler)
          .finally(function() {
            $scope.inboxCreating = false;
          });
      } catch (e) {
        $scope.inboxCreating = false;
      }
    };

    $scope.inboxRemove = function(inbox) {
      prompt({
        title: 'Remove Repository?',
        message: 'Are you sure you want to remove the repository ' + inbox.name + '?',
        buttons: [{
          label: 'Remove',
          primary: true,
          class: 'action-remove'
        }, {
          label: 'Cancel',
          cancel: true,
          class: 'action-cancel'
        }]
      }).then(function() {
        inbox.$del('self').then(function(result) {
          $scope.message.value = 'Successfully removed repository';
          var index = $scope.inboxes.indexOf(inbox);
          $scope.inboxes.splice(index, 1);
          $timeout(function() {
            $('.commitstream-admin .message').fadeOut('slow', function() {
              $scope.message.value = '';
            });
          }, 4000);
        })
          .catch(errorHandler);
      });
    };

    var inboxHighlight = function(el) {
      if ($rootScope.config.enabled) {
        el.focus();
        el.select();
      }
    };

    $scope.inboxHighlight = function(evt) {
      inboxHighlight(evt.currentTarget);
    };

    $scope.inboxHighlightTop = function() {
      $timeout(function() {
        var el = $($('.commitstream-admin .inbox')[0]);
        inboxHighlight(el);
      }, 0);
    };

  }
]);<|endoftext|>


<commit_before>var Substance = require('substance');
var $$ = React.createElement;
var Annotator = Substance.Document.Annotator;

var NodeView = require('./node_view');
var AnnotationView = require('./annotation_view');

// TextProperty
// ----------------
//

var TextProperty = React.createClass({
  displayName: "text-property",

  contextTypes: {
    componentFactory: React.PropTypes.object.isRequired
  },

  componentShouldUpdate: function() {
    return false;
  },

  componentDidMount: function() {
    var doc = this.props.doc;
    doc.addDocumentChangeListener(this, this.props.path, this.propertyDidChange);
    this.renderManually();
  },

  componentDidUpdate: function() {
    // this.renderManually();
  },

  componentWillUnmount: function() {
    var doc = this.props.doc;
    doc.removeDocumentChangeListener(this, this.props.path);
  },

  renderManually: function() {
    var contentView = new TextProperty.ContentView({
      doc: this.props.doc,
      node: this.props.node,
      children: this.getContent()
    })
    var fragment = contentView.render();
    var domNode = this.getDOMNode();
    domNode.innerHTML = "";
    domNode.appendChild(fragment);
  },

  getContent: function() {
    var doc = this.props.doc;
    var componentFactory = this.context.componentFactory;
    var path = this.props.path;
    var text = doc.get(path) || "";
    var annotations = doc.getIndex('annotations').get(path);

    var annotator = new Annotator();
    annotator.onText = function(context, text) {
      context.children.push(text);
    };

    annotator.onEnter = function(entry) {
      var anno = doc.get(entry.id);
      // TODO: we need a component factory, so that we can create the appropriate component
      var ViewClass = AnnotationView;
      return {
        ViewClass: ViewClass,
        props: {
          doc: doc,
          node: anno
        },
        children: []
      };
    };
    annotator.onExit = function(entry, context, parentContext) {
      var props = context.props;
      props.children = context.children;
      var view = new context.ViewClass(props);
      parentContext.children.push(view);
    };

    var root = { children: [] };
    annotator.start(root, text, annotations);

    return root.children;
  },

  // TODO: we need to join the ContentEditable dance...
  // when this is edited directly we usually do not need to update
  propertyDidChange: function(documentChange, ops, info) {
    // TODO: maybe we want to find an incremental solution
    // However, this is surprisingly fast so that almost no flickering can be observed.
    // If we can recreate/preserve annotation classes/styles this would be good way to go for now.
    // if (info && info.source === this.getDOMNode()) {
    //   console.log('Skipping update of text-property');
    //   return;
    // }
    this.renderManually();
  },

  render: function() {
    return $$('span', {
      className: "text-property " + (this.props.className || ""),
      contentEditable: true,
      style: {
        "white-space": "pre-wrap"
      },
      "data-path": this.props.path.join('.')
    });
  },

});

TextProperty.ContentView = NodeView.extend({
  createElement: function() {
    return document.createDocumentFragment();
  }
})

module.exports = TextProperty;
<commit_msg>Register TextProperties to using new event proxy api.<commit_after>var Substance = require('substance');
var $$ = React.createElement;
var Annotator = Substance.Document.Annotator;

var NodeView = require('./node_view');
var AnnotationView = require('./annotation_view');

// TextProperty
// ----------------
//

var TextProperty = React.createClass({
  displayName: "text-property",

  componentShouldUpdate: function() {
    return false;
  },

  componentDidMount: function() {
    var doc = this.props.doc;
    doc.getEventProxy('path').add(this.props.path, this, this.propertyDidChange);
    this.renderManually();
  },

  componentDidUpdate: function() {
    // this.renderManually();
  },

  componentWillUnmount: function() {
    var doc = this.props.doc;
    doc.getEventProxy('path').remove(this.props.path, this);
  },

  renderManually: function() {
    var contentView = new TextProperty.ContentView({
      doc: this.props.doc,
      node: this.props.node,
      children: this.getContent()
    })
    var fragment = contentView.render();
    var domNode = this.getDOMNode();
    domNode.innerHTML = "";
    domNode.appendChild(fragment);
  },

  getContent: function() {
    var doc = this.props.doc;
    var path = this.props.path;
    var text = doc.get(path) || "";
    var annotations = doc.getIndex('annotations').get(path);

    var annotator = new Annotator();
    annotator.onText = function(context, text) {
      context.children.push(text);
    };

    annotator.onEnter = function(entry) {
      var anno = doc.get(entry.id);
      // TODO: we need a component factory, so that we can create the appropriate component
      var ViewClass = AnnotationView;
      return {
        ViewClass: ViewClass,
        props: {
          doc: doc,
          node: anno
        },
        children: []
      };
    };
    annotator.onExit = function(entry, context, parentContext) {
      var props = context.props;
      props.children = context.children;
      var view = new context.ViewClass(props);
      parentContext.children.push(view);
    };

    var root = { children: [] };
    annotator.start(root, text, annotations);

    return root.children;
  },

  // TODO: we need to join the ContentEditable dance...
  // when this is edited directly we usually do not need to update
  propertyDidChange: function(/*documentChange, ops, info*/) {
    // TODO: maybe we want to find an incremental solution
    // However, this is surprisingly fast so that almost no flickering can be observed.
    this.renderManually();
  },

  render: function() {
    return $$('span', {
      className: "text-property " + (this.props.className || ""),
      contentEditable: true,
      style: {
        "white-space": "pre-wrap"
      },
      "data-path": this.props.path.join('.')
    });
  },

});

TextProperty.ContentView = NodeView.extend({
  createElement: function() {
    return document.createDocumentFragment();
  }
});

module.exports = TextProperty;
<|endoftext|>