<commit_before>// @flow

import { highlight, getLanguage } from 'highlight.js'
import React from 'react'
import { connect } from 'react-redux'
import { Link } from 'react-router'
import type { Dispatch } from 'redux'
import { extname } from 'path'

import styles from './FileContent.css'
import Button from '../common/atoms/Button'
import InnerContainer from '../common/layouts/InnerContainer'
import SegmentedButtonsContainer from '../common/layouts/SegmentedButtonsContainer'
import type { ReducersStateT } from '../../ducks'
import * as EntriessAction from '../../ducks/repository/entries'
import panelStyles from '../../styles/panel.css'
import type { EntryT } from '../../../types/gh'

export class RepoFileContent extends React.Component<{
  params: {
    branch: string,
    owner: string,
    repo: string,
    splat: string
  },
  contributors: Array<string>,
  dispatch: Dispatch<*>,
  entry: ?EntryT
}> {
  static defaultProps = {
    contributors: ['a', 'b', 'c']
  }

  async componentDidMount() {
    const {
      dispatch,
      params: { branch, owner, repo, splat: path = '' }
    } = this.props
    dispatch(await EntriessAction.fetch({ owner, repo, path, branch }))
  }

  render() {
    const {
      contributors,
      entry,
      params: { branch, owner, repo, splat: path = '' }
    } = this.props

    if (entry == null) return null

    const { content, size } = entry
    const lines = [].concat(content.match(/\n/g)).length
    const ext = extname(path).substr(1)
    return (
      <InnerContainer>

commit tease

            <button>{contributors.length} contributors</button>
            {contributors.map(c => (
              <Link key={c} to={`/${c}`}>

              </Link>
            ))}

            {lines} line | {size} bytes

              <SegmentedButtonsContainer>
                <Button
                  as={Link}
                  // $FlowFixMe
                  small="true"
                  to={`/${owner}/${repo}/raw/${branch}/${path}`}
                >
                  Raw
                </Button>
                <Button
                  as={Link}
                  // $FlowFixMe
                  small="true"
                  to={`/${owner}/${repo}/blame/${branch}/${path}`}
                >
                  Blame
                </Button>
                <Button
                  as={Link}
                  // $FlowFixMe
                  small="true"
                  to={`/${owner}/${repo}/commits/${branch}/${path}`}
                >
                  History
                </Button>
              </SegmentedButtonsContainer>
              <form>
                <button>

                </button>
              </form>
              <form>
                <button>

                </button>
              </form>

              {(() => {
                const e = []
                for (let i = 0; i < lines; i++) {
                  e.push(
{i + 1}
)
                }
                return e
              })()}

      </InnerContainer>
    )
  }
}

export default connect(
  ({ entries }: ReducersStateT, { params: { splat: path } }) => ({
    entry: entries.find(e => (e.path = path))
  })
)(RepoFileContent)
<commit_msg>:shirt: Typo<commit_after>// @flow

import { highlight, getLanguage } from 'highlight.js'
import React from 'react'
import { connect } from 'react-redux'
import { Link } from 'react-router'
import type { Dispatch } from 'redux'
import { extname } from 'path'

import styles from './FileContent.css'
import Button from '../common/atoms/Button'
import InnerContainer from '../common/layouts/InnerContainer'
import SegmentedButtonsContainer from '../common/layouts/SegmentedButtonsContainer'
import type { ReducersStateT } from '../../ducks'
import * as EntriesAction from '../../ducks/repository/entries'
import panelStyles from '../../styles/panel.css'
import type { EntryT } from '../../../types/gh'

export class RepoFileContent extends React.Component<{
  params: {
    branch: string,
    owner: string,
    repo: string,
    splat: string
  },
  contributors: Array<string>,
  dispatch: Dispatch<*>,
  entry: ?EntryT
}> {
  static defaultProps = {
    contributors: ['a', 'b', 'c']
  }

  async componentDidMount() {
    const {
      dispatch,
      params: { branch, owner, repo, splat: path = '' }
    } = this.props
    dispatch(await EntriesAction.fetch({ owner, repo, path, branch }))
  }

  render() {
    const {
      contributors,
      entry,
      params: { branch, owner, repo, splat: path = '' }
    } = this.props

    if (entry == null) return null

    const { content, size } = entry
    const lines = [].concat(content.match(/\n/g)).length
    const ext = extname(path).substr(1)
    return (
      <InnerContainer>

commit tease

            <button>{contributors.length} contributors</button>
            {contributors.map(c => (
              <Link key={c} to={`/${c}`}>

              </Link>
            ))}

            {lines} line | {size} bytes

              <SegmentedButtonsContainer>
                <Button
                  as={Link}
                  // $FlowFixMe
                  small="true"
                  to={`/${owner}/${repo}/raw/${branch}/${path}`}
                >
                  Raw
                </Button>
                <Button
                  as={Link}
                  // $FlowFixMe
                  small="true"
                  to={`/${owner}/${repo}/blame/${branch}/${path}`}
                >
                  Blame
                </Button>
                <Button
                  as={Link}
                  // $FlowFixMe
                  small="true"
                  to={`/${owner}/${repo}/commits/${branch}/${path}`}
                >
                  History
                </Button>
              </SegmentedButtonsContainer>
              <form>
                <button>

                </button>
              </form>
              <form>
                <button>

                </button>
              </form>

              {(() => {
                const e = []
                for (let i = 0; i < lines; i++) {
                  e.push(
{i + 1}
)
                }
                return e
              })()}

      </InnerContainer>
    )
  }
}

export default connect(
  ({ entries }: ReducersStateT, { params: { splat: path } }) => ({
    entry: entries.find(e => (e.path = path))
  })
)(RepoFileContent)
<|endoftext|>


<commit_before>YUI.add('moodle-core-formchangechecker',
    function(Y) {
        // The CSS selectors we use
        var CSS = {
        };

        var FORMCHANGECHECKERNAME = 'core-formchangechecker';

        var FORMCHANGECHECKER = function() {
            FORMCHANGECHECKER.superclass.constructor.apply(this, arguments);
        }

        Y.extend(FORMCHANGECHECKER, Y.Base, {
                /**
                 * Initialize the module
                 */
                initializer : function(config) {
                    var formid = 'form#' + this.get('formid');

                    // Add change events to the form elements
                    Y.all(formid + ' input').once('change', M.core_formchangechecker.set_form_changed, this);
                    Y.all(formid + ' textarea').once('change', M.core_formchangechecker.set_form_changed, this);
                    Y.all(formid + ' select').once('change', M.core_formchangechecker.set_form_changed, this);

                    // Add a focus event to check for changes which are made without triggering a change event
                    Y.all(formid + ' input').on('focus', this.store_initial_value, this);
                    Y.all(formid + ' textarea').on('focus', this.store_initial_value, this);
                    Y.all(formid + ' select').on('focus', this.store_initial_value, this);

                    // We need any submit buttons on the form to set the submitted flag
                    Y.one(formid).on('submit', M.core_formchangechecker.set_form_submitted, this);

                    // YUI doesn't support onbeforeunload properly so we must use the DOM to set the onbeforeunload. As
                    // a result, the has_changed must stay in the DOM too
                    window.onbeforeunload = M.core_formchangechecker.report_form_dirty_state;
                },

                /**
                 * Store the initial value of the currently focussed element
                 *
                 * If an element has been focussed and changed but not yet blurred, the on change
                 * event won't be fired. We need to store it's initial value to compare it in the
                 * get_form_dirty_state function later.
                 */
                store_initial_value : function(e) {
                    if (e.target.hasClass('ignoredirty')) {
                        // Don't warn on elements with the ignoredirty class
                        return;
                    }
                    if (M.core_formchangechecker.get_form_dirty_state()) {
                        // Clear the store_initial_value listeners as the form is already dirty so
                        // we no longer need to call this function
                        var formid = 'form#' + this.get('formid');

                        Y.all(formid + ' input').detach('focus', this.store_initial_value, this);
                        Y.all(formid + ' textarea').detach('focus', this.store_initial_value, this);
                        Y.all(formid + ' select').detach('focus', this.store_initial_value, this);
                        return;
                    }

                    // Make a note of the current element so that it can be interrogated and
                    // compared in the get_form_dirty_state function
                    M.core_formchangechecker.stateinformation.focused_element = {
                        element : e.target,
                        initial_value : e.target.get('value')
                    }
                }
            },
            {
                NAME : FORMCHANGECHECKERNAME,
                ATTRS : {
                    formid : {
                        'value' : ''
                    }
                }
            }
        );

        M.core_formchangechecker = M.core_formchangechecker || {};

        // We might have multiple instances of the form change protector
        M.core_formchangechecker.instances = M.core_formchangechecker.instances || [];
        M.core_formchangechecker.init = function(config) {
            var formchangechecker = new FORMCHANGECHECKER(config);
            M.core_formchangechecker.instances.push(formchangechecker);
            return formchangechecker;
        }

        // Store state information
        M.core_formchangechecker.stateinformation = [];

        /**
         * Set the form changed state to true
         */
        M.core_formchangechecker.set_form_changed = function(e) {
            if (e && e.target && e.target.hasClass('ignoredirty')) {
                // Don't warn on elements with the ignoredirty class
                return;
            }
            M.core_formchangechecker.stateinformation.formchanged = 1;

            // Once the form has been marked as dirty, we no longer need to keep track of form elements
            // which haven't yet blurred
            delete M.core_formchangechecker.stateinformation.focused_element;
        }

        /**
         * Set the form submitted state to true
         */
        M.core_formchangechecker.set_form_submitted = function() {
            M.core_formchangechecker.stateinformation.formsubmitted = 1;
        }

        /**
         * Attempt to determine whether the form has been modified in any way and
         * is thus 'dirty'
         *
         * @return Integer 1 is the form is dirty; 0 if not
         */
        M.core_formchangechecker.get_form_dirty_state = function() {
            var state = M.core_formchangechecker.stateinformation;

            // If the form was submitted, then return a non-dirty state
            if (state.formsubmitted) {
                return 0;
            }

            // If any fields have been marked dirty, return a dirty state
            if (state.formchanged) {
                return 1;
            }

            // If a field has been focused and changed, but still has focus then the browser won't fire the
            // onChange event. We check for this eventuality here
            if (state.focused_element) {
                if (state.focused_element.element.get('value') != state.focused_element.initial_value) {
                    return 1;
                }
            }

            // Handle TinyMCE editor instances
            // We can't add a listener in the initializer as the editors may not have been created by that point
            // so we do so here instead
            if (typeof tinyMCE != 'undefined') {
                for (var editor in tinyMCE.editors) {
                    if (tinyMCE.editors[editor].isDirty()) {
                        return 1;
                    }
                }
            }

            // If we reached here, then the form hasn't met any of the dirty conditions
            return 0;
        };

        /**
         * Return a suitable message if changes have been made to a form
         */
        M.core_formchangechecker.report_form_dirty_state = function(e) {
            if (!M.core_formchangechecker.get_form_dirty_state()) {
                // the form is not dirty, so don't display any message
                return;
            }

            // This is the error message that we'll show to browsers which support it
            var warningmessage = M.util.get_string('changesmadereallygoaway', 'moodle');

            // Most browsers are happy with the returnValue being set on the event
            // But some browsers do not consistently pass the event
            if (e) {
                e.returnValue = warningmessage;
            }

            // But some require it to be returned instead
            return warningmessage;
        };
    },
    '@VERSION@', {
        requires : ['base']
    }
);
<commit_msg>MDL-35673 Improve performance of formchangechecker event creation<commit_after>YUI.add('moodle-core-formchangechecker',
    function(Y) {
        // The CSS selectors we use
        var CSS = {
        };

        var FORMCHANGECHECKERNAME = 'core-formchangechecker';

        var FORMCHANGECHECKER = function() {
            FORMCHANGECHECKER.superclass.constructor.apply(this, arguments);
        }

        Y.extend(FORMCHANGECHECKER, Y.Base, {

                // The delegated listeners we need to detach after the initial value has been stored once
                initialvaluelisteners : [],

                /**
                 * Initialize the module
                 */
                initializer : function(config) {
                    var formid = 'form#' + this.get('formid');

                    // Add change events to the form elements
                    var currentform = Y.one(formid);
                    currentform.delegate('change', M.core_formchangechecker.set_form_changed, 'input', this);
                    currentform.delegate('change', M.core_formchangechecker.set_form_changed, 'textarea', this);
                    currentform.delegate('change', M.core_formchangechecker.set_form_changed, 'select', this);

                    // Add a focus event to check for changes which are made without triggering a change event
                    this.initialvaluelisteners.push(currentform.delegate('focus', this.store_initial_value, 'input', this));
                    this.initialvaluelisteners.push(currentform.delegate('focus', this.store_initial_value, 'textarea', this));
                    this.initialvaluelisteners.push(currentform.delegate('focus', this.store_initial_value, 'select', this));

                    // We need any submit buttons on the form to set the submitted flag
                    Y.one(formid).on('submit', M.core_formchangechecker.set_form_submitted, this);

                    // YUI doesn't support onbeforeunload properly so we must use the DOM to set the onbeforeunload. As
                    // a result, the has_changed must stay in the DOM too
                    window.onbeforeunload = M.core_formchangechecker.report_form_dirty_state;
                },

                /**
                 * Store the initial value of the currently focussed element
                 *
                 * If an element has been focussed and changed but not yet blurred, the on change
                 * event won't be fired. We need to store it's initial value to compare it in the
                 * get_form_dirty_state function later.
                 */
                store_initial_value : function(e) {
                    if (e.target.hasClass('ignoredirty')) {
                        // Don't warn on elements with the ignoredirty class
                        return;
                    }
                    if (M.core_formchangechecker.get_form_dirty_state()) {
                        // Clear the store_initial_value listeners as the form is already dirty so
                        // we no longer need to call this function
                        var formid = 'form#' + this.get('formid');

                        // Detach all listen events to prevent duplicate initial value setting
                        var thisevent;
                        while (thisevent = this.initialvaluelisteners.shift()) {
                            thisevent.detach();
                        }

                        return;
                    }

                    // Make a note of the current element so that it can be interrogated and
                    // compared in the get_form_dirty_state function
                    M.core_formchangechecker.stateinformation.focused_element = {
                        element : e.target,
                        initial_value : e.target.get('value')
                    }
                }
            },
            {
                NAME : FORMCHANGECHECKERNAME,
                ATTRS : {
                    formid : {
                        'value' : ''
                    }
                }
            }
        );

        M.core_formchangechecker = M.core_formchangechecker || {};

        // We might have multiple instances of the form change protector
        M.core_formchangechecker.instances = M.core_formchangechecker.instances || [];
        M.core_formchangechecker.init = function(config) {
            var formchangechecker = new FORMCHANGECHECKER(config);
            M.core_formchangechecker.instances.push(formchangechecker);
            return formchangechecker;
        }

        // Store state information
        M.core_formchangechecker.stateinformation = [];

        /**
         * Set the form changed state to true
         */
        M.core_formchangechecker.set_form_changed = function(e) {
            if (e && e.target && e.target.hasClass('ignoredirty')) {
                // Don't warn on elements with the ignoredirty class
                return;
            }
            M.core_formchangechecker.stateinformation.formchanged = 1;

            // Once the form has been marked as dirty, we no longer need to keep track of form elements
            // which haven't yet blurred
            delete M.core_formchangechecker.stateinformation.focused_element;
        }

        /**
         * Set the form submitted state to true
         */
        M.core_formchangechecker.set_form_submitted = function() {
            M.core_formchangechecker.stateinformation.formsubmitted = 1;
        }

        /**
         * Attempt to determine whether the form has been modified in any way and
         * is thus 'dirty'
         *
         * @return Integer 1 is the form is dirty; 0 if not
         */
        M.core_formchangechecker.get_form_dirty_state = function() {
            var state = M.core_formchangechecker.stateinformation;

            // If the form was submitted, then return a non-dirty state
            if (state.formsubmitted) {
                return 0;
            }

            // If any fields have been marked dirty, return a dirty state
            if (state.formchanged) {
                return 1;
            }

            // If a field has been focused and changed, but still has focus then the browser won't fire the
            // onChange event. We check for this eventuality here
            if (state.focused_element) {
                if (state.focused_element.element.get('value') != state.focused_element.initial_value) {
                    return 1;
                }
            }

            // Handle TinyMCE editor instances
            // We can't add a listener in the initializer as the editors may not have been created by that point
            // so we do so here instead
            if (typeof tinyMCE != 'undefined') {
                for (var editor in tinyMCE.editors) {
                    if (tinyMCE.editors[editor].isDirty()) {
                        return 1;
                    }
                }
            }

            // If we reached here, then the form hasn't met any of the dirty conditions
            return 0;
        };

        /**
         * Return a suitable message if changes have been made to a form
         */
        M.core_formchangechecker.report_form_dirty_state = function(e) {
            if (!M.core_formchangechecker.get_form_dirty_state()) {
                // the form is not dirty, so don't display any message
                return;
            }

            // This is the error message that we'll show to browsers which support it
            var warningmessage = M.util.get_string('changesmadereallygoaway', 'moodle');

            // Most browsers are happy with the returnValue being set on the event
            // But some browsers do not consistently pass the event
            if (e) {
                e.returnValue = warningmessage;
            }

            // But some require it to be returned instead
            return warningmessage;
        };
    },
    '@VERSION@', {
        requires : ['base', 'event-focus']
    }
);
<|endoftext|>


<commit_before>// Amara, universalsubtitles.org
//
// Copyright (C) 2013 Participatory Culture Foundation
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program.  If not, see
// http://www.gnu.org/licenses/agpl-3.0.html.

/**
 * @fileoverview A model in true MVC sense: dispatches events when model
 *     changes. This keeps disparate parts of the UI which are interested
 *     in model state (e.g. timeline, sync panel, video) informed when
 *     alterations are made to subtitles.
 */

goog.provide('unisubs.subtitle.EditableCaptionSet');

/**
 * @constructor
 * @param {array.<object.<string, *>>} existingJsonCaptions No sort order necessary.
 * @param {boolean=} opt_completed Only meaningful for non-dependent subs.
 * @param {string=} opt_title
 * @param {string=} opt_description
 * @param {boolean=} opt_forkedDuringEdits This is a bit ugly, but this parameter should only be used
 *     when deserializing an EditableCaptionSet from memory after a finish failure. It means that
 *     during the failed editing session, the EditableCaptionSet got forked.
 */
unisubs.subtitle.EditableCaptionSet = function(dfxp, opt_completed, opt_title, opt_forkedDuringEdits, opt_description, opt_languageName, opt_languageIsRTL, opt_isModerated, opt_languageWasForked) {
    goog.events.EventTarget.call(this);
    var that = this;
    var c;

    this.x = new window['AmaraDFXPParser']();
    this.x['init'](dfxp);
    window.x = this.x;

    this.captions_ = goog.array.map(
        this.x['getSubtitles'](), function(node) {
            c = new unisubs.subtitle.EditableCaption(node, that.x);
            c.setParentEventTarget(that);
            return c;
        });
    var i;

    this.setPreviousAndNextCaptions();

    this.completed = opt_completed;

    this.title = opt_title;
    this.description = opt_description;

    this.originalTitle = opt_title;
    this.originalDescription = opt_description;

    this.forkedDuringEdits_ = !!opt_forkedDuringEdits;
    this.languageName = opt_languageName;
    this.languageIsRTL = opt_languageIsRTL;
    this.isModerated = opt_isModerated;

    this.languageWasForked = opt_languageWasForked;
};

goog.inherits(unisubs.subtitle.EditableCaptionSet, goog.events.EventTarget);

unisubs.subtitle.EditableCaptionSet.EventType = {
    CLEAR_ALL: 'clearall',
    CLEAR_TIMES: 'cleartimes',
    RESET_SUBS: 'resetsubs',
    ADD: 'addsub',
    DELETE: 'deletesub'
};

/**
 * Always in ascending order by start time.
 */
unisubs.subtitle.EditableCaptionSet.prototype.captionsWithTimes = function() {
    return goog.array.filter(
        this.captions_, function(c) { return c.getStartTime() != -1; });
};

unisubs.subtitle.EditableCaptionSet.prototype.clear = function() {
    this.x['removeSubtitles']();
    this.dispatchEvent(
        unisubs.subtitle.EditableCaptionSet.EventType.CLEAR_ALL);
};
unisubs.subtitle.EditableCaptionSet.prototype.clearTimes = function() {
    this.x['clearAllTimes']();
    this.dispatchEvent(
        unisubs.subtitle.EditableCaptionSet.EventType.CLEAR_TIMES);
};
unisubs.subtitle.EditableCaptionSet.prototype.needsTranslation = function() {
    return this.x['needsAnyTranscribed']();
};
unisubs.subtitle.EditableCaptionSet.prototype.resetSubs = function() {
    this.x['resetSubtitles']();
    this.dispatchEvent(
        unisubs.subtitle.EditableCaptionSet.EventType.RESET_SUBS);
};
unisubs.subtitle.EditableCaptionSet.prototype.count = function() {
    return this.x['subtitlesCount']();
};
unisubs.subtitle.EditableCaptionSet.prototype.caption = function(index) {
    return this.captions_[index];
};
unisubs.subtitle.EditableCaptionSet.prototype.makeDFXPString = function() {
    return this.x['xmlToString'](true);
};
//unisubs.subtitle.EditableCaptionSet.prototype.makeJsonSubs = function() {
    //return goog.array.map(this.captions_, function(c) { return c.json; });
//};
unisubs.subtitle.EditableCaptionSet.prototype.nonblankSubtitles = function() {
    var nonBlank = goog.array.filter(this.captions_, function(caption, index){
       return caption.getTrimmedText()  !== '';
    })
    return nonBlank;
};
unisubs.subtitle.EditableCaptionSet.prototype.identicalTo = function(otherCaptionSet) {
    var myNonblanks = this.nonblankSubtitles();
    var otherNonblanks = otherCaptionSet.nonblankSubtitles();
    if (myNonblanks.length != otherNonblanks.length)
        return false;
    for (var i = 0; i < myNonblanks.length; i++)
        if (!myNonblanks[i].identicalTo(otherNonblanks[i]))
            return false;
    return true;
};
unisubs.subtitle.EditableCaptionSet.prototype.addNewDependentSubtitle = function(originalNode, dfxpWrapper, atIndex) {
    var newNode = dfxpWrapper['cloneSubtitle'](originalNode,false);
    var c = this.insertCaption(atIndex, newNode);
    return c;
};

/**
 *
 * @param {Number} atIndex The next subtitle's subOrder
 *     (returned by EditableCaption#getSubOrder())
 */
unisubs.subtitle.EditableCaptionSet.prototype.insertCaption = function(atIndex, newNode) {
    var prevSub;
    var nextSub = this.captions_[atIndex] || this.captions_[this.captions_.length -1];
    if(atIndex >0){
        prevSub = nextSub.getPreviousCaption();
    }
    var c;
    if (newNode){
        // if you are adding subs that are in the source language
        // but not the translated one, you want to keep the node
        // as it can have other content
        this.x['addSubtitleNode'](newNode, atIndex);
        c = new unisubs.subtitle.EditableCaption(newNode, this.x);
    }else{
        // no node, you just want to add an 'empty' subtitle
        c = new unisubs.subtitle.EditableCaption(this.x['addSubtitle'](
            atIndex >= 1 ? atIndex  -1 : -1, {}, ""), this.x);
    }
    unisubs.SubTracker.getInstance().trackAdd(c.getCaptionIndex());
    goog.array.insertAt(this.captions_, c, atIndex );
    if (prevSub) {
        prevSub.setNextCaption(c);
        c.setPreviousCaption(prevSub);
    }
    c.setNextCaption(nextSub);
    nextSub.setPreviousCaption(c);
    if (c.needsSync()){
        this.setTimesOnInsertedSub_(c, prevSub, nextSub);
    }
    c.setParentEventTarget(this);
    this.dispatchEvent(
        new unisubs.subtitle.EditableCaptionSet.CaptionEvent(
            unisubs.subtitle.EditableCaptionSet.EventType.ADD,
            c));
    return c;
};
unisubs.subtitle.EditableCaptionSet.prototype.setTimesOnInsertedSub_ = function(insertedSub, prevSub, nextSub) {
    // if the gap between the prevSub.end_time and nextSub.start time is
    // > a minimal, we insert the sub right there.
    // Else, we take the time interval between the prevSub.startTime and nextSub.endTime
    // and divide by 3, that's the duration of each of the subs now..

    if (!nextSub.needsSync() && !prevSub.needsSync()){
        // both are synced we can set actual time values for the new one
         var gap = nextSub.getStartTime() - prevSub.getEndTime();
        if (gap > 500){
            // gap is enough to fit in a sub between it
            insertedSub.setStartTime(prevSub.getEndTime());
            insertedSub.setEndTime(nextSub.getStartTime());
            return;
        }
        // no gap enough, so we need to divide times:
        var initialTime = prevSub.getStartTime();
        var finalTime = nextSub.getEndTime();
        if (finalTime == -1){
            // not synched can't rely on that timing
            finalTime = nextSub.getStartTime();
            // since there is no end time for the final one,
            // we can only split the time between the first
            // two
            gap = (finalTime - initialTime) / 2;
        }{

            gap = (finalTime - initialTime) / 3;
        }
        prevSub.setEndTime(initialTime + gap);
        insertedSub.setStartTime(prevSub.getEndTime());
        insertedSub.setEndTime(prevSub.getEndTime() + gap);
        nextSub.setStartTime(insertedSub.getEndTime());
    }
};

/**
 *
 * @param {unisubs.subtitle.EditableCaption} caption
 */
unisubs.subtitle.EditableCaptionSet.prototype.deleteCaption = function(caption) {
    var index = caption.getCaptionIndex();
    var sub = this.captions_[index];
    var prevSub = sub.getPreviousCaption();
    var nextSub = sub.getNextCaption();
    goog.array.removeAt(this.captions_, index);
    this.x['removeSubtitle'](index);
    if (prevSub){
        prevSub.setNextCaption(nextSub);
    }
    if (nextSub){
        nextSub.setPreviousCaption(prevSub);
    }
    this.dispatchEvent(
        new unisubs.subtitle.EditableCaptionSet.CaptionEvent(
            unisubs.subtitle.EditableCaptionSet.EventType.DELETE,
            sub, index));
};
unisubs.subtitle.EditableCaptionSet.prototype.findSubIndex_ = function(caption) {
    return this.x['getSubtitleIndex'](caption.node);
};
unisubs.subtitle.EditableCaptionSet.prototype.addNewCaption = function(opt_dispatchEvent) {

    // Pass the new node and the DFXP parser instance to the new EditableCaption.
    var c = new unisubs.subtitle.EditableCaption(this.x['addSubtitle'](), this.x);

    unisubs.SubTracker.getInstance().trackAdd(c.getCaptionIndex());

    c.setParentEventTarget(this);
    this.captions_.push(c)
    if (this.x['subtitlesCount']()> 1) {
        var previousCaption = this.captions_[this.captions_.length - 2];
        previousCaption.setNextCaption(c);
        c.setPreviousCaption(previousCaption);
    }
    if (opt_dispatchEvent) {
        this.dispatchEvent(
            new unisubs.subtitle.EditableCaptionSet.CaptionEvent(
                unisubs.subtitle.EditableCaptionSet.EventType.ADD,
                c));
    }
    return c;
};

/**
 * Find the last subtitle with a start time at or before time.
 * @param {number} time
 * @return {?unisubs.subtitle.EditableCaption} null if before first
 *     sub start time, or last subtitle with start time
 *     at or before playheadTime.
 */
unisubs.subtitle.EditableCaptionSet.prototype.findLastForTime = function(time) {
    var i;
    // TODO: write unit test then get rid of linear search in future.
    var captions = this.x['getSubtitles']();
    var currentStartTime;
    var nextStartTime;
    var isLast = false;
    var length = captions.length;
    for (i = 0; i < length; i++){
        currentStartTime = this.x['startTime'](this.x['getSubtitleByIndex'](i));
        isLast = i == length -1;
        if (!isLast){
            nextStartTime = this.x['startTime'](this.x['getSubtitleByIndex'](i+1));
        }else{
            nextStartTime  = undefined;
        }
        // we want a sub with a start time < play time
        // that either is the last one, or one where
        // the next is unsyced or it's start time is
        // greater then play time
        if (currentStartTime != -1 &&
            currentStartTime <= time &&
            (nextStartTime == -1 ||
             nextStartTime > time || isLast )){
            return this.captions_[i];
        }
    }
    return null;
};
/**
 * Always in ascending order by start time.
 * Returns a list of EditableCaptions that
 * should be displayed on the timeline. These are
 * all synced subs + the first unsyced sub
 */
unisubs.subtitle.EditableCaptionSet.prototype.timelineCaptions = function() {
    return goog.array.filter(
        this.captions_,
        function(c) {
            var prev = c.getPreviousCaption();
            return c.getStartTime() != -1 ||
                (prev && prev.getStartTime() != -1) ||
                (c.getStartTime() == -1 && !prev );
        });
};
/**
 * Used for both add and delete.
 * @constructor
 * @param {unisubs.subtitle.EditableCaptionSet.EventType} type of event
 * @param {unisubs.subtitle.EditableCaption} Caption the event applies to.
 */
unisubs.subtitle.EditableCaptionSet.CaptionEvent = function(type, caption, index) {
    this.type = type;
    /**
     * @type {unisubs.subtitle.EditableCaption}
     */
    this.caption = caption;
    this.index = index;
};

/*
 * @return {boolean} True if one or more captions have no time data,
 * except for the last one, whose end time (only) can be undefined.
 */
unisubs.subtitle.EditableCaptionSet.prototype.needsSync = function() {
    return this.x['needsAnySynced']();
};

unisubs.subtitle.EditableCaptionSet.prototype.fork = function(originalSubtitleState) {
    var subMap = this.makeMap();
    var translatedSub;
    goog.array.forEach(
        originalSubtitleState.SUBTITLES,
        function(origSub) {
            translatedSub = subMap[origSub['subtitle_id']];
            if (translatedSub) {
                translatedSub.fork(origSub);
            }
        });
    goog.array.sort(
        this.captions_,
        unisubs.subtitle.EditableCaption.orderCompare);
    this.forkedDuringEdits_ = true;
};
unisubs.subtitle.EditableCaptionSet.prototype.wasForkedDuringEdits = function() {
    return this.forkedDuringEdits_;
};
unisubs.subtitle.EditableCaptionSet.prototype.makeMap = function() {
    var map = {};
    goog.array.forEach(
        this.captions_,
        function(c) {
            map[c.getCaptionIndex()] = c;
        });
    return map;
};
unisubs.subtitle.EditableCaptionSet.prototype.setPreviousAndNextCaptions = function() {
    for (i = 1; i < this.captions_.length; i++) {
        this.captions_[i - 1].setNextCaption(this.captions_[i]);
        this.captions_[i].setPreviousCaption(this.captions_[i - 1]);
    }
};

unisubs.subtitle.EditableCaptionSet.prototype.hasTitleChanged = function() {
    return this.originalTitle !== this.title;
};

unisubs.subtitle.EditableCaptionSet.prototype.hasDescriptionChanged = function() {
    return this.originalDescription !== this.description;
};
<commit_msg>2110: Fixed referenence to subtitle by index, should be by node.<commit_after>// Amara, universalsubtitles.org
//
// Copyright (C) 2013 Participatory Culture Foundation
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program.  If not, see
// http://www.gnu.org/licenses/agpl-3.0.html.

/**
 * @fileoverview A model in true MVC sense: dispatches events when model
 *     changes. This keeps disparate parts of the UI which are interested
 *     in model state (e.g. timeline, sync panel, video) informed when
 *     alterations are made to subtitles.
 */

goog.provide('unisubs.subtitle.EditableCaptionSet');

/**
 * @constructor
 * @param {array.<object.<string, *>>} existingJsonCaptions No sort order necessary.
 * @param {boolean=} opt_completed Only meaningful for non-dependent subs.
 * @param {string=} opt_title
 * @param {string=} opt_description
 * @param {boolean=} opt_forkedDuringEdits This is a bit ugly, but this parameter should only be used
 *     when deserializing an EditableCaptionSet from memory after a finish failure. It means that
 *     during the failed editing session, the EditableCaptionSet got forked.
 */
unisubs.subtitle.EditableCaptionSet = function(dfxp, opt_completed, opt_title, opt_forkedDuringEdits, opt_description, opt_languageName, opt_languageIsRTL, opt_isModerated, opt_languageWasForked) {
    goog.events.EventTarget.call(this);
    var that = this;
    var c;

    this.x = new window['AmaraDFXPParser']();
    this.x['init'](dfxp);
    window.x = this.x;

    this.captions_ = goog.array.map(
        this.x['getSubtitles'](), function(node) {
            c = new unisubs.subtitle.EditableCaption(node, that.x);
            c.setParentEventTarget(that);
            return c;
        });
    var i;

    this.setPreviousAndNextCaptions();

    this.completed = opt_completed;

    this.title = opt_title;
    this.description = opt_description;

    this.originalTitle = opt_title;
    this.originalDescription = opt_description;

    this.forkedDuringEdits_ = !!opt_forkedDuringEdits;
    this.languageName = opt_languageName;
    this.languageIsRTL = opt_languageIsRTL;
    this.isModerated = opt_isModerated;

    this.languageWasForked = opt_languageWasForked;
};

goog.inherits(unisubs.subtitle.EditableCaptionSet, goog.events.EventTarget);

unisubs.subtitle.EditableCaptionSet.EventType = {
    CLEAR_ALL: 'clearall',
    CLEAR_TIMES: 'cleartimes',
    RESET_SUBS: 'resetsubs',
    ADD: 'addsub',
    DELETE: 'deletesub'
};

/**
 * Always in ascending order by start time.
 */
unisubs.subtitle.EditableCaptionSet.prototype.captionsWithTimes = function() {
    return goog.array.filter(
        this.captions_, function(c) { return c.getStartTime() != -1; });
};

unisubs.subtitle.EditableCaptionSet.prototype.clear = function() {
    this.x['removeSubtitles']();
    this.dispatchEvent(
        unisubs.subtitle.EditableCaptionSet.EventType.CLEAR_ALL);
};
unisubs.subtitle.EditableCaptionSet.prototype.clearTimes = function() {
    this.x['clearAllTimes']();
    this.dispatchEvent(
        unisubs.subtitle.EditableCaptionSet.EventType.CLEAR_TIMES);
};
unisubs.subtitle.EditableCaptionSet.prototype.needsTranslation = function() {
    return this.x['needsAnyTranscribed']();
};
unisubs.subtitle.EditableCaptionSet.prototype.resetSubs = function() {
    this.x['resetSubtitles']();
    this.dispatchEvent(
        unisubs.subtitle.EditableCaptionSet.EventType.RESET_SUBS);
};
unisubs.subtitle.EditableCaptionSet.prototype.count = function() {
    return this.x['subtitlesCount']();
};
unisubs.subtitle.EditableCaptionSet.prototype.caption = function(index) {
    return this.captions_[index];
};
unisubs.subtitle.EditableCaptionSet.prototype.makeDFXPString = function() {
    return this.x['xmlToString'](true);
};
//unisubs.subtitle.EditableCaptionSet.prototype.makeJsonSubs = function() {
    //return goog.array.map(this.captions_, function(c) { return c.json; });
//};
unisubs.subtitle.EditableCaptionSet.prototype.nonblankSubtitles = function() {
    var nonBlank = goog.array.filter(this.captions_, function(caption, index){
       return caption.getTrimmedText()  !== '';
    })
    return nonBlank;
};
unisubs.subtitle.EditableCaptionSet.prototype.identicalTo = function(otherCaptionSet) {
    var myNonblanks = this.nonblankSubtitles();
    var otherNonblanks = otherCaptionSet.nonblankSubtitles();
    if (myNonblanks.length != otherNonblanks.length)
        return false;
    for (var i = 0; i < myNonblanks.length; i++)
        if (!myNonblanks[i].identicalTo(otherNonblanks[i]))
            return false;
    return true;
};
unisubs.subtitle.EditableCaptionSet.prototype.addNewDependentSubtitle = function(originalNode, dfxpWrapper, atIndex) {
    var newNode = dfxpWrapper['cloneSubtitle'](originalNode,false);
    var c = this.insertCaption(atIndex, newNode);
    return c;
};

/**
 *
 * @param {Number} atIndex The next subtitle's subOrder
 *     (returned by EditableCaption#getSubOrder())
 */
unisubs.subtitle.EditableCaptionSet.prototype.insertCaption = function(atIndex, newNode) {
    var prevSub;
    var nextSub = this.captions_[atIndex] || this.captions_[this.captions_.length -1];
    if(atIndex >0){
        prevSub = nextSub.getPreviousCaption();
    }
    var c;
    if (newNode){
        // if you are adding subs that are in the source language
        // but not the translated one, you want to keep the node
        // as it can have other content
        this.x['addSubtitleNode'](newNode, atIndex);
        c = new unisubs.subtitle.EditableCaption(newNode, this.x);
    }else{
        // no node, you just want to add an 'empty' subtitle
        c = new unisubs.subtitle.EditableCaption(this.x['addSubtitle'](
            atIndex >= 1 ? atIndex  -1 : -1, {}, ""), this.x);
    }
    unisubs.SubTracker.getInstance().trackAdd(c.getCaptionIndex());
    goog.array.insertAt(this.captions_, c, atIndex );
    if (prevSub) {
        prevSub.setNextCaption(c);
        c.setPreviousCaption(prevSub);
    }
    c.setNextCaption(nextSub);
    nextSub.setPreviousCaption(c);
    if (c.needsSync()){
        this.setTimesOnInsertedSub_(c, prevSub, nextSub);
    }
    c.setParentEventTarget(this);
    this.dispatchEvent(
        new unisubs.subtitle.EditableCaptionSet.CaptionEvent(
            unisubs.subtitle.EditableCaptionSet.EventType.ADD,
            c));
    return c;
};
unisubs.subtitle.EditableCaptionSet.prototype.setTimesOnInsertedSub_ = function(insertedSub, prevSub, nextSub) {
    // if the gap between the prevSub.end_time and nextSub.start time is
    // > a minimal, we insert the sub right there.
    // Else, we take the time interval between the prevSub.startTime and nextSub.endTime
    // and divide by 3, that's the duration of each of the subs now..

    if (!nextSub.needsSync() && !prevSub.needsSync()){
        // both are synced we can set actual time values for the new one
         var gap = nextSub.getStartTime() - prevSub.getEndTime();
        if (gap > 500){
            // gap is enough to fit in a sub between it
            insertedSub.setStartTime(prevSub.getEndTime());
            insertedSub.setEndTime(nextSub.getStartTime());
            return;
        }
        // no gap enough, so we need to divide times:
        var initialTime = prevSub.getStartTime();
        var finalTime = nextSub.getEndTime();
        if (finalTime == -1){
            // not synched can't rely on that timing
            finalTime = nextSub.getStartTime();
            // since there is no end time for the final one,
            // we can only split the time between the first
            // two
            gap = (finalTime - initialTime) / 2;
        }{

            gap = (finalTime - initialTime) / 3;
        }
        prevSub.setEndTime(initialTime + gap);
        insertedSub.setStartTime(prevSub.getEndTime());
        insertedSub.setEndTime(prevSub.getEndTime() + gap);
        nextSub.setStartTime(insertedSub.getEndTime());
    }
};

/**
 *
 * @param {unisubs.subtitle.EditableCaption} caption
 */
unisubs.subtitle.EditableCaptionSet.prototype.deleteCaption = function(caption) {
    var index = caption.getCaptionIndex();
    var sub = this.captions_[index];
    var prevSub = sub.getPreviousCaption();
    var nextSub = sub.getNextCaption();
    goog.array.removeAt(this.captions_, index);
    this.x['removeSubtitle'](caption.node);
    if (prevSub){
        prevSub.setNextCaption(nextSub);
    }
    if (nextSub){
        nextSub.setPreviousCaption(prevSub);
    }
    this.dispatchEvent(
        new unisubs.subtitle.EditableCaptionSet.CaptionEvent(
            unisubs.subtitle.EditableCaptionSet.EventType.DELETE,
            sub, index));
};
unisubs.subtitle.EditableCaptionSet.prototype.findSubIndex_ = function(caption) {
    return this.x['getSubtitleIndex'](caption.node);
};
unisubs.subtitle.EditableCaptionSet.prototype.addNewCaption = function(opt_dispatchEvent) {

    // Pass the new node and the DFXP parser instance to the new EditableCaption.
    var c = new unisubs.subtitle.EditableCaption(this.x['addSubtitle'](), this.x);

    unisubs.SubTracker.getInstance().trackAdd(c.getCaptionIndex());

    c.setParentEventTarget(this);
    this.captions_.push(c)
    if (this.x['subtitlesCount']()> 1) {
        var previousCaption = this.captions_[this.captions_.length - 2];
        previousCaption.setNextCaption(c);
        c.setPreviousCaption(previousCaption);
    }
    if (opt_dispatchEvent) {
        this.dispatchEvent(
            new unisubs.subtitle.EditableCaptionSet.CaptionEvent(
                unisubs.subtitle.EditableCaptionSet.EventType.ADD,
                c));
    }
    return c;
};

/**
 * Find the last subtitle with a start time at or before time.
 * @param {number} time
 * @return {?unisubs.subtitle.EditableCaption} null if before first
 *     sub start time, or last subtitle with start time
 *     at or before playheadTime.
 */
unisubs.subtitle.EditableCaptionSet.prototype.findLastForTime = function(time) {
    var i;
    // TODO: write unit test then get rid of linear search in future.
    var captions = this.x['getSubtitles']();
    var currentStartTime;
    var nextStartTime;
    var isLast = false;
    var length = captions.length;
    for (i = 0; i < length; i++){
        currentStartTime = this.x['startTime'](this.x['getSubtitleByIndex'](i));
        isLast = i == length -1;
        if (!isLast){
            nextStartTime = this.x['startTime'](this.x['getSubtitleByIndex'](i+1));
        }else{
            nextStartTime  = undefined;
        }
        // we want a sub with a start time < play time
        // that either is the last one, or one where
        // the next is unsyced or it's start time is
        // greater then play time
        if (currentStartTime != -1 &&
            currentStartTime <= time &&
            (nextStartTime == -1 ||
             nextStartTime > time || isLast )){
            return this.captions_[i];
        }
    }
    return null;
};
/**
 * Always in ascending order by start time.
 * Returns a list of EditableCaptions that
 * should be displayed on the timeline. These are
 * all synced subs + the first unsyced sub
 */
unisubs.subtitle.EditableCaptionSet.prototype.timelineCaptions = function() {
    return goog.array.filter(
        this.captions_,
        function(c) {
            var prev = c.getPreviousCaption();
            return c.getStartTime() != -1 ||
                (prev && prev.getStartTime() != -1) ||
                (c.getStartTime() == -1 && !prev );
        });
};
/**
 * Used for both add and delete.
 * @constructor
 * @param {unisubs.subtitle.EditableCaptionSet.EventType} type of event
 * @param {unisubs.subtitle.EditableCaption} Caption the event applies to.
 */
unisubs.subtitle.EditableCaptionSet.CaptionEvent = function(type, caption, index) {
    this.type = type;
    /**
     * @type {unisubs.subtitle.EditableCaption}
     */
    this.caption = caption;
    this.index = index;
};

/*
 * @return {boolean} True if one or more captions have no time data,
 * except for the last one, whose end time (only) can be undefined.
 */
unisubs.subtitle.EditableCaptionSet.prototype.needsSync = function() {
    return this.x['needsAnySynced']();
};

unisubs.subtitle.EditableCaptionSet.prototype.fork = function(originalSubtitleState) {
    var subMap = this.makeMap();
    var translatedSub;
    goog.array.forEach(
        originalSubtitleState.SUBTITLES,
        function(origSub) {
            translatedSub = subMap[origSub['subtitle_id']];
            if (translatedSub) {
                translatedSub.fork(origSub);
            }
        });
    goog.array.sort(
        this.captions_,
        unisubs.subtitle.EditableCaption.orderCompare);
    this.forkedDuringEdits_ = true;
};
unisubs.subtitle.EditableCaptionSet.prototype.wasForkedDuringEdits = function() {
    return this.forkedDuringEdits_;
};
unisubs.subtitle.EditableCaptionSet.prototype.makeMap = function() {
    var map = {};
    goog.array.forEach(
        this.captions_,
        function(c) {
            map[c.getCaptionIndex()] = c;
        });
    return map;
};
unisubs.subtitle.EditableCaptionSet.prototype.setPreviousAndNextCaptions = function() {
    for (i = 1; i < this.captions_.length; i++) {
        this.captions_[i - 1].setNextCaption(this.captions_[i]);
        this.captions_[i].setPreviousCaption(this.captions_[i - 1]);
    }
};

unisubs.subtitle.EditableCaptionSet.prototype.hasTitleChanged = function() {
    return this.originalTitle !== this.title;
};

unisubs.subtitle.EditableCaptionSet.prototype.hasDescriptionChanged = function() {
    return this.originalDescription !== this.description;
};
<|endoftext|>


<commit_before>import { createStore, applyMiddleware, combineReducers } from 'redux';
import router from 'app/reducers/router';
import user from 'app/reducers/user';
import entities from 'app/reducers/entities';
import pagination from 'app/reducers/pagination';
import conversationStore from 'app/reducers/conversationStore';
import thunkMiddleware from 'redux-thunk';
import loggerMiddleware from 'redux-logger'; // TODO: don't import on prod
import apiMiddleware from 'app/api';

const reducer = combineReducers({
  router,
  user,
  entities,
  pagination,
});

const createStoreWithMiddleware = applyMiddleware(...[
  apiMiddleware,
  thunkMiddleware,
  DEBUG && loggerMiddleware,
].filter(Boolean))(createStore);

export function configureStore(initialState) {
  return createStoreWithMiddleware(reducer, initialState);
}

export default configureStore;
<commit_msg>client: don't bundle redux-logger to production<commit_after>import { createStore, applyMiddleware, combineReducers } from 'redux';
import router from 'app/reducers/router';
import user from 'app/reducers/user';
import entities from 'app/reducers/entities';
import pagination from 'app/reducers/pagination';
import conversationStore from 'app/reducers/conversationStore';
import thunkMiddleware from 'redux-thunk';
import apiMiddleware from 'app/api';

const reducer = combineReducers({
  router,
  user,
  entities,
  pagination,
});

const createStoreWithMiddleware = applyMiddleware(...[
  apiMiddleware,
  thunkMiddleware,
  DEBUG && require('redux-logger'),
].filter(Boolean))(createStore);

export function configureStore(initialState) {
  return createStoreWithMiddleware(reducer, initialState);
}

export default configureStore;
<|endoftext|>


<commit_before>'use strict';

var LoginPage = require('../pages/login_page.js');
var LogoutPage = require('../pages/logout_page.js');
var CollectionPage = require('../pages/collections_page.js');
var OrgPage = require('../pages/organization_page');
var data = require('../fixtures/test_data.json');
var configuration = require('../utilities/configuration.js');
var EC = require('protractor').ExpectedConditions;

describe('OCL Collections Page', function () {

    var loginPage;
    var logoutPage;
    var collectionPage;
    var orgPage;
    var id = '';

    beforeEach(function () {
        loginPage = new LoginPage();
        logoutPage = new LogoutPage();
        collectionPage = new CollectionPage();
        orgPage = new OrgPage();
        return browser.ignoreSynchronization = true;
    });

    it('should login', function () {
        loginPage.visit();
        loginPage.login();

        expect((loginPage.loginStatus).getText()).toEqual('Successfully signed in as ' + configuration.get("username") + '.');
    });

    it('should create collection', function () {
        id = orgPage.getRandomString(2);
        collectionPage.createNewUserCollection(data.short_code + id,
            data.col_name,
            data.full_name,
            data.supported_locale
        );

        expect((collectionPage.status).getText()).toEqual('Collection created');
    });

    it('should create collection with openmrs validation schema', function () {
        collectionPage.userHomeLink.click();
        id = orgPage.getRandomString(2);
        collectionPage.createNewUserCollection(data.short_code + id,
            data.col_name,
            data.full_name,
            data.supported_locale,
            data.custom_validation_schema
        );

        expect((collectionPage.status).getText()).toEqual('Collection created');
    });

    it('should create collection version', function () {

        collectionPage.createNewCollectionVersion('V1', 'Version 1');

        expect((orgPage.status).getText()).toEqual('Collection version created!');
        browser.refresh();
    });

    it('should release a user collection version', function () {
        orgPage.releaseVersion();

        browser.wait(EC.textToBePresentInElement(orgPage.notification, 'Successfully Released'), 500);
        expect(orgPage.notification.getText()).toEqual('Successfully Released.');

        browser.wait(EC.textToBePresentInElement(orgPage.releaseLabel.get(1), 'Released'), 500);
        expect(orgPage.releaseLabel.get(1).getText()).toEqual('Released');

        orgPage.notification.click();
    });

    it('should retire user collection version', function () {
        orgPage.retireVersion();

        browser.wait(EC.textToBePresentInElement(orgPage.notification, 'Successfully Retired.'), 500);
        expect(orgPage.notification.getText()).toEqual('Successfully Retired.');

        browser.wait(EC.textToBePresentInElement(orgPage.retireLabel.get(1), 'Retired'), 500);
        expect(orgPage.retireLabel.get(1).getText()).toEqual('Retired');

        orgPage.notification.click();
    });

    it('should un-retire user collection version', function () {
        orgPage.retireVersion();

        browser.wait(EC.textToBePresentInElement(orgPage.notification, 'Successfully Un-Retired.'), 1000);
        browser.wait(EC.textToBePresentInElement(orgPage.releaseLabel.get(1), 'Released'), 1000);

        orgPage.notification.click();
    });

    it('should un-release a user collection version', function () {
        orgPage.releaseVersion();

        browser.wait(EC.textToBePresentInElement(orgPage.notification, 'Successfully Un-Released.'), 500);

        orgPage.notification.click();
    });

    it('should delete a user collection version', function () {
        orgPage.deleteCollectionVersion();

        browser.wait(EC.textToBePresentInElement(orgPage.notification, 'Successfully removed collection version.'), 1000);

        orgPage.notification.click();
    });

    it('should edit collection', function () {
        collectionPage.editCollection(data.col_desc, data.ext_id);

        expect((collectionPage.status).getText()).toEqual('Collection updated');
        expect((collectionPage.updatedDescValue).getText()).toContain('collection description');
        expect((collectionPage.updatedExtIdValue).getText()).toContain('1.1');
    });

    it('should delete collection', function () {
        collectionPage.deleteCollection();

        expect((collectionPage.status).getText()).toEqual('Collection Deleted');
    });

    it('should logout', function () {
        logoutPage.logout();

        expect((loginPage.loginStatus).getText()).toEqual('You have signed out.');
    });
});

<commit_msg>#300 add test to creat/edite collection with openmrs validation schema<commit_after>'use strict';

var LoginPage = require('../pages/login_page.js');
var LogoutPage = require('../pages/logout_page.js');
var CollectionPage = require('../pages/collections_page.js');
var OrgPage = require('../pages/organization_page');
var data = require('../fixtures/test_data.json');
var configuration = require('../utilities/configuration.js');
var EC = require('protractor').ExpectedConditions;

describe('OCL Collections Page', function () {

    var loginPage = new LoginPage();
    var logoutPage = new LogoutPage();
    var collectionPage = new CollectionPage();
    var orgPage = new OrgPage();
    var id = '';
    browser.ignoreSynchronization = true;

    beforeAll(function () {
        loginPage.visit();
        loginPage.login();
    });

    it('should create collection', function () {
        id = orgPage.getRandomString(2);
        collectionPage.createNewUserCollection(data.short_code + id,
            data.col_name,
            data.full_name,
            data.supported_locale
        );

        expect((collectionPage.status).getText()).toEqual('Collection created');
    });

    it('should create collection with openmrs validation schema', function () {
        collectionPage.userHomeLink.click();
        id = orgPage.getRandomString(2);
        collectionPage.createNewUserCollection(data.short_code + id,
            data.col_name,
            data.full_name,
            data.supported_locale,
            data.custom_validation_schema
        );
        expect((collectionPage.status).getText()).toEqual('Collection created');
    });

    it('should update collection to openmrs validation schema', function () {
        collectionPage.userHomeLink.click();
        id = orgPage.getRandomString(2);
        collectionPage.createNewUserCollection(data.short_code + id,
            data.col_name,
            data.full_name,
            data.supported_locale
        );

        collectionPage.clickEditIcon();
        collectionPage.setCustomValidationSchema(data.customValidationSchema);
        collectionPage.clickUpdateCollection();
        expect((collectionPage.status).getText()).toEqual('Collection updated');
    });

    it('should create collection version', function () {

        collectionPage.createNewCollectionVersion('V1', 'Version 1');

        expect((orgPage.status).getText()).toEqual('Collection version created!');
        browser.refresh();
    });

    it('should release a user collection version', function () {
        orgPage.releaseVersion();

        browser.wait(EC.textToBePresentInElement(orgPage.notification, 'Successfully Released'), 500);
        expect(orgPage.notification.getText()).toEqual('Successfully Released.');

        browser.wait(EC.textToBePresentInElement(orgPage.releaseLabel.get(1), 'Released'), 500);
        expect(orgPage.releaseLabel.get(1).getText()).toEqual('Released');

        orgPage.notification.click();
    });

    it('should retire user collection version', function () {
        orgPage.retireVersion();

        browser.wait(EC.textToBePresentInElement(orgPage.notification, 'Successfully Retired.'), 500);
        expect(orgPage.notification.getText()).toEqual('Successfully Retired.');

        browser.wait(EC.textToBePresentInElement(orgPage.retireLabel.get(1), 'Retired'), 500);
        expect(orgPage.retireLabel.get(1).getText()).toEqual('Retired');

        orgPage.notification.click();
    });

    it('should un-retire user collection version', function () {
        orgPage.retireVersion();

        browser.wait(EC.textToBePresentInElement(orgPage.notification, 'Successfully Un-Retired.'), 1000);
        browser.wait(EC.textToBePresentInElement(orgPage.releaseLabel.get(1), 'Released'), 1000);

        orgPage.notification.click();
    });

    it('should un-release a user collection version', function () {
        orgPage.releaseVersion();

        browser.wait(EC.textToBePresentInElement(orgPage.notification, 'Successfully Un-Released.'), 500);

        orgPage.notification.click();
    });

    it('should delete a user collection version', function () {
        orgPage.deleteCollectionVersion();

        browser.wait(EC.textToBePresentInElement(orgPage.notification, 'Successfully removed collection version.'), 1000);

        orgPage.notification.click();
    });

    it('should edit collection', function () {
        collectionPage.editCollection(data.col_desc, data.ext_id);

        expect((collectionPage.status).getText()).toEqual('Collection updated');
        expect((collectionPage.updatedDescValue).getText()).toContain('collection description');
        expect((collectionPage.updatedExtIdValue).getText()).toContain('1.1');
    });

    it('should delete collection', function () {
        collectionPage.deleteCollection();

        expect((collectionPage.status).getText()).toEqual('Collection Deleted');
    });

    it('should logout', function () {
        logoutPage.logout();

        expect((loginPage.loginStatus).getText()).toEqual('You have signed out.');
    });
});

<|endoftext|>


<commit_before>
define([
    'flight/lib/component',
    './form/form',
    'tpl!./workspaces',
    'tpl!./list',
    'tpl!./item',
    'util/withDataRequest'
], function(defineComponent,
    WorkspaceForm,
    workspacesTemplate,
    listTemplate,
    itemTemplate,
    withDataRequest) {
    'use strict';

    return defineComponent(Workspaces, withDataRequest);

    function Workspaces() {

        this.defaultAttrs({
            listSelector: 'ul.nav-list',
            workspaceListItemSelector: 'ul.nav-list li',
            addNewInputSelector: 'input.new',
            addNewSelector: 'button.new',
            disclosureSelector: 'button.disclosure',
            formSelector: '.workspace-form'
        });

        this.onWorkspaceItemClick = function(event) {
            var $target = $(event.target);

            if ($target.is('input') || $target.is('button') || $target.is('.new-workspace')) return;
            if ($target.closest('.workspace-form').length) return;

            var li = $(event.target).closest('li'),
                workspaceId = li.data('workspaceId');

            if (workspaceId) {
                this.switchToWorkspace(workspaceId.toString());
            }
        };

        this.switchToWorkspace = function(workspaceId) {
            if (workspaceId !== lumifyData.currentWorkspaceId) {
                this.trigger('switchWorkspace', { workspaceId: workspaceId });
            }
        };

        this.onAddNew = function(event) {
            var self = this,
                $input = $(event.target).parents('li').find('input'),
                title = $.trim($input.val());

            if (!title) return;

            this.dataRequest('workspace', 'create', { title: title })
                .then(function(workspace) {
                    $input.val('')
                    self.trigger('switchWorkspace', { workspaceId: workspace.workspaceId });
                })
                .catch(function() {
                    $input.focus();
                })
        };

        this.onInputKeyUp = function(event) {
            switch (event.which) {
                case $.ui.keyCode.ENTER:
                    this.onAddNew(event);
            }
        };

        this.onDisclosure = function(event) {
            var self = this,
                $target = $(event.target),
                data = $target.closest('li').data();

            event.preventDefault();

            this.switchToWorkspace(data.workspaceId);

            var container = this.select('formSelector'),
                form = container.resizable({
                        handles: 'e',
                        minWidth: 120,
                        maxWidth: 250,
                        resize: function() {
                            self.trigger(document, 'paneResized');
                        }
                    }).show().find('.content'),
                instance = form.lookupComponent(WorkspaceForm);

            if (instance && instance.attr.data.workspaceId === data.workspaceId) {
                container.hide();
                instance.teardown();
                return self.trigger(document, 'paneResized');
            }

            WorkspaceForm.teardownAll();
            WorkspaceForm.attachTo(form, {
                data: data
            });

            self.trigger(container, 'paneResized');
        };

        this.collapseEditForm = function() {
            if (this.select('formSelector').is(':visible')) {
                WorkspaceForm.teardownAll();
                this.select('formSelector').hide();
                this.trigger(document, 'paneResized');
            }
        };

        this.onWorkspaceDeleting = function(event, data) {
            this.deletingWorkspace = $.extend({}, data);
        };

        this.onWorkspaceDeleted = function(event, data) {
            this.collapseEditForm();
            this.findWorkspaceRow(data.workspaceId).remove();
        };

        this.onSwitchWorkspace = function(event, data) {
            var li = this.findWorkspaceRow(data.workspaceId);
            if (li.length) {
                this.switchActive(data.workspaceId);
                li.find('.badge').addClass('loading').show().next().hide();
            }
            this.collapseEditForm();
        };

        this.onWorkspaceLoad = function(event, data) {
            if (this.$node.closest('.visible').length) {
                this.updateListItemWithData(data);
                this.switchActive(data.workspaceId);
            }
        };

        this.findWorkspaceRow = function(workspaceId) {
            return this.select('workspaceListItemSelector').filter(function() {
                return $(this).data('workspaceId') == workspaceId;
            });
        };

        this.onWorkspaceSaving = function(event, data) {
            var li = this.findWorkspaceRow(data.workspaceId);
            li.find('.badge').addClass('loading').show().next().hide();
        };

        this.onWorkspaceSaved = function(event, data) {
            var li = this.findWorkspaceRow(data.workspaceId);
            li.find('.badge').removeClass('loading').hide().next().show();
        };

        this.updateListItemWithData = function(data, timestamp) {
            var self = this,
                currentUser = lumifyData.currentUser,
                li = this.findWorkspaceRow(data.workspaceId);

            if (data.createdBy === currentUser.id ||
                _.contains(_.pluck(data.users, 'userId'), currentUser.id)
            ) {
                li.find('.badge').removeClass('loading').hide().next().show();
                this.workspaceDataForItemRow(data)
                    .done(function(data) {
                        var content = $(itemTemplate({ workspace: data, selected: self.workspaceId }));
                        if (li.length === 0) {
                            self.$node.find('li.nav-header').eq(data.sharedToUser ? 1 : 0).after(content);
                        } else {
                            li.replaceWith(content);
                        }

                        // Sort section because title might be renamed
                        var lis = self.getWorkspaceListItemsInSection(data.sharedToUser),
                            dataGetter = function(li) {
                                return $(this).data();
                            },
                            insertIndex = _.sortedIndex(lis.map(dataGetter).toArray(), data, function(w) {
                                return [w.title.toLowerCase(), w.createdBy, w.workspaceId].join('\u0000')
                            }),
                            currentIndex = lis.index(content);

                        if (currentIndex < insertIndex) {
                            content.insertAfter(lis.eq(insertIndex));
                        } else if (currentIndex > insertIndex) {
                            content.insertBefore(lis.eq(insertIndex));
                        }
                    });
            } else {
                li.remove();
            }
        };

        this.getWorkspaceListItemsInSection = function(shared) {
            if (shared) {
                return this.select('listSelector')
                    .children('.nav-header').eq(1)
                    .nextUntil();
            } else {
                return this.select('listSelector')
                    .children('.nav-header').eq(0)
                    .nextUntil('.new-workspace');
            }
        };

        this.onWorkspaceUpdated = function(event, data) {
            this.updateListItemWithData(data.workspace);
        };

        this.onWorkspaceNotAvailable = function(event, data) {
            this.loadWorkspaceList();
            this.trigger('displayInformation', { message: i18n('workspaces.not_found') });
        };

        this.switchActive = function(workspaceId) {
            var self = this;
            this.workspaceId = workspaceId;

            var found = false;
            this.select('workspaceListItemSelector')
                .removeClass('active')
                .each(function() {
                    if ($(this).data('workspaceId') == workspaceId) {
                        found = true;
                        $(this).addClass('active');
                        return false;
                    }
                });

            if (!found) {
                this.loadWorkspaceList();
            }
        };

        this.loadWorkspaceList = function(switchToFirst) {
            var self = this;

            // TODO: convert to d3
            return this.dataRequest('workspace', 'all')
                .then(function(workspacesResponse) {
                    var workspaces = workspacesResponse || [],
                        users = _.chain(workspaces)
                            .map(function(workspace) {
                                return _.pluck(workspace.users, 'userId');
                            })
                            .flatten()
                            .uniq()
                            .value(),
                        updateHtml = function() {
                            $.when.apply($, _.chain(workspaces)
                                .reject(function(workspace) {
                                    return _.isUndefined(workspace.createdBy);
                                })
                                .map(self.workspaceDataForItemRow.bind(self))
                                .value()
                            ).done(function() {
                                var rows = _.chain(arguments)
                                        .sortBy('workspaceId')
                                        .sortBy('createdBy')
                                        .sortBy(function(w) {
                                            return w.title.toLowerCase()
                                        })
                                        .groupBy(function(w) {
                                            return w.sharedToUser ? 'shared' : 'mine';
                                        })
                                        .value();

                                self.select('listSelector').html(
                                    listTemplate({
                                        results: rows,
                                        selected: self.workspaceId
                                    })
                                );
                            });
                            self.trigger(document, 'paneResized');
                            if (switchToFirst) {
                                self.switchToWorkspace(workspaces[0].workspaceId);
                            }
                        };

                    if (users.length) {
                        return self.dataRequest('user', 'search', { userIds: users })
                            .done(function(result) {
                                self.usersById = $.extend(self.usersById, _.indexBy(users, 'id'));
                                updateHtml();
                            })
                    } else {
                        updateHtml();
                    }
                });
        };

        this.workspaceDataForItemRow = function(w) {
            var deferred = $.Deferred(),
                row = $.extend({}, w),
                usersNotCurrent = row.users.filter(function(u) {
                    return u.userId != lumifyData.currentUser.id;
                }),
                people = usersNotCurrent.length;

            if (row.sharedToUser) {
                this.dataRequest('user', 'search', { userIds: row.createdBy })
                    .done(function(createdBy) {
                        var name = createdBy && createdBy.displayName ||
                                i18n('workspaces.shared_with_me.subtitle.unknown_user');

                        deferred.resolve(i18n('workspaces.shared_with_me.subtitle.prefix', name));
                    });
            } else {
                deferred.resolve(i18n('workspaces.sharing.subtitle.prefix'));
            }

            return deferred.promise().then(function(text) {
                if (people === 1 && row.sharedToUser) {
                    row.sharingSubtitle = text + ' ' + i18n('workspaces.sharing.subtitle.suffix.only_you');
                } else if (people === 1) {
                    row.sharingSubtitle = text + ' ' + i18n('workspaces.sharing.subtitle.suffix.one_other');
                } else if (people) {
                    row.sharingSubtitle = text + ' ' + i18n('workspaces.sharing.subtitle.suffix.others', people);
                } else {
                    row.sharingSubtitle = null;
                }
                return row;
            });
        };

        this.onToggleMenu = function(event, data) {
            var self = this;

            if (data.name === 'workspaces') {
                if (this.$node.closest('.visible').length) {
                    this.loadWorkspaceList()
                        .then(function() {
                            self.switchActive(lumifyData.currentWorkspaceId);
                        })
                } else {
                    this.collapseEditForm();
                }
            }
        };

        this.after('initialize', function() {

            this.on(document, 'workspaceLoaded', this.onWorkspaceLoad);
            this.on(document, 'workspaceSaving', this.onWorkspaceSaving);
            this.on(document, 'workspaceSaved', this.onWorkspaceSaved);
            this.on(document, 'workspaceDeleted', this.onWorkspaceDeleted);
            this.on(document, 'workspaceUpdated', this.onWorkspaceUpdated);
            this.on(document, 'workspaceNotAvailable', this.onWorkspaceNotAvailable);

            this.on(document, 'menubarToggleDisplay', this.onToggleMenu);
            this.on(document, 'switchWorkspace', this.onSwitchWorkspace);

            this.on('workspaceDeleting', this.onWorkspaceDeleting);

            this.on('click', {
                workspaceListItemSelector: this.onWorkspaceItemClick,
                addNewSelector: this.onAddNew,
                disclosureSelector: this.onDisclosure
            });

            this.on('keyup', {
                addNewInputSelector: this.onInputKeyUp
            });

            this.$node.html(workspacesTemplate({}));
        });
    }

});
<commit_msg>Fix workspace from requesting all workspaces twice. #542<commit_after>
define([
    'flight/lib/component',
    './form/form',
    'tpl!./workspaces',
    'tpl!./list',
    'tpl!./item',
    'util/withDataRequest'
], function(defineComponent,
    WorkspaceForm,
    workspacesTemplate,
    listTemplate,
    itemTemplate,
    withDataRequest) {
    'use strict';

    return defineComponent(Workspaces, withDataRequest);

    function Workspaces() {

        this.defaultAttrs({
            listSelector: 'ul.nav-list',
            workspaceListItemSelector: 'ul.nav-list li',
            addNewInputSelector: 'input.new',
            addNewSelector: 'button.new',
            disclosureSelector: 'button.disclosure',
            formSelector: '.workspace-form'
        });

        this.onWorkspaceItemClick = function(event) {
            var $target = $(event.target);

            if ($target.is('input') || $target.is('button') || $target.is('.new-workspace')) return;
            if ($target.closest('.workspace-form').length) return;

            var li = $(event.target).closest('li'),
                workspaceId = li.data('workspaceId');

            if (workspaceId) {
                this.switchToWorkspace(workspaceId.toString());
            }
        };

        this.switchToWorkspace = function(workspaceId) {
            if (workspaceId !== lumifyData.currentWorkspaceId) {
                this.trigger('switchWorkspace', { workspaceId: workspaceId });
            }
        };

        this.onAddNew = function(event) {
            var self = this,
                $input = $(event.target).parents('li').find('input'),
                title = $.trim($input.val());

            if (!title) return;

            this.dataRequest('workspace', 'create', { title: title })
                .then(function(workspace) {
                    $input.val('')
                    self.trigger('switchWorkspace', { workspaceId: workspace.workspaceId });
                })
                .catch(function() {
                    $input.focus();
                })
        };

        this.onInputKeyUp = function(event) {
            switch (event.which) {
                case $.ui.keyCode.ENTER:
                    this.onAddNew(event);
            }
        };

        this.onDisclosure = function(event) {
            var self = this,
                $target = $(event.target),
                data = $target.closest('li').data();

            event.preventDefault();

            this.switchToWorkspace(data.workspaceId);

            var container = this.select('formSelector'),
                form = container.resizable({
                        handles: 'e',
                        minWidth: 120,
                        maxWidth: 250,
                        resize: function() {
                            self.trigger(document, 'paneResized');
                        }
                    }).show().find('.content'),
                instance = form.lookupComponent(WorkspaceForm);

            if (instance && instance.attr.data.workspaceId === data.workspaceId) {
                container.hide();
                instance.teardown();
                return self.trigger(document, 'paneResized');
            }

            WorkspaceForm.teardownAll();
            WorkspaceForm.attachTo(form, {
                data: data
            });

            self.trigger(container, 'paneResized');
        };

        this.collapseEditForm = function() {
            if (this.select('formSelector').is(':visible')) {
                WorkspaceForm.teardownAll();
                this.select('formSelector').hide();
                this.trigger(document, 'paneResized');
            }
        };

        this.onWorkspaceDeleting = function(event, data) {
            this.deletingWorkspace = $.extend({}, data);
        };

        this.onWorkspaceDeleted = function(event, data) {
            this.collapseEditForm();
            this.findWorkspaceRow(data.workspaceId).remove();
        };

        this.onSwitchWorkspace = function(event, data) {
            var li = this.findWorkspaceRow(data.workspaceId);
            if (li.length) {
                this.switchActive(data.workspaceId);
                li.find('.badge').addClass('loading').show().next().hide();
            }
            this.collapseEditForm();
        };

        this.onWorkspaceLoad = function(event, data) {
            if (this.$node.closest('.visible').length) {
                this.updateListItemWithData(data);
                this.switchActive(data.workspaceId);
            }
        };

        this.findWorkspaceRow = function(workspaceId) {
            return this.select('workspaceListItemSelector').filter(function() {
                return $(this).data('workspaceId') == workspaceId;
            });
        };

        this.onWorkspaceSaving = function(event, data) {
            var li = this.findWorkspaceRow(data.workspaceId);
            li.find('.badge').addClass('loading').show().next().hide();
        };

        this.onWorkspaceSaved = function(event, data) {
            var li = this.findWorkspaceRow(data.workspaceId);
            li.find('.badge').removeClass('loading').hide().next().show();
        };

        this.updateListItemWithData = function(data, timestamp) {
            var self = this,
                currentUser = lumifyData.currentUser,
                li = this.findWorkspaceRow(data.workspaceId);

            if (data.createdBy === currentUser.id ||
                _.contains(_.pluck(data.users, 'userId'), currentUser.id)
            ) {
                li.find('.badge').removeClass('loading').hide().next().show();
                this.workspaceDataForItemRow(data)
                    .done(function(data) {
                        var content = $(itemTemplate({ workspace: data, selected: self.workspaceId }));
                        if (li.length === 0) {
                            self.$node.find('li.nav-header').eq(data.sharedToUser ? 1 : 0).after(content);
                        } else {
                            li.replaceWith(content);
                        }

                        // Sort section because title might be renamed
                        var lis = self.getWorkspaceListItemsInSection(data.sharedToUser),
                            dataGetter = function(li) {
                                return $(this).data();
                            },
                            insertIndex = _.sortedIndex(lis.map(dataGetter).toArray(), data, function(w) {
                                return [w.title.toLowerCase(), w.createdBy, w.workspaceId].join('\u0000')
                            }),
                            currentIndex = lis.index(content);

                        if (currentIndex < insertIndex) {
                            content.insertAfter(lis.eq(insertIndex));
                        } else if (currentIndex > insertIndex) {
                            content.insertBefore(lis.eq(insertIndex));
                        }
                    });
            } else {
                li.remove();
            }
        };

        this.getWorkspaceListItemsInSection = function(shared) {
            if (shared) {
                return this.select('listSelector')
                    .children('.nav-header').eq(1)
                    .nextUntil();
            } else {
                return this.select('listSelector')
                    .children('.nav-header').eq(0)
                    .nextUntil('.new-workspace');
            }
        };

        this.onWorkspaceUpdated = function(event, data) {
            this.updateListItemWithData(data.workspace);
        };

        this.onWorkspaceNotAvailable = function(event, data) {
            this.loadWorkspaceList();
            this.trigger('displayInformation', { message: i18n('workspaces.not_found') });
        };

        this.switchActive = function(workspaceId) {
            var self = this;
            this.workspaceId = workspaceId;

            var found = false;
            this.select('workspaceListItemSelector')
                .removeClass('active')
                .each(function() {
                    if ($(this).data('workspaceId') == workspaceId) {
                        found = true;
                        $(this).addClass('active');
                        return false;
                    }
                });

            if (!found) {
                this.loadWorkspaceList();
            }
        };

        this.loadWorkspaceList = function(switchToFirst) {
            var self = this;

            // TODO: convert to d3
            return this.dataRequest('workspace', 'all')
                .then(function(workspacesResponse) {
                    var workspaces = workspacesResponse || [],
                        userIds = _.chain(workspaces)
                            .map(function(workspace) {
                                return _.pluck(workspace.users, 'userId');
                            })
                            .flatten()
                            .uniq()
                            .value(),
                        updateHtml = function() {
                            return Promise.all(
                                _.chain(workspaces)
                                .reject(function(workspace) {
                                    return _.isUndefined(workspace.createdBy);
                                })
                                .map(self.workspaceDataForItemRow.bind(self))
                                .value()
                            ).then(function(workspaces) {
                                var rows = _.chain(workspaces)
                                        .sortBy('workspaceId')
                                        .sortBy('createdBy')
                                        .sortBy(function(w) {
                                            return w.title.toLowerCase()
                                        })
                                        .groupBy(function(w) {
                                            return w.sharedToUser ? 'shared' : 'mine';
                                        })
                                        .value();

                                self.select('listSelector').html(
                                    listTemplate({
                                        results: rows,
                                        selected: self.workspaceId
                                    })
                                );

                                self.trigger(document, 'paneResized');
                                if (switchToFirst) {
                                    self.switchToWorkspace(workspaces[0].workspaceId);
                                }
                            });
                        };

                    if (userIds.length) {
                        return self.dataRequest('user', 'getUserNames', userIds)
                            .then(function(users) {
                                if (users) {
                                    self.usersById = $.extend(self.usersById || {}, _.object(userIds, users));
                                }
                                return updateHtml();
                            })
                    } else {
                        return updateHtml();
                    }
                });
        };

        this.workspaceDataForItemRow = function(w) {
            var deferred = $.Deferred(),
                row = $.extend({}, w),
                usersNotCurrent = row.users.filter(function(u) {
                    return u.userId != lumifyData.currentUser.id;
                }),
                people = usersNotCurrent.length,
                sharedByUser = row.sharedToUser && this.usersById[row.createdBy];

            if (row.sharedToUser) {
                if (sharedByUser) {
                    deferred.resolve(
                        i18n('workspaces.shared_with_me.subtitle.prefix', sharedByUser)
                    );
                } else {
                    this.dataRequest('user', 'getUserNames', [row.createdBy])
                        .then(function(users) {
                            var subtitle;
                            if (users && users[0]) {
                                subtitle = i18n('workspaces.shared_with_me.subtitle.prefix', users[0])
                            } else {
                                subtitle = i18n('workspaces.shared_with_me.subtitle.unknown_user');
                            }
                            deferred.resolve(subtitle);
                        })
                        .catch(function() {
                            deferred.resolve(i18n('workspaces.shared_with_me.subtitle.unknown_user'));
                        })
                }
            } else {
                deferred.resolve(i18n('workspaces.sharing.subtitle.prefix'));
            }

            return deferred.promise().then(function(text) {
                if (people === 1 && row.sharedToUser) {
                    row.sharingSubtitle = text + ' ' + i18n('workspaces.sharing.subtitle.suffix.only_you');
                } else if (people === 1) {
                    row.sharingSubtitle = text + ' ' + i18n('workspaces.sharing.subtitle.suffix.one_other');
                } else if (people) {
                    row.sharingSubtitle = text + ' ' + i18n('workspaces.sharing.subtitle.suffix.others', people);
                } else {
                    row.sharingSubtitle = null;
                }
                return row;
            });
        };

        this.onToggleMenu = function(event, data) {
            var self = this;

            if (data.name === 'workspaces') {
                if (this.$node.closest('.visible').length) {
                    this.loadWorkspaceList()
                        .then(function() {
                            self.switchActive(lumifyData.currentWorkspaceId);
                        })
                } else {
                    this.collapseEditForm();
                }
            }
        };

        this.after('initialize', function() {
            this.usersById = {};

            this.on(document, 'workspaceLoaded', this.onWorkspaceLoad);
            this.on(document, 'workspaceSaving', this.onWorkspaceSaving);
            this.on(document, 'workspaceSaved', this.onWorkspaceSaved);
            this.on(document, 'workspaceDeleted', this.onWorkspaceDeleted);
            this.on(document, 'workspaceUpdated', this.onWorkspaceUpdated);
            this.on(document, 'workspaceNotAvailable', this.onWorkspaceNotAvailable);

            this.on(document, 'menubarToggleDisplay', this.onToggleMenu);
            this.on(document, 'switchWorkspace', this.onSwitchWorkspace);

            this.on('workspaceDeleting', this.onWorkspaceDeleting);

            this.on('click', {
                workspaceListItemSelector: this.onWorkspaceItemClick,
                addNewSelector: this.onAddNew,
                disclosureSelector: this.onDisclosure
            });

            this.on('keyup', {
                addNewInputSelector: this.onInputKeyUp
            });

            this.$node.html(workspacesTemplate({}));
        });
    }

});
<|endoftext|>


<commit_before>require('./scrabble');

describe('Scrabble', function() {
  it("scores an empty word as zero",function() {
    var score = Scrabble.score("");
    expect(score).toEqual(0);
  });

  it("scores a null as zero",function() {
    var score = Scrabble.score(null);
    expect(score).toEqual(0);
  });

  it("scores a very short word",function() {
    var score = Scrabble.score("a");
    expect(score).toEqual(1);
  });

  it("scores the word by the number of letters",function() {
    var score = Scrabble.score("street");
    expect(score).toEqual(6);
  });

  it("scores more complicated words with more",function() {
    var score = Scrabble.score("quirky");
    expect(score).toEqual(22);
  });

  it("scores case insensitive words",function() {
    var score = Scrabble.score("MULTIBILLIONAIRE");
    expect(score).toEqual(20);
  });
});<commit_msg>skips all but first test<commit_after>require('./scrabble');

describe('Scrabble', function() {
  it("scores an empty word as zero",function() {
    var score = Scrabble.score("");
    expect(score).toEqual(0);
  });

  xit("scores a null as zero",function() {
    var score = Scrabble.score(null);
    expect(score).toEqual(0);
  });

  xit("scores a very short word",function() {
    var score = Scrabble.score("a");
    expect(score).toEqual(1);
  });

  xit("scores the word by the number of letters",function() {
    var score = Scrabble.score("street");
    expect(score).toEqual(6);
  });

  xit("scores more complicated words with more",function() {
    var score = Scrabble.score("quirky");
    expect(score).toEqual(22);
  });

  xit("scores case insensitive words",function() {
    var score = Scrabble.score("MULTIBILLIONAIRE");
    expect(score).toEqual(20);
  });
});<|endoftext|>