<commit_before>/*!
 * VerbalExpressions JavaScript Library v0.1.2
 * https://github.com/VerbalExpressions/JSVerbalExpressions
 *
 *
 * Released under the MIT license
 * http://opensource.org/licenses/MIT
 *
 * Date: 2013-07-19
 *
 */

// Define the collection class.
(function verbalExpressionIIFE() {
    var root = this;

    // I am the constructor function.
    function VerbalExpression() {
        var verbalExpression = new RegExp();

        // Add all the class methods
        VerbalExpression.injectClassMethods(verbalExpression);

        // Return the new object.
        return verbalExpression;
    }

    // Define the static methods.
    VerbalExpression.injectClassMethods = function injectClassMethods(verbalExpression) {
        // Loop over all the prototype methods
        for (var method in VerbalExpression.prototype) {
            // Make sure this is a local method.
            if (VerbalExpression.prototype.hasOwnProperty(method)) {
                // Add the method
                verbalExpression[method] = VerbalExpression.prototype[method];
            }
        }

        return verbalExpression;
    };

    // Define the class methods.
    VerbalExpression.prototype = {

        // Variables to hold the whole
        // expression construction in order
        _prefixes: '',
        _source: '',
        _suffixes: '',
        _modifiers: 'gm', // default to global multiline matching

        // Sanitation function for adding
        // anything safely to the expression
        sanitize: function sanitize(value) {
            if (value.source) {
                return value.source;
            }

            if (typeof value === 'number') {
                return value;
            }

            // Regular expression meta characters, URL: https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/regexp
            var reRegExpEscape = /([\].|*?+(){}^$\\:=[])/g;

            // Escape RegExp special characters only
            // $& => Last match, URL: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/lastMatch
            return value.replace(reRegExpEscape, '\\$&');
        },

        // Function to add stuff to the
        // expression. Also compiles the
        // new expression so it's ready to
        // be used.
        add: function add(value) {
            this._source += value || '';
            this.compile(this._prefixes + this._source + this._suffixes, this._modifiers);

            return this;
        },

        // Start and end of line functions
        startOfLine: function startOfLine(enable) {
            enable = (enable !== false);
            this._prefixes = enable ? '^' : '';
            this.add();

            return this;
        },

        endOfLine: function endOfLine(enable) {
            enable = (enable !== false);
            this._suffixes = enable ? '$' : '';
            this.add();

            return this;
        },

        // We try to keep the syntax as
        // user-friendly as possible.
        // So we can use the "normal"
        // behaviour to split the "sentences"
        // naturally.
        then: function then(value) {
            value = this.sanitize(value);
            this.add('(?:' + value + ')');

            return this;
        },

        // And because we can't start with
        // "then" function, we create an alias
        // to be used as the first function
        // of the chain.
        find: function find(value) {
            return this.then(value);
        },

        // Maybe is used to add values with ?
        maybe: function maybe(value) {
            value = this.sanitize(value);
            this.add('(?:' + value + ')?');

            return this;
        },

        // Any character any number of times
        anything: function anything() {
            this.add('(?:.*)');
            return this;
        },

        // Anything but these characters
        anythingBut: function anythingBut(value) {
            value = this.sanitize(value);
            this.add('(?:[^' + value + ']*)');

            return this;
        },

        // Any character at least one time
        something: function something() {
            this.add('(?:.+)');
            return this;
        },

        // Any character at least one time except for these characters
        somethingBut: function somethingBut(value) {
            value = this.sanitize(value);
            this.add('(?:[^' + value + ']+)');

            return this;
        },

        // Shorthand function for the
        // String.replace function to
        // give more logical flow if, for
        // example, we're doing multiple
        // replacements on one regexp.
        replace: function replace(source, value) {
            source = source.toString();
            return source.replace(this, value);
        },

        /// Add regular expression special ///
        /// characters                     ///

        // Line break
        lineBreak: function lineBreak() {
            this.add('(?:\\r\\n|\\r|\\n)'); // Unix + Windows CRLF
            return this;
        },

        // And a shorthand for html-minded
        br: function br() {
            return this.lineBreak();
        },

        // Tab (duh?)
        tab: function tab() {
            this.add('\\t');
            return this;
        },

        // Any alphanumeric
        word: function word() {
            this.add('\\w+');
            return this;
        },

        // Any whitespace
        whitespace: function whitespace() {
            this.add('\\s');
            return this;
        },

        // Any given character
        anyOf: function anyOf(value) {
            value = this.sanitize(value);
            this.add('[' + value + ']');
            return this;
        },

        // Shorthand
        any: function any(value) {
            return this.anyOf(value);
        },

        // Usage: .range( from, to [, from, to ... ] )
        range: function range() {
            var value = '[';

            for (var _to = 1; _to < arguments.length; _to += 2) {
                var from = this.sanitize(arguments[_to - 1]);
                var to = this.sanitize(arguments[_to]);

                value += from + '-' + to;
            }

            value += ']';

            this.add(value);

            return this;
        },

        /// Modifiers      ///

        // Modifier abstraction
        addModifier: function addModifier(modifier) {
            if (this._modifiers.indexOf(modifier) === -1) {
                this._modifiers += modifier;
            }

            this.add();

            return this;
        },

        removeModifier: function removeModifier(modifier) {
            this._modifiers = this._modifiers.replace(modifier, '');
            this.add();

            return this;
        },

        // Case-insensitivity modifier
        withAnyCase: function withAnyCase(enable) {
            if (enable !== false) {
                this.addModifier('i');
            } else {
                this.removeModifier('i');
            }

            this.add();

            return this;
        },

        // Default behaviour is with "g" modifier,
        // so we can turn this another way around
        // than other modifiers
        stopAtFirst: function stopAtFirst(enable) {
            if (enable !== false) {
                this.removeModifier('g');
            } else {
                this.addModifier('g');
            }

            this.add();

            return this;
        },

        // Multiline, also reversed
        searchOneLine: function searchOneLine(enable) {
            if (enable !== false) {
                this.removeModifier('m');
            } else {
                this.addModifier('m');
            }

            this.add();

            return this;
        },

        // Repeats the previous item
        // exactly n times or
        // between n and m times.
        repeatPrevious: function repeatPrevious() {
            var value;
            if (arguments.length <= 1) {
                if (/\d+/.exec(arguments[0]) !== null) {
                    value = '{' + arguments[0] + '}';
                }
            } else {
                var values = [];
                for (var i = 0; i < arguments.length; i++) {
                    if (/\d+/.exec(arguments[i]) !== null) {
                        values.push(arguments[i]);
                    }
                }

                value = '{' + values.join(',') + '}';
            }

            this.add(value);

            return (this);
        },

        // Repeats the previous at least ones
        oneOrMore: function oneOrMore() {
            this.add('+');
            return (this);
        },

        /// Loops  ///

        multiple: function multiple(value) {
            // Use expression or string

            value = value.source ? value.source : this.sanitize(value);
            if (arguments.length === 1) {
                this.add('(?:' + value + ')*');
            }

            if (arguments.length > 1) {
                this.add('(?:' + value + ')');
                this.add('{' + arguments[1] + '}');
            }

            return this;
        },

        // Adds alternative expressions
        or: function or(value) {
            this._prefixes += '(?:';
            this._suffixes = ')' + this._suffixes;

            this.add(')|(?:');
            if (value) {
                this.then(value);
            }

            return this;
        },

        // Starts a capturing group
        beginCapture: function beginCapture() {
            // Add the end of the capture group to the suffixes for now so compilation continues to work
            this._suffixes += ')';
            this.add('(');

            return this;
        },

        // Ends a capturing group
        endCapture: function endCapture() {
            // Remove the last parentheses from the _suffixes and add to the regex itself
            this._suffixes = this._suffixes.substring(0, this._suffixes.length - 1);
            this.add(')');

            return this;
        },

        // Convert to RegExp object
        toRegExp: function toRegExp() {
            var arr = this.toString().match(/\/(.*)\/([a-z]+)?/);
            return new RegExp(arr[1], arr[2]);
        },
    };

    function createVerbalExpression() {
        return new VerbalExpression();
    }

    // Support both browser and node.js
    if (typeof module !== 'undefined' && module.exports) {
        module.exports = createVerbalExpression;
    } else if (typeof define === 'function' && define.amd) {
        define(function define() {
            return VerbalExpression;
        });
    } else {
        root.VerEx = createVerbalExpression;
    }
})();
<commit_msg>Use character class of actual flag values<commit_after>/*!
 * VerbalExpressions JavaScript Library v0.1.2
 * https://github.com/VerbalExpressions/JSVerbalExpressions
 *
 *
 * Released under the MIT license
 * http://opensource.org/licenses/MIT
 *
 * Date: 2013-07-19
 *
 */

// Define the collection class.
(function verbalExpressionIIFE() {
    var root = this;

    // I am the constructor function.
    function VerbalExpression() {
        var verbalExpression = new RegExp();

        // Add all the class methods
        VerbalExpression.injectClassMethods(verbalExpression);

        // Return the new object.
        return verbalExpression;
    }

    // Define the static methods.
    VerbalExpression.injectClassMethods = function injectClassMethods(verbalExpression) {
        // Loop over all the prototype methods
        for (var method in VerbalExpression.prototype) {
            // Make sure this is a local method.
            if (VerbalExpression.prototype.hasOwnProperty(method)) {
                // Add the method
                verbalExpression[method] = VerbalExpression.prototype[method];
            }
        }

        return verbalExpression;
    };

    // Define the class methods.
    VerbalExpression.prototype = {

        // Variables to hold the whole
        // expression construction in order
        _prefixes: '',
        _source: '',
        _suffixes: '',
        _modifiers: 'gm', // default to global multiline matching

        // Sanitation function for adding
        // anything safely to the expression
        sanitize: function sanitize(value) {
            if (value.source) {
                return value.source;
            }

            if (typeof value === 'number') {
                return value;
            }

            // Regular expression meta characters, URL: https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/regexp
            var reRegExpEscape = /([\].|*?+(){}^$\\:=[])/g;

            // Escape RegExp special characters only
            // $& => Last match, URL: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/lastMatch
            return value.replace(reRegExpEscape, '\\$&');
        },

        // Function to add stuff to the
        // expression. Also compiles the
        // new expression so it's ready to
        // be used.
        add: function add(value) {
            this._source += value || '';
            this.compile(this._prefixes + this._source + this._suffixes, this._modifiers);

            return this;
        },

        // Start and end of line functions
        startOfLine: function startOfLine(enable) {
            enable = (enable !== false);
            this._prefixes = enable ? '^' : '';
            this.add();

            return this;
        },

        endOfLine: function endOfLine(enable) {
            enable = (enable !== false);
            this._suffixes = enable ? '$' : '';
            this.add();

            return this;
        },

        // We try to keep the syntax as
        // user-friendly as possible.
        // So we can use the "normal"
        // behaviour to split the "sentences"
        // naturally.
        then: function then(value) {
            value = this.sanitize(value);
            this.add('(?:' + value + ')');

            return this;
        },

        // And because we can't start with
        // "then" function, we create an alias
        // to be used as the first function
        // of the chain.
        find: function find(value) {
            return this.then(value);
        },

        // Maybe is used to add values with ?
        maybe: function maybe(value) {
            value = this.sanitize(value);
            this.add('(?:' + value + ')?');

            return this;
        },

        // Any character any number of times
        anything: function anything() {
            this.add('(?:.*)');
            return this;
        },

        // Anything but these characters
        anythingBut: function anythingBut(value) {
            value = this.sanitize(value);
            this.add('(?:[^' + value + ']*)');

            return this;
        },

        // Any character at least one time
        something: function something() {
            this.add('(?:.+)');
            return this;
        },

        // Any character at least one time except for these characters
        somethingBut: function somethingBut(value) {
            value = this.sanitize(value);
            this.add('(?:[^' + value + ']+)');

            return this;
        },

        // Shorthand function for the
        // String.replace function to
        // give more logical flow if, for
        // example, we're doing multiple
        // replacements on one regexp.
        replace: function replace(source, value) {
            source = source.toString();
            return source.replace(this, value);
        },

        /// Add regular expression special ///
        /// characters                     ///

        // Line break
        lineBreak: function lineBreak() {
            this.add('(?:\\r\\n|\\r|\\n)'); // Unix + Windows CRLF
            return this;
        },

        // And a shorthand for html-minded
        br: function br() {
            return this.lineBreak();
        },

        // Tab (duh?)
        tab: function tab() {
            this.add('\\t');
            return this;
        },

        // Any alphanumeric
        word: function word() {
            this.add('\\w+');
            return this;
        },

        // Any whitespace
        whitespace: function whitespace() {
            this.add('\\s');
            return this;
        },

        // Any given character
        anyOf: function anyOf(value) {
            value = this.sanitize(value);
            this.add('[' + value + ']');
            return this;
        },

        // Shorthand
        any: function any(value) {
            return this.anyOf(value);
        },

        // Usage: .range( from, to [, from, to ... ] )
        range: function range() {
            var value = '[';

            for (var _to = 1; _to < arguments.length; _to += 2) {
                var from = this.sanitize(arguments[_to - 1]);
                var to = this.sanitize(arguments[_to]);

                value += from + '-' + to;
            }

            value += ']';

            this.add(value);

            return this;
        },

        /// Modifiers      ///

        // Modifier abstraction
        addModifier: function addModifier(modifier) {
            if (this._modifiers.indexOf(modifier) === -1) {
                this._modifiers += modifier;
            }

            this.add();

            return this;
        },

        removeModifier: function removeModifier(modifier) {
            this._modifiers = this._modifiers.replace(modifier, '');
            this.add();

            return this;
        },

        // Case-insensitivity modifier
        withAnyCase: function withAnyCase(enable) {
            if (enable !== false) {
                this.addModifier('i');
            } else {
                this.removeModifier('i');
            }

            this.add();

            return this;
        },

        // Default behaviour is with "g" modifier,
        // so we can turn this another way around
        // than other modifiers
        stopAtFirst: function stopAtFirst(enable) {
            if (enable !== false) {
                this.removeModifier('g');
            } else {
                this.addModifier('g');
            }

            this.add();

            return this;
        },

        // Multiline, also reversed
        searchOneLine: function searchOneLine(enable) {
            if (enable !== false) {
                this.removeModifier('m');
            } else {
                this.addModifier('m');
            }

            this.add();

            return this;
        },

        // Repeats the previous item
        // exactly n times or
        // between n and m times.
        repeatPrevious: function repeatPrevious() {
            var value;
            if (arguments.length <= 1) {
                if (/\d+/.exec(arguments[0]) !== null) {
                    value = '{' + arguments[0] + '}';
                }
            } else {
                var values = [];
                for (var i = 0; i < arguments.length; i++) {
                    if (/\d+/.exec(arguments[i]) !== null) {
                        values.push(arguments[i]);
                    }
                }

                value = '{' + values.join(',') + '}';
            }

            this.add(value);

            return (this);
        },

        // Repeats the previous at least ones
        oneOrMore: function oneOrMore() {
            this.add('+');
            return (this);
        },

        /// Loops  ///

        multiple: function multiple(value) {
            // Use expression or string

            value = value.source ? value.source : this.sanitize(value);
            if (arguments.length === 1) {
                this.add('(?:' + value + ')*');
            }

            if (arguments.length > 1) {
                this.add('(?:' + value + ')');
                this.add('{' + arguments[1] + '}');
            }

            return this;
        },

        // Adds alternative expressions
        or: function or(value) {
            this._prefixes += '(?:';
            this._suffixes = ')' + this._suffixes;

            this.add(')|(?:');
            if (value) {
                this.then(value);
            }

            return this;
        },

        // Starts a capturing group
        beginCapture: function beginCapture() {
            // Add the end of the capture group to the suffixes for now so compilation continues to work
            this._suffixes += ')';
            this.add('(');

            return this;
        },

        // Ends a capturing group
        endCapture: function endCapture() {
            // Remove the last parentheses from the _suffixes and add to the regex itself
            this._suffixes = this._suffixes.substring(0, this._suffixes.length - 1);
            this.add(')');

            return this;
        },

        // Convert to RegExp object
        toRegExp: function toRegExp() {
            var array = this.toString().match(/\/(.*)\/([gimuy]+)?/);
            return new RegExp(array[1], array[2]);
        },
    };

    function createVerbalExpression() {
        return new VerbalExpression();
    }

    // Support both browser and node.js
    if (typeof module !== 'undefined' && module.exports) {
        module.exports = createVerbalExpression;
    } else if (typeof define === 'function' && define.amd) {
        define(function define() {
            return VerbalExpression;
        });
    } else {
        root.VerEx = createVerbalExpression;
    }
})();
<|endoftext|>


<commit_before>import React, {Component} from 'react';
import PropTypes from 'prop-types';
import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';
import classNames from 'classnames';
import {autobind} from 'core-decorators';
import {compose} from 'recompose';

import {events as metricEvents} from '@ciscospark/react-redux-spark-metrics';
import MessageWidget from '@ciscospark/widget-message';
import MeetWidget from '@ciscospark/widget-meet';
import RosterWidget from '@ciscospark/widget-roster';
import TitleBar from '@ciscospark/react-component-title-bar';
import Button from '@ciscospark/react-component-button';
import LoadingScreen from '@ciscospark/react-component-loading-screen';
import Timer from '@ciscospark/react-component-timer';
import ErrorDisplay from '@ciscospark/react-component-error-display';

import {getFeature} from '@ciscospark/redux-module-features';
import {fetchCurrentUser} from '@ciscospark/redux-module-user';
import {fetchAvatar} from '@ciscospark/redux-module-avatar';
import {connectToMercury} from '@ciscospark/redux-module-mercury';
import {
  ICON_TYPE_CONTACT,
  ICON_TYPE_MESSAGE_OUTLINE,
  ICON_TYPE_VIDEO_OUTLINE,
  ICON_TYPE_WAFFLE
} from '@ciscospark/react-component-icon';

import ActivityMenu from './components/activity-menu';

import {
  getSpaceDetails,
  updateActivityType,
  updateSecondaryActivityType,
  storeActivityTypes,
  storeToPerson,
  storeSpaceDetails,
  toggleActivityMenuVisible,
  updateWidgetStatus
} from './actions';
import messages from './messages';
import {getSpaceWidgetProps} from './selector';
import styles from './styles.css';
import {eventNames} from './events';
import enhancers from './enhancers';

const ACTIVITY_TYPE_PRIMARY = `ACTIVITY_TYPE_PRIMARY`;
const ACTIVITY_TYPE_SECONDARY = `ACTIVITY_TYPE_SECONDARY`;
const DEFAULT_ACTIVITY = `message`;

export class SpaceWidget extends Component {

  constructor(props) {
    super(props);
    // Store activity types used in widget
    const DEFAULT_ACTIVITY_TYPES = [
      {
        displayName: `Message`,
        name: `message`,
        buttonClassName: styles.messageButton,
        iconClassName: ICON_TYPE_MESSAGE_OUTLINE,
        component: MessageWidget,
        spaceTypes: [`group`, `direct`],
        type: ACTIVITY_TYPE_PRIMARY
      },
      {
        displayName: `Call`,
        name: `meet`,
        buttonClassName: styles.meetButton,
        iconClassName: ICON_TYPE_VIDEO_OUTLINE,
        component: MeetWidget,
        spaceTypes: [`direct`, `group`],
        type: ACTIVITY_TYPE_PRIMARY,
        feature: {
          type: `developer`,
          key: `js-widgets-group-calling`
        }
      },
      {
        displayName: `Call`,
        name: `meet`,
        buttonClassName: styles.meetButton,
        iconClassName: ICON_TYPE_VIDEO_OUTLINE,
        component: MeetWidget,
        spaceTypes: [`direct`],
        type: ACTIVITY_TYPE_PRIMARY,
        feature: {
          type: `developer`,
          key: `js-widgets-group-calling`,
          // Hide this option if feature flag is set
          hide: true
        }
      },
      {
        displayName: `People`,
        name: `people`,
        buttonClassName: styles.peopleButton,
        iconClassName: ICON_TYPE_CONTACT,
        component: RosterWidget,
        spaceTypes: [`direct`, `group`],
        type: ACTIVITY_TYPE_SECONDARY,
        props: {
          onClickClose: this.handleSecondaryDismiss,
          onClickMenu: this.handleMenuClick
        },
        feature: {
          type: `developer`,
          key: `js-widgets-roster`
        }
      }
    ];

    let activityTypes = DEFAULT_ACTIVITY_TYPES;
    if (typeof props.customActivityTypes === `object`) {
      activityTypes = activityTypes.concat(props.customActivityTypes);
    }
    props.storeActivityTypes(activityTypes);

    if (props.toPersonEmail || props.toPersonId) {
      props.storeToPerson(props.toPersonEmail || props.toPersonId);
    }
  }

  componentWillMount() {
    const props = this.props;
    this.setup(props);
  }

  componentWillReceiveProps(nextProps) {
    const {
      call,
      spaceDetails,
      activityTypes,
      initialActivity
    } = nextProps;
    this.setup(nextProps);
    const props = this.props;
    const prevCall = props.call;
    // Switch to Meet if ringing
    if (call && !prevCall) {
      nextProps.updateActivityType(`meet`);
    }

    // Reset back to message view after call ends
    if (prevCall && !call) {
      nextProps.updateActivityType(`message`);
    }

    // Set the initial activity once we get space details
    if (!props.spaceDetails && spaceDetails) {
      if (activityTypes.find((a) => a.name === initialActivity)) {
        props.updateActivityType(initialActivity);
      }
      else {
        props.updateActivityType(DEFAULT_ACTIVITY);
      }
    }
  }

  shouldComponentUpdate(nextProps) {
    return nextProps !== this.props;
  }

  componentWillUnmount() {
    this.props.removeWidget();
  }

  @autobind
  setup(props) { // eslint-disable-line complexity
    const {
      conversation,
      errors,
      spaceId,
      sparkInstance,
      sparkState,
      toPersonEmail,
      toPersonId,
      mercury,
      metrics,
      user,
      spaceDetails,
      widgetStatus
    } = props;

    if (sparkInstance
      && sparkState.get(`authenticated`)
      && sparkState.get(`registered`)
      && !sparkState.get(`hasError`)) {
      // Get current user details
      if (!user.getIn([`status`, `isFetchingCurrentUser`]) && !user.get(`currentUser`)) {
        props.fetchCurrentUser(sparkInstance);
      }
      if (!widgetStatus.get(`isFetchingSpaceDetails`) && !errors.get(`hasError`) && !spaceDetails) {
        if (spaceId) {
          props.getSpaceDetails(sparkInstance, spaceId, props.intl);
        }
        // Hack until we find a way to get a one-on-one space's details before loading conversation
        else if (toPersonEmail || toPersonId) {
          props.storeSpaceDetails({
            type: `direct`
          });
        }
      }

      this.getFeatureFlags(props);

      // Setup Web Sockets
      if (!mercury.getIn([`status`, `hasConnected`])
        && !mercury.getIn([`status`, `isConnecting`])
        && !mercury.getIn([`status`, `isConnected`])
        && sparkInstance.internal.device.registered) {
        props.connectToMercury(sparkInstance);
      }

      if (conversation.get(`id`)) {
        metrics.sendEndMetric(metricEvents.WIDGET_LOAD);
        props.fetchAvatar({space: conversation.toJS()}, sparkInstance);
      }

    }
  }

  @autobind
  getFeatureFlags(props) {
    const {
      widgetSpace,
      widgetStatus
    } = props;
    if (widgetStatus.get(`hasFetchedFeatureFlags`) || widgetStatus.get(`isFetchingFeatureFlags`)) {
      return;
    }
    props.updateWidgetStatus({isFetchingFeatureFlags: true});
    widgetSpace.get(`activityTypes`).toJS().forEach((activityType) => {
      if (!activityType.feature) {
        return;
      }
      props.getFeature(activityType.feature.type, activityType.feature.key, props.sparkInstance);
    });
    props.updateWidgetStatus({hasFetchedFeatureFlags: true, isFetchingFeatureFlags: false});
  }

  @autobind
  handleActivityChange(activity) {
    if (activity.type === ACTIVITY_TYPE_PRIMARY) {
      this.props.updateActivityType(activity.name);
    }
    else {
      this.props.updateSecondaryActivityType(activity.name);
    }
  }

  @autobind
  handleSecondaryDismiss() {
    this.props.updateSecondaryActivityType(null);
  }

  @autobind
  handleMenuClick() {
    this.props.toggleActivityMenuVisible();
  }

  /**
   * Builds widget elements from activityTypes list
   *
   * @param {List} activityTypes Immutable List of activity types
   * @param {String} primaryActivityType Active activity type
   * @returns Array of Widget elements
   */
  @autobind
  constructActivityWidgets(activityTypes, primaryActivityType) {
    return activityTypes
      .filter((activityType) => activityType.type !== ACTIVITY_TYPE_SECONDARY)
      .map((a) => {
        // Only allow widget if it's available for this space type
        const Widget = a.component;
        const isVisible = primaryActivityType === a.name ? `` : styles.hidden;
        return (
          <div
            className={classNames(`ciscospark-${a.name}-wrapper`, styles.activityComponentWrapper, isVisible)}
            key={a.name}
          >
            <Widget
              {...this.props}
              eventNames={eventNames}
              injectProvider={false}
              toPerson={this.props.spaceDetails.get(`toPerson`)}
            />

        );
      });
  }

  /**
   * Builds secondary widget elements
   *
   * @param {List} activityTypes
   * @param {String} secondaryActivityType
   */
  @autobind
  constructSecondaryActivityWidget(activityTypes, secondaryActivityType) {
    if (!secondaryActivityType) {
      return null;
    }
    return activityTypes
      .filter((activityType) => activityType.type === ACTIVITY_TYPE_SECONDARY && activityType.name === secondaryActivityType)
      .map((activityType) => {
        const Widget = activityType.component;
        return (
          <div
            className={classNames(`ciscospark-secondary-widget`, styles.secondaryWidget)}
            key={activityType.name}
          >
            <Widget
              {...this.props}
              {...activityType.props}
              eventNames={eventNames}
              injectProvider={false}
            />

        );
      });
  }

  render() {
    const props = this.props;
    const {
      activityTypes,
      media,
      sparkInstance,
      spaceDetails,
      errors,
      widgetSpace,
      widgetStatus
    } = props;

    const {formatMessage} = props.intl;
    let errorElement;
    if (errors.get(`hasError`)) {
      const widgetError = errors.get(`errors`).first();
      errorElement = ( // eslint-disable-line no-extra-parens
        <div className={classNames(`ciscospark-error-wrapper`, styles.errorWrapper)}>
          <ErrorDisplay
            secondaryTitle={widgetError.displaySubtitle}
            title={widgetError.displayTitle}
            transparent={widgetError.temporary}
            {...widgetError}
          />

      );
    }
    if (sparkInstance && spaceDetails) {
      // Construct widgets
      const primaryActivityType = widgetSpace.get(`primaryActivityType`);
      const secondaryActivityType = widgetSpace.get(`secondaryActivityType`);
      const widgets = this.constructActivityWidgets(activityTypes, primaryActivityType);
      const secondaryWidget = this.constructSecondaryActivityWidget(activityTypes, secondaryActivityType);
      const spaceTitle = spaceDetails.get(`title`) || `Loading...`;
      const spaceAvatar = spaceDetails.get(`avatar`);
      const callStartTime = media.get(`callStartTime`);
      return (
        <div className={classNames(`ciscospark-space-widget`, styles.spaceWidget)}>
          { errors.get(`hasError`) && errorElement }
          {
            widgetStatus.get(`activityMenuVisible`) &&
            <div className={classNames(`ciscospark-activity-menu-wrapper`, styles.activityMenuWrapper)}>
              <ActivityMenu
                activityTypes={activityTypes}
                onChange={this.handleActivityChange}
                onExit={this.handleMenuClick}
              />

          }
          {
            secondaryWidget
          }
          <div className={classNames(`ciscospark-title-bar-wrapper`, styles.titleBarWrapper)}>
            <TitleBar image={spaceAvatar} name={spaceTitle}>
              {
                callStartTime &&
                <div className={classNames(`ciscospark-title-bar-call-timer`, styles.callTimer)}>
                  <Timer startTime={callStartTime} />

              }
              <div className={classNames(`ciscospark-activity-menu-button-wrapper`, styles.activityMenuButtonWrapper)}>
                <div className={classNames(`ciscospark-activity-menu-button`, styles.activityMenuButton)}>
                  <Button accessibilityLabel={`Main Menu`} iconType={ICON_TYPE_WAFFLE} onClick={this.handleMenuClick} />

            </TitleBar>

          <div className={classNames(`ciscospark-widget-body`, styles.widgetBody)}>
            {widgets}

      );
    }
    else if (errors.get(`hasError`)) {
      return errorElement;
    }

    return <LoadingScreen loadingMessage={formatMessage(messages.loadingMessage)} />;
  }
}

const injectedPropTypes = {
  activityTypes: PropTypes.array,
  call: PropTypes.object,
  conversation: PropTypes.object,
  errors: PropTypes.object,
  media: PropTypes.object,
  mercury: PropTypes.object,
  sparkInstance: PropTypes.object,
  user: PropTypes.object,
  widgetSpace: PropTypes.object,
  connectToMercury: PropTypes.func,
  fetchAvatar: PropTypes.func,
  getFeature: PropTypes.func,
  getSpaceDetails: PropTypes.func,
  storeActivityTypes: PropTypes.func,
  storeSpaceDetails: PropTypes.func,
  toggleActivityMenuVisible: PropTypes.func,
  updateActivityType: PropTypes.func,
  updateSecondaryActivityType: PropTypes.func,
  updateWidgetStatus: PropTypes.func
};

export const ownPropTypes = {
  customActivityTypes: PropTypes.object,
  initialActivity: PropTypes.string,
  muteNotifications: PropTypes.bool,
  spaceId: PropTypes.string,
  startCall: PropTypes.oneOfType([
    PropTypes.string,
    PropTypes.bool
  ]),
  toPersonEmail: PropTypes.string,
  toPersonId: PropTypes.string
};

SpaceWidget.propTypes = {
  ...injectedPropTypes,
  ...ownPropTypes
};

SpaceWidget.defaultProps = {
  initialActivity: DEFAULT_ACTIVITY
};

function mapStateToProps(state, props) {
  return getSpaceWidgetProps(state, props);
}

export default compose(
  connect(
    mapStateToProps,
    (dispatch) => bindActionCreators({
      connectToMercury,
      getFeature,
      getSpaceDetails,
      fetchAvatar,
      fetchCurrentUser,
      getSpaceWidgetProps,
      storeActivityTypes,
      storeSpaceDetails,
      storeToPerson,
      toggleActivityMenuVisible,
      updateActivityType,
      updateSecondaryActivityType,
      updateWidgetStatus
    }, dispatch)
  ),
  ...enhancers
)(SpaceWidget);
<commit_msg>refactor(widget-space): Remove extra error check<commit_after>import React, {Component} from 'react';
import PropTypes from 'prop-types';
import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';
import classNames from 'classnames';
import {autobind} from 'core-decorators';
import {compose} from 'recompose';

import {events as metricEvents} from '@ciscospark/react-redux-spark-metrics';
import MessageWidget from '@ciscospark/widget-message';
import MeetWidget from '@ciscospark/widget-meet';
import RosterWidget from '@ciscospark/widget-roster';
import TitleBar from '@ciscospark/react-component-title-bar';
import Button from '@ciscospark/react-component-button';
import LoadingScreen from '@ciscospark/react-component-loading-screen';
import Timer from '@ciscospark/react-component-timer';
import ErrorDisplay from '@ciscospark/react-component-error-display';

import {getFeature} from '@ciscospark/redux-module-features';
import {fetchCurrentUser} from '@ciscospark/redux-module-user';
import {fetchAvatar} from '@ciscospark/redux-module-avatar';
import {connectToMercury} from '@ciscospark/redux-module-mercury';
import {
  ICON_TYPE_CONTACT,
  ICON_TYPE_MESSAGE_OUTLINE,
  ICON_TYPE_VIDEO_OUTLINE,
  ICON_TYPE_WAFFLE
} from '@ciscospark/react-component-icon';

import ActivityMenu from './components/activity-menu';

import {
  getSpaceDetails,
  updateActivityType,
  updateSecondaryActivityType,
  storeActivityTypes,
  storeToPerson,
  storeSpaceDetails,
  toggleActivityMenuVisible,
  updateWidgetStatus
} from './actions';
import messages from './messages';
import {getSpaceWidgetProps} from './selector';
import styles from './styles.css';
import {eventNames} from './events';
import enhancers from './enhancers';

const ACTIVITY_TYPE_PRIMARY = `ACTIVITY_TYPE_PRIMARY`;
const ACTIVITY_TYPE_SECONDARY = `ACTIVITY_TYPE_SECONDARY`;
const DEFAULT_ACTIVITY = `message`;

export class SpaceWidget extends Component {

  constructor(props) {
    super(props);
    // Store activity types used in widget
    const DEFAULT_ACTIVITY_TYPES = [
      {
        displayName: `Message`,
        name: `message`,
        buttonClassName: styles.messageButton,
        iconClassName: ICON_TYPE_MESSAGE_OUTLINE,
        component: MessageWidget,
        spaceTypes: [`group`, `direct`],
        type: ACTIVITY_TYPE_PRIMARY
      },
      {
        displayName: `Call`,
        name: `meet`,
        buttonClassName: styles.meetButton,
        iconClassName: ICON_TYPE_VIDEO_OUTLINE,
        component: MeetWidget,
        spaceTypes: [`direct`, `group`],
        type: ACTIVITY_TYPE_PRIMARY,
        feature: {
          type: `developer`,
          key: `js-widgets-group-calling`
        }
      },
      {
        displayName: `Call`,
        name: `meet`,
        buttonClassName: styles.meetButton,
        iconClassName: ICON_TYPE_VIDEO_OUTLINE,
        component: MeetWidget,
        spaceTypes: [`direct`],
        type: ACTIVITY_TYPE_PRIMARY,
        feature: {
          type: `developer`,
          key: `js-widgets-group-calling`,
          // Hide this option if feature flag is set
          hide: true
        }
      },
      {
        displayName: `People`,
        name: `people`,
        buttonClassName: styles.peopleButton,
        iconClassName: ICON_TYPE_CONTACT,
        component: RosterWidget,
        spaceTypes: [`direct`, `group`],
        type: ACTIVITY_TYPE_SECONDARY,
        props: {
          onClickClose: this.handleSecondaryDismiss,
          onClickMenu: this.handleMenuClick
        },
        feature: {
          type: `developer`,
          key: `js-widgets-roster`
        }
      }
    ];

    let activityTypes = DEFAULT_ACTIVITY_TYPES;
    if (typeof props.customActivityTypes === `object`) {
      activityTypes = activityTypes.concat(props.customActivityTypes);
    }
    props.storeActivityTypes(activityTypes);

    if (props.toPersonEmail || props.toPersonId) {
      props.storeToPerson(props.toPersonEmail || props.toPersonId);
    }
  }

  componentWillMount() {
    const props = this.props;
    this.setup(props);
  }

  componentWillReceiveProps(nextProps) {
    const {
      call,
      spaceDetails,
      activityTypes,
      initialActivity
    } = nextProps;
    this.setup(nextProps);
    const props = this.props;
    const prevCall = props.call;
    // Switch to Meet if ringing
    if (call && !prevCall) {
      nextProps.updateActivityType(`meet`);
    }

    // Reset back to message view after call ends
    if (prevCall && !call) {
      nextProps.updateActivityType(`message`);
    }

    // Set the initial activity once we get space details
    if (!props.spaceDetails && spaceDetails) {
      if (activityTypes.find((a) => a.name === initialActivity)) {
        props.updateActivityType(initialActivity);
      }
      else {
        props.updateActivityType(DEFAULT_ACTIVITY);
      }
    }
  }

  shouldComponentUpdate(nextProps) {
    return nextProps !== this.props;
  }

  componentWillUnmount() {
    this.props.removeWidget();
  }

  @autobind
  setup(props) { // eslint-disable-line complexity
    const {
      conversation,
      errors,
      spaceId,
      sparkInstance,
      sparkState,
      toPersonEmail,
      toPersonId,
      mercury,
      metrics,
      user,
      spaceDetails,
      widgetStatus
    } = props;

    if (sparkInstance
      && sparkState.get(`authenticated`)
      && sparkState.get(`registered`)
      && !sparkState.get(`hasError`)) {
      // Get current user details
      if (!user.getIn([`status`, `isFetchingCurrentUser`]) && !user.get(`currentUser`)) {
        props.fetchCurrentUser(sparkInstance);
      }
      if (!widgetStatus.get(`isFetchingSpaceDetails`) && !errors.get(`hasError`) && !spaceDetails) {
        if (spaceId) {
          props.getSpaceDetails(sparkInstance, spaceId, props.intl);
        }
        // Hack until we find a way to get a one-on-one space's details before loading conversation
        else if (toPersonEmail || toPersonId) {
          props.storeSpaceDetails({
            type: `direct`
          });
        }
      }

      this.getFeatureFlags(props);

      // Setup Web Sockets
      if (!mercury.getIn([`status`, `hasConnected`])
        && !mercury.getIn([`status`, `isConnecting`])
        && !mercury.getIn([`status`, `isConnected`])
        && sparkInstance.internal.device.registered) {
        props.connectToMercury(sparkInstance);
      }

      if (conversation.get(`id`)) {
        metrics.sendEndMetric(metricEvents.WIDGET_LOAD);
        props.fetchAvatar({space: conversation.toJS()}, sparkInstance);
      }

    }
  }

  @autobind
  getFeatureFlags(props) {
    const {
      widgetSpace,
      widgetStatus
    } = props;
    if (widgetStatus.get(`hasFetchedFeatureFlags`) || widgetStatus.get(`isFetchingFeatureFlags`)) {
      return;
    }
    props.updateWidgetStatus({isFetchingFeatureFlags: true});
    widgetSpace.get(`activityTypes`).toJS().forEach((activityType) => {
      if (!activityType.feature) {
        return;
      }
      props.getFeature(activityType.feature.type, activityType.feature.key, props.sparkInstance);
    });
    props.updateWidgetStatus({hasFetchedFeatureFlags: true, isFetchingFeatureFlags: false});
  }

  @autobind
  handleActivityChange(activity) {
    if (activity.type === ACTIVITY_TYPE_PRIMARY) {
      this.props.updateActivityType(activity.name);
    }
    else {
      this.props.updateSecondaryActivityType(activity.name);
    }
  }

  @autobind
  handleSecondaryDismiss() {
    this.props.updateSecondaryActivityType(null);
  }

  @autobind
  handleMenuClick() {
    this.props.toggleActivityMenuVisible();
  }

  /**
   * Builds widget elements from activityTypes list
   *
   * @param {List} activityTypes Immutable List of activity types
   * @param {String} primaryActivityType Active activity type
   * @returns Array of Widget elements
   */
  @autobind
  constructActivityWidgets(activityTypes, primaryActivityType) {
    return activityTypes
      .filter((activityType) => activityType.type !== ACTIVITY_TYPE_SECONDARY)
      .map((a) => {
        // Only allow widget if it's available for this space type
        const Widget = a.component;
        const isVisible = primaryActivityType === a.name ? `` : styles.hidden;
        return (
          <div
            className={classNames(`ciscospark-${a.name}-wrapper`, styles.activityComponentWrapper, isVisible)}
            key={a.name}
          >
            <Widget
              {...this.props}
              eventNames={eventNames}
              injectProvider={false}
              toPerson={this.props.spaceDetails.get(`toPerson`)}
            />

        );
      });
  }

  /**
   * Builds secondary widget elements
   *
   * @param {List} activityTypes
   * @param {String} secondaryActivityType
   */
  @autobind
  constructSecondaryActivityWidget(activityTypes, secondaryActivityType) {
    if (!secondaryActivityType) {
      return null;
    }
    return activityTypes
      .filter((activityType) => activityType.type === ACTIVITY_TYPE_SECONDARY && activityType.name === secondaryActivityType)
      .map((activityType) => {
        const Widget = activityType.component;
        return (
          <div
            className={classNames(`ciscospark-secondary-widget`, styles.secondaryWidget)}
            key={activityType.name}
          >
            <Widget
              {...this.props}
              {...activityType.props}
              eventNames={eventNames}
              injectProvider={false}
            />

        );
      });
  }

  render() {
    const props = this.props;
    const {
      activityTypes,
      media,
      sparkInstance,
      spaceDetails,
      errors,
      widgetSpace,
      widgetStatus
    } = props;

    const {formatMessage} = props.intl;
    let errorElement;
    if (errors.get(`hasError`)) {
      const widgetError = errors.get(`errors`).first();
      errorElement = ( // eslint-disable-line no-extra-parens
        <div className={classNames(`ciscospark-error-wrapper`, styles.errorWrapper)}>
          <ErrorDisplay
            secondaryTitle={widgetError.displaySubtitle}
            title={widgetError.displayTitle}
            transparent={widgetError.temporary}
            {...widgetError}
          />

      );
    }
    if (sparkInstance && spaceDetails) {
      // Construct widgets
      const primaryActivityType = widgetSpace.get(`primaryActivityType`);
      const secondaryActivityType = widgetSpace.get(`secondaryActivityType`);
      const widgets = this.constructActivityWidgets(activityTypes, primaryActivityType);
      const secondaryWidget = this.constructSecondaryActivityWidget(activityTypes, secondaryActivityType);
      const spaceTitle = spaceDetails.get(`title`) || `Loading...`;
      const spaceAvatar = spaceDetails.get(`avatar`);
      const callStartTime = media.get(`callStartTime`);
      return (
        <div className={classNames(`ciscospark-space-widget`, styles.spaceWidget)}>
          { errorElement }
          {
            widgetStatus.get(`activityMenuVisible`) &&
            <div className={classNames(`ciscospark-activity-menu-wrapper`, styles.activityMenuWrapper)}>
              <ActivityMenu
                activityTypes={activityTypes}
                onChange={this.handleActivityChange}
                onExit={this.handleMenuClick}
              />

          }
          {
            secondaryWidget
          }
          <div className={classNames(`ciscospark-title-bar-wrapper`, styles.titleBarWrapper)}>
            <TitleBar image={spaceAvatar} name={spaceTitle}>
              {
                callStartTime &&
                <div className={classNames(`ciscospark-title-bar-call-timer`, styles.callTimer)}>
                  <Timer startTime={callStartTime} />

              }
              <div className={classNames(`ciscospark-activity-menu-button-wrapper`, styles.activityMenuButtonWrapper)}>
                <div className={classNames(`ciscospark-activity-menu-button`, styles.activityMenuButton)}>
                  <Button accessibilityLabel={`Main Menu`} iconType={ICON_TYPE_WAFFLE} onClick={this.handleMenuClick} />

            </TitleBar>

          <div className={classNames(`ciscospark-widget-body`, styles.widgetBody)}>
            {widgets}

      );
    }
    else if (errors.get(`hasError`)) {
      return errorElement;
    }

    return <LoadingScreen loadingMessage={formatMessage(messages.loadingMessage)} />;
  }
}

const injectedPropTypes = {
  activityTypes: PropTypes.array,
  call: PropTypes.object,
  conversation: PropTypes.object,
  errors: PropTypes.object,
  media: PropTypes.object,
  mercury: PropTypes.object,
  sparkInstance: PropTypes.object,
  user: PropTypes.object,
  widgetSpace: PropTypes.object,
  connectToMercury: PropTypes.func,
  fetchAvatar: PropTypes.func,
  getFeature: PropTypes.func,
  getSpaceDetails: PropTypes.func,
  storeActivityTypes: PropTypes.func,
  storeSpaceDetails: PropTypes.func,
  toggleActivityMenuVisible: PropTypes.func,
  updateActivityType: PropTypes.func,
  updateSecondaryActivityType: PropTypes.func,
  updateWidgetStatus: PropTypes.func
};

export const ownPropTypes = {
  customActivityTypes: PropTypes.object,
  initialActivity: PropTypes.string,
  muteNotifications: PropTypes.bool,
  spaceId: PropTypes.string,
  startCall: PropTypes.oneOfType([
    PropTypes.string,
    PropTypes.bool
  ]),
  toPersonEmail: PropTypes.string,
  toPersonId: PropTypes.string
};

SpaceWidget.propTypes = {
  ...injectedPropTypes,
  ...ownPropTypes
};

SpaceWidget.defaultProps = {
  initialActivity: DEFAULT_ACTIVITY
};

function mapStateToProps(state, props) {
  return getSpaceWidgetProps(state, props);
}

export default compose(
  connect(
    mapStateToProps,
    (dispatch) => bindActionCreators({
      connectToMercury,
      getFeature,
      getSpaceDetails,
      fetchAvatar,
      fetchCurrentUser,
      getSpaceWidgetProps,
      storeActivityTypes,
      storeSpaceDetails,
      storeToPerson,
      toggleActivityMenuVisible,
      updateActivityType,
      updateSecondaryActivityType,
      updateWidgetStatus
    }, dispatch)
  ),
  ...enhancers
)(SpaceWidget);
<|endoftext|>


<commit_before>
const path = require('path');
const webpack = require('webpack');
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
const ProgressBarPlugin = require('progress-bar-webpack-plugin');
const {getBabelPath, getExcludeBabelPath, getGlobalCheckWhiteList, getFreePort} = require('./utils');
var UglifyJsPlugin = require('uglifyjs-webpack-plugin')
var OptimizeCSSPlugin = require('optimize-css-assets-webpack-plugin')
const ChameleonWebpackPlugin = require('chameleon-webpack-plugin')
const WebpackCheckPlugin = require('webpack-check-plugin')
const config = require('./config.js');
const ChameleonErrorsWebpackPlugin = require('chameleon-errors-webpack-plugin');
const fs = require('fs');
const cmlUtils = require('chameleon-tool-utils');
const ExtraWatchWebpackPlugin = require('extra-watch-webpack-plugin');
const DuplicatePackageCheckerPlugin = require('duplicate-package-checker-webpack-plugin');

module.exports = function (options) {
  let {
    type,
    media,
    root
  } = options;

  function getstaticPath(filetype) {
    let staticPath = `static/${filetype}/[name]_[hash:7].[ext]`;
    if (options.staticPath) {
      staticPath = options.staticPath + staticPath;
    }
    return staticPath;
  }

  let webServerPort = getFreePort().webServerPort;

  let publicPath;
  let defaultPublichPathMap = {
    'wx': `http://${config.ip}:${webServerPort}/wx/`,
    'qq': `http://${config.ip}:${webServerPort}/qq/`,
    'tt': `http://${config.ip}:${webServerPort}/tt/`,
    'alipay': `http://${config.ip}:${webServerPort}/alipay/`,
    'baidu': `http://${config.ip}:${webServerPort}/baidu/`, // baidu小程序的publicPath不能设置能/  所以在启动dev服务的时候 也将dist作为静态资源
    'web': `http://${config.ip}:${webServerPort}/`,
    'weex': `http://${config.ip}:${webServerPort}/weex/`
  }
  publicPath = options.publicPath || defaultPublichPathMap[type];
  if (!publicPath) {
    publicPath = `http://${config.ip}:${webServerPort}/${type}/`
  }

  let commonConfig = {
    stats: cml.logLevel === 'debug' ? 'verbose' : 'none',
    output: {
      publicPath: publicPath
    },
    resolve: {
      symlinks: false,
      extensions: ['.cml', '.interface', '.vue', '.js'],
      alias: {
        '$CMLPROJECT': path.resolve(cml.root),
        '/components': path.resolve(cml.projectRoot, 'src/components'),
        '$PROJECT': path.resolve(root),
        '$ROUTER_CONFIG': path.resolve(root, './src/router.config.json')
      },
      modules: [
        'node_modules',
        path.join(cml.root, '/node_modules')
      ]
    },
    resolveLoader: {
      modules: [
        path.join(cml.root, '/node_modules'),
        'node_modules'
      ]
    },
    module: {
      rules: [{
        test: path.resolve(root, 'node_modules/chameleon-runtime/.temp/router.js'),
        loader: path.join(__dirname, 'routerLoader.js')
      },
      {
        test: /\.js$/,
        exclude: getExcludeBabelPath(),
        // 不能babel babel-runtime
        include: getBabelPath(),
        use: [{
          loader: 'babel-loader',
          options: {
            'filename': path.join(cml.root, 'chameleon.js')
          }
        }]
      },
      {
        test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
        loader: 'chameleon-url-loader',
        options: {
          limit: false, // 不做limit的base64转换，需要添加?inline参数
          name: getstaticPath('img'),
          outputPath: function(output) {
            // 处理图片中的@符号 改成_ 解决在支付宝小程序中上传失败的问题
            output = cml.utils.handleSpecialChar(output)
            return output;
          }
        }
      }, {
        test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/,
        loader: 'file-loader',
        options: {
          name: getstaticPath('media'),
          outputPath: function(output) {
            // 处理图片中的@符号 改成_ 解决在支付宝小程序中上传失败的问题
            output = cml.utils.handleSpecialChar(output)
            return output;
          }

        }
      },
      {
        test: /\.(woff|woff2?|eot|ttf|otf)(\?.*)?$/,
        loader: 'file-loader',
        options: {
          name: getstaticPath('fonts'),
          outputPath: function(output) {
            // 处理图片中的@符号 改成_ 解决在支付宝小程序中上传失败的问题
            output = cml.utils.handleSpecialChar(output)
            return output;
          }
        }
      },

      {
        test: /\.interface$/,
        // exclude: /(node_modules|bower_components)/,
        // 不能babel babel-runtime
        include: getBabelPath(),
        use: [{
          loader: 'babel-loader',
          options: {
            'filename': path.join(cml.root, 'chameleon.js')
          }
        },

        {
          loader: 'interface-loader',
          options: {
            cmlType: type,
            media,
            check: cml.config.get().check
          }
        }
        ]
      }
      ]

    },
    plugins: [
      new ProgressBarPlugin(),
      new webpack.DefinePlugin({
        'process.env.platform': JSON.stringify(type)
      }),
      new ChameleonErrorsWebpackPlugin({
        cmlType: type
      })
    ]
  }
  if (cml.config.get().enableGlobalCheck === true) {
    commonConfig.plugins.push(
      new WebpackCheckPlugin({
        cmlType: type,
        whiteListFile: getGlobalCheckWhiteList()
      })
    )
  }

  if (options.definePlugin) {
    commonConfig.plugins.push(new webpack.DefinePlugin(options.definePlugin))
  }
  if (options.analysis) {
    commonConfig.plugins.push(new BundleAnalyzerPlugin())
  }
  let devApiPrefix = `http://${config.ip}:${webServerPort}`
  // 兼容旧版api
  let apiPrefix = options.apiPrefix || devApiPrefix;
  // 新版api 优先读取domain
  // 浅拷贝不影响config中的domain
  let domain = {};
  if (options.domain) {
    domain = {
      ...options.domain
    }
  }

  if (options.media === 'dev') {

    // dev模式添加domainKey参数
    Object.keys(domain).forEach(key => {
      if (domain[key].toLowerCase() === 'localhost') {
        domain[key] = devApiPrefix;
      }
      domain[key] = domain[key] + '__DEV_SPLIT__' + key;
    })
    commonConfig.plugins.push(
      new ExtraWatchWebpackPlugin({
        dirs: [path.join(cml.projectRoot, 'mock/api')]
      })
    );
    commonConfig.plugins.push(
      new DuplicatePackageCheckerPlugin({
        verbose: true,
      }),
    );
  }
  // 兼容旧版api
  commonConfig.plugins.push(new webpack.DefinePlugin({
    'process.env.cmlApiPrefix': JSON.stringify(apiPrefix)
  }))
  Object.keys(domain).forEach(key => {
    commonConfig.plugins.push(new webpack.DefinePlugin({
      ['process.env.domain.' + key]: JSON.stringify(domain[key])
    }))
  })

  commonConfig.plugins.push(new webpack.DefinePlugin({
    'process.env.media': JSON.stringify(options.media)
  }))

  if (options.minimize) {
    commonConfig.plugins = commonConfig.plugins.concat([
      new OptimizeCSSPlugin({
        assetNameRegExp: /\.css$/,
        cssProcessorOptions: { safe: true, discardComments: { removeAll: true }, autoprefixer: false }
      }),
      new UglifyJsPlugin({})
    ])
  }

  let moduleIdType = options.moduleIdType
  let moduleIdMap = {
    hash: new webpack.HashedModuleIdsPlugin(),
    name: new webpack.NamedModulesPlugin(),
    chameleon: new ChameleonWebpackPlugin({openModuleHash: true, openChunkHash: true})
  }
  if (moduleIdType && moduleIdMap[moduleIdType]) {
    commonConfig.plugins.push(moduleIdMap[moduleIdType])
  }

  let subProject = cml.config.get().subProject;
  if (subProject && subProject.length > 0) {
    subProject.forEach(item => {
      let npmName = cmlUtils.isString(item) ? item : item.npmName;
      let packageJSON = JSON.parse(fs.readFileSync(path.resolve(cml.projectRoot, 'node_modules', npmName, 'package.json'), {encoding: 'utf-8'}));
      let cmlConfig = packageJSON.cml || {};
      let definePlugin = cmlConfig.definePlugin;
      if (definePlugin) {
        Object.keys(definePlugin).forEach(key => {
          definePlugin[key] = JSON.stringify(definePlugin[key])
        })
        commonConfig.plugins.push(new webpack.DefinePlugin(definePlugin))
      }
    })
  }

  return commonConfig;
}
<commit_msg>feat: 支持weex多bundle自定义bundle名称<commit_after>
const path = require('path');
const webpack = require('webpack');
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
const ProgressBarPlugin = require('progress-bar-webpack-plugin');
const {getBabelPath, getExcludeBabelPath, getGlobalCheckWhiteList, getFreePort} = require('./utils');
var UglifyJsPlugin = require('uglifyjs-webpack-plugin')
var OptimizeCSSPlugin = require('optimize-css-assets-webpack-plugin')
const ChameleonWebpackPlugin = require('chameleon-webpack-plugin')
const WebpackCheckPlugin = require('webpack-check-plugin')
const config = require('./config.js');
const ChameleonErrorsWebpackPlugin = require('chameleon-errors-webpack-plugin');
const fs = require('fs');
const cmlUtils = require('chameleon-tool-utils');
const ExtraWatchWebpackPlugin = require('extra-watch-webpack-plugin');
const DuplicatePackageCheckerPlugin = require('duplicate-package-checker-webpack-plugin');

module.exports = function (options) {
  let {
    type,
    media,
    root
  } = options;

  function getstaticPath(filetype) {
    let staticPath = `static/${filetype}/[name]_[hash:7].[ext]`;
    if (options.staticPath) {
      staticPath = options.staticPath + staticPath;
    }
    return staticPath;
  }

  let webServerPort = getFreePort().webServerPort;

  let publicPath;
  let defaultPublichPathMap = {
    'wx': `http://${config.ip}:${webServerPort}/wx/`,
    'qq': `http://${config.ip}:${webServerPort}/qq/`,
    'tt': `http://${config.ip}:${webServerPort}/tt/`,
    'alipay': `http://${config.ip}:${webServerPort}/alipay/`,
    'baidu': `http://${config.ip}:${webServerPort}/baidu/`, // baidu小程序的publicPath不能设置能/  所以在启动dev服务的时候 也将dist作为静态资源
    'web': `http://${config.ip}:${webServerPort}/`,
    'weex': `http://${config.ip}:${webServerPort}/weex/`
  }
  publicPath = options.publicPath || defaultPublichPathMap[type];
  if (!publicPath) {
    publicPath = `http://${config.ip}:${webServerPort}/${type}/`
  }

  let commonConfig = {
    stats: cml.logLevel === 'debug' ? 'verbose' : 'none',
    output: {
      publicPath: publicPath
    },
    resolve: {
      symlinks: false,
      extensions: ['.cml', '.interface', '.vue', '.js'],
      alias: {
        '$CMLPROJECT': path.resolve(cml.root),
        '/components': path.resolve(cml.projectRoot, 'src/components'),
        '$PROJECT': path.resolve(root),
        '$ROUTER_CONFIG': path.resolve(root, './src/router.config.json')
      },
      modules: [
        'node_modules',
        path.join(cml.root, '/node_modules')
      ]
    },
    resolveLoader: {
      modules: [
        path.join(cml.root, '/node_modules'),
        'node_modules'
      ]
    },
    module: {
      rules: [{
        test: path.resolve(root, 'node_modules/chameleon-runtime/.temp/router.js'),
        loader: path.join(__dirname, 'routerLoader.js')
      },
      {
        test: /\.js$/,
        exclude: getExcludeBabelPath(),
        // 不能babel babel-runtime
        include: getBabelPath(),
        use: [{
          loader: 'babel-loader',
          options: {
            'filename': path.join(cml.root, 'chameleon.js')
          }
        }]
      },
      {
        test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
        loader: 'chameleon-url-loader',
        options: {
          limit: false, // 不做limit的base64转换，需要添加?inline参数
          name: getstaticPath('img'),
          outputPath: function(output) {
            // 处理图片中的@符号 改成_ 解决在支付宝小程序中上传失败的问题
            output = cml.utils.handleSpecialChar(output)
            return output;
          }
        }
      }, {
        test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/,
        loader: 'file-loader',
        options: {
          name: getstaticPath('media'),
          outputPath: function(output) {
            // 处理图片中的@符号 改成_ 解决在支付宝小程序中上传失败的问题
            output = cml.utils.handleSpecialChar(output)
            return output;
          }

        }
      },
      {
        test: /\.(woff|woff2?|eot|ttf|otf)(\?.*)?$/,
        loader: 'file-loader',
        options: {
          name: getstaticPath('fonts'),
          outputPath: function(output) {
            // 处理图片中的@符号 改成_ 解决在支付宝小程序中上传失败的问题
            output = cml.utils.handleSpecialChar(output)
            return output;
          }
        }
      },

      {
        test: /\.interface$/,
        // exclude: /(node_modules|bower_components)/,
        // 不能babel babel-runtime
        include: getBabelPath(),
        use: [{
          loader: 'babel-loader',
          options: {
            'filename': path.join(cml.root, 'chameleon.js')
          }
        },

        {
          loader: 'interface-loader',
          options: {
            cmlType: type,
            media,
            check: cml.config.get().check
          }
        }
        ]
      }
      ]

    },
    plugins: [
      new ProgressBarPlugin(),
      new webpack.DefinePlugin({
        'process.env.platform': JSON.stringify(type)
      }),
      new ChameleonErrorsWebpackPlugin({
        cmlType: type
      })
    ]
  }
  if (cml.config.get().enableGlobalCheck === true) {
    commonConfig.plugins.push(
      new WebpackCheckPlugin({
        cmlType: type,
        whiteListFile: getGlobalCheckWhiteList()
      })
    )
  }

  if (options.definePlugin) {
    commonConfig.plugins.push(new webpack.DefinePlugin(options.definePlugin))
  }
  if (options.analysis) {
    commonConfig.plugins.push(new BundleAnalyzerPlugin())
  }
  let devApiPrefix = `http://${config.ip}:${webServerPort}`
  // 兼容旧版api
  let apiPrefix = options.apiPrefix || devApiPrefix;
  // 新版api 优先读取domain
  // 浅拷贝不影响config中的domain
  let domain = {};
  if (options.domain) {
    domain = {
      ...options.domain
    }
  }

  if (options.media === 'dev') {

    // dev模式添加domainKey参数
    Object.keys(domain).forEach(key => {
      if (domain[key].toLowerCase() === 'localhost') {
        domain[key] = devApiPrefix;
      }
      domain[key] = domain[key] + '__DEV_SPLIT__' + key;
    })
    commonConfig.plugins.push(
      new ExtraWatchWebpackPlugin({
        dirs: [path.join(cml.projectRoot, 'mock/api')]
      })
    );
    commonConfig.plugins.push(
      new DuplicatePackageCheckerPlugin({
        verbose: true
      })
    );
  }
  // 兼容旧版api
  commonConfig.plugins.push(new webpack.DefinePlugin({
    'process.env.cmlApiPrefix': JSON.stringify(apiPrefix)
  }))
  Object.keys(domain).forEach(key => {
    commonConfig.plugins.push(new webpack.DefinePlugin({
      ['process.env.domain.' + key]: JSON.stringify(domain[key])
    }))
  })

  commonConfig.plugins.push(new webpack.DefinePlugin({
    'process.env.media': JSON.stringify(options.media)
  }))

  if (options.minimize) {
    commonConfig.plugins = commonConfig.plugins.concat([
      new OptimizeCSSPlugin({
        assetNameRegExp: /\.css$/,
        cssProcessorOptions: { safe: true, discardComments: { removeAll: true }, autoprefixer: false }
      }),
      new UglifyJsPlugin({})
    ])
  }

  let moduleIdType = options.moduleIdType
  let moduleIdMap = {
    hash: new webpack.HashedModuleIdsPlugin(),
    name: new webpack.NamedModulesPlugin(),
    chameleon: new ChameleonWebpackPlugin({openModuleHash: true, openChunkHash: true})
  }
  if (moduleIdType && moduleIdMap[moduleIdType]) {
    commonConfig.plugins.push(moduleIdMap[moduleIdType])
  }

  let subProject = cml.config.get().subProject;
  if (subProject && subProject.length > 0) {
    subProject.forEach(item => {
      let npmName = cmlUtils.isString(item) ? item : item.npmName;
      let packageJSON = JSON.parse(fs.readFileSync(path.resolve(cml.projectRoot, 'node_modules', npmName, 'package.json'), {encoding: 'utf-8'}));
      let cmlConfig = packageJSON.cml || {};
      let definePlugin = cmlConfig.definePlugin;
      if (definePlugin) {
        Object.keys(definePlugin).forEach(key => {
          definePlugin[key] = JSON.stringify(definePlugin[key])
        })
        commonConfig.plugins.push(new webpack.DefinePlugin(definePlugin))
      }
    })
  }

  return commonConfig;
}
<|endoftext|>


<commit_before>/*
 * r_highlight_rules.js
 *
 * Copyright (C) 2009-12 by RStudio, Inc.
 *
 * The Initial Developer of the Original Code is
 * Ajax.org B.V.
 * Portions created by the Initial Developer are Copyright (C) 2010
 * the Initial Developer. All Rights Reserved.
 *
 * Unless you have received this program directly from RStudio pursuant
 * to the terms of a commercial license agreement with RStudio, then
 * this program is licensed to you under the terms of version 3 of the
 * GNU Affero General Public License. This program is distributed WITHOUT
 * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
 * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
 * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
 *
 */

var $colorFunctionCalls = false;

define("mode/r_highlight_rules", function(require, exports, module)
{
   function include(/*...*/) {
      var result = new Array(arguments.length);
      for (var i = 0; i < arguments.length; i++) {
         result[i] = {include: arguments[i]};
      }
      return result;
   }

   var oop = require("ace/lib/oop");
   var lang = require("ace/lib/lang");
   var TextHighlightRules = require("ace/mode/text_highlight_rules")
         .TextHighlightRules;
   var TexHighlightRules = require("mode/tex_highlight_rules").TexHighlightRules;

   var RHighlightRules = function()
   {
      var keywords = lang.arrayToMap([
         "function", "if", "else", "in", "break", "next", "repeat", "for", "while"
      ]);

      var specialFunctions = lang.arrayToMap([
         "return", "switch", "try", "tryCatch", "stop",
         "warning", "require", "library", "attach", "detach",
         "source", "setMethod", "setGeneric", "setGroupGeneric",
         "setClass", "setRefClass", "R6Class", "UseMethod", "NextMethod"
      ]);

      var builtinConstants = lang.arrayToMap([
         "NULL", "NA", "TRUE", "FALSE", "T", "F", "Inf",
         "NaN", "NA_integer_", "NA_real_", "NA_character_",
         "NA_complex_"
      ]);

      var reIdentifier = "[a-zA-Z.][a-zA-Z0-9._]*";

      var rules = {};

      // Define rule sub-blocks that can be included to create
      // full rule states.
      rules["#comment"] = [
         {
            token : "comment.sectionhead",
            regex : "#+(?!').*(?:----|====|####)\\s*$",
            next  : "start"
         },
         {
            // Roxygen
            token : "comment",
            regex : "#+'",
            next  : "rd-start"
         },
         {
            token : "comment",
            regex : "#.*$",
            next  : "start"
         }
      ];

      rules["#string"] = [
         {
            token : "string", // single line
            regex : '["](?:(?:\\\\.)|(?:[^"\\\\]))*?["]',
            next  : "start"
         },
         {
            token : "string", // single line
            regex : "['](?:(?:\\\\.)|(?:[^'\\\\]))*?[']",
            next  : "start"
         },
         {
            token : "string", // multi line string start
            merge : true,
            regex : '["]',
            next : "qqstring"
         },
         {
            token : "string", // multi line string start
            merge : true,
            regex : "[']",
            next : "qstring"
         }
      ];

      rules["#number"] = [
         {
            token : "constant.numeric", // hex
            regex : "0[xX][0-9a-fA-F]+[Li]?\\b",
            merge : false,
            next  : "start"
         },
         {
            token : "constant.numeric", // number + integer
            regex : "\\d+(?:\\.\\d*)?(?:[eE][+\\-]?\\d*)?[iL]?\\b",
            merge : false,
            next  : "start"
         },
         {
            token : "constant.numeric", // number + integer with leading decimal
            regex : "\\.\\d+(?:[eE][+\\-]?\\d*)?[iL]?\\b",
            merge : false,
            next  : "start"
         }
      ];

      rules["#quoted-identifier"] = [
         {
            token : "identifier",
            regex : "`.*?`",
            merge : false,
            next  : "start"
         }
      ];

      rules["#identifier"] = [
         {
            token : function(value)
            {
               if (builtinConstants[value])
                  return "constant.language";
               else if (value.match(/^\.\.\d+$/))
                  return "variable.language";
               else
                  return "identifier";
            },
            regex : reIdentifier,
            next  : "start"
         }
      ];

      rules["#keyword-or-identifier"] = [
         {
            token : function(value)
            {
               if (builtinConstants[value])
                  return "constant.language";
               else if (keywords[value])
                  return "keyword";
               else if (value.match(/^\.\.\d+$/))
                  return "variable.language";
               else
                  return "identifier";
            },
            regex : reIdentifier,
            next  : "start"
         }
      ];

      rules["#package-access"] = [
         {
            token : function(value) {
               if ($colorFunctionCalls)
                  return "identifier.support.class";
               else
                  return "identifier";
            },
            regex : reIdentifier + "(?=\\s*::)",
            next  : "start"
         }
      ];

      rules["#function-call"] = [
         {
            token : function(value) {
               if ($colorFunctionCalls)
                  return "identifier.support.function";
               else
                  return "identifier";
            },
            regex : reIdentifier + "(?=\\s*\\()",
            next  : "start"
         }
      ];

      rules["#function-call-or-keyword"] = [
         {
            token : function(value) {
               if (specialFunctions[value] || keywords[value])
                  return "keyword";
               else if ($colorFunctionCalls)
                  return "identifier.support.function";
               else
                  return "identifier";
            },
            regex : reIdentifier + "(?=\\s*\\()",
            next  : "start"
         }
      ];

      rules["#operator"] = [
         {
            token : "keyword.operator",
            regex : "\\$|@",
            merge : false,
            next  : "after-dollar"
         },
         {
            token : "keyword.operator",
            regex : ":::|::|:=|%%|>=|<=|==|!=|\\->|<\\-|<<\\-|\\|\\||&&|=|\\+|\\-|\\*|/|\\^|>|<|!|&|\\||~|\\$|:|@",
            merge : false,
            next  : "start"
         },
         {
            token : "keyword.operator.infix", // infix operators
            regex : "%.*?%",
            merge : false,
            next  : "start"
         },
         {
            // Obviously these are neither keywords nor operators, but
            // labelling them as such was the easiest way to get them
            // to be colored distinctly from regular text
            token : "paren.keyword.operator",
            merge : false,
            regex : "[[({]",
            next  : "start"
         },
         {
            // Obviously these are neither keywords nor operators, but
            // labelling them as such was the easiest way to get them
            // to be colored distinctly from regular text
            token : "paren.keyword.operator",
            merge : false,
            regex : "[\\])}]",
            next  : "start"
         },
         {
            token : function(value) {
               return $colorFunctionCalls ?
                  "punctuation.keyword.operator" :
                  "punctuation";
            },
            regex : "[;]",
            merge : false,
            next  : "start"
         },
         {
            token : function(value) {
               return $colorFunctionCalls ?
                  "punctuation.keyword.operator" :
                  "punctuation";
            },
            regex : "[,]",
            merge : false,
            next  : "start"
         },
      ];

      rules["#text"] = [
         {
            token : "text",
            regex : "\\s+"
         }
      ];

      // Construct rules from previously defined blocks.
      rules["start"] = include(
         "#comment", "#string", "#number",
         "#package-access", "#quoted-identifier",
         "#function-call-or-keyword", "#keyword-or-identifier",
         "#operator", "#text"
      );

      rules["after-dollar"] = include(
         "#comment", "#string", "#number",
         "#quoted-identifier",
         "#function-call", "#keyword-or-identifier",
         "#operator", "#text"
      );

      rules["qqstring"] = [
         {
            token : "string",
            regex : '(?:(?:\\\\.)|(?:[^"\\\\]))*?"',
            next  : "start"
         },
         {
            token : "string",
            regex : '.+',
            merge : true
         }
      ];

      rules["qstring"] = [
         {
            token : "string",
            regex : "(?:(?:\\\\.)|(?:[^'\\\\]))*?'",
            next  : "start"
         },
         {
            token : "string",
            regex : '.+',
            merge : true
         }
      ];

      this.$rules = rules;

      // Embed 'Rd' comment rules, for Roxygen.
      var rdRules = new TexHighlightRules("comment").getRules();

      // Make all embedded TeX virtual-comment so they don't interfere with
      // auto-indent.
      for (var i = 0; i < rdRules["start"].length; i++) {
         rdRules["start"][i].token += ".virtual-comment";
      }

      this.addRules(rdRules, "rd-");
      this.$rules["rd-start"].unshift({
          token: "text",
          regex: "^",
          next: "start"
      });
      this.$rules["rd-start"].unshift({
         token : "keyword",
         regex : "@(?!@)[^ ]*"
      });
      this.$rules["rd-start"].unshift({
         token : "comment",
         regex : "@@"
      });
      this.$rules["rd-start"].push({
         token : "comment",
         regex : "[^%\\\\[({\\])}]+"
      });

      this.normalizeRules();
   };

   oop.inherits(RHighlightRules, TextHighlightRules);

   exports.RHighlightRules = RHighlightRules;
   exports.setHighlightRFunctionCalls = function(value) {
      $colorFunctionCalls = value;
   };

});
<commit_msg>fix gwt js compilation issue (trailing comma)<commit_after>/*
 * r_highlight_rules.js
 *
 * Copyright (C) 2009-12 by RStudio, Inc.
 *
 * The Initial Developer of the Original Code is
 * Ajax.org B.V.
 * Portions created by the Initial Developer are Copyright (C) 2010
 * the Initial Developer. All Rights Reserved.
 *
 * Unless you have received this program directly from RStudio pursuant
 * to the terms of a commercial license agreement with RStudio, then
 * this program is licensed to you under the terms of version 3 of the
 * GNU Affero General Public License. This program is distributed WITHOUT
 * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
 * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
 * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
 *
 */

var $colorFunctionCalls = false;

define("mode/r_highlight_rules", function(require, exports, module)
{
   function include(/*...*/) {
      var result = new Array(arguments.length);
      for (var i = 0; i < arguments.length; i++) {
         result[i] = {include: arguments[i]};
      }
      return result;
   }

   var oop = require("ace/lib/oop");
   var lang = require("ace/lib/lang");
   var TextHighlightRules = require("ace/mode/text_highlight_rules")
         .TextHighlightRules;
   var TexHighlightRules = require("mode/tex_highlight_rules").TexHighlightRules;

   var RHighlightRules = function()
   {
      var keywords = lang.arrayToMap([
         "function", "if", "else", "in", "break", "next", "repeat", "for", "while"
      ]);

      var specialFunctions = lang.arrayToMap([
         "return", "switch", "try", "tryCatch", "stop",
         "warning", "require", "library", "attach", "detach",
         "source", "setMethod", "setGeneric", "setGroupGeneric",
         "setClass", "setRefClass", "R6Class", "UseMethod", "NextMethod"
      ]);

      var builtinConstants = lang.arrayToMap([
         "NULL", "NA", "TRUE", "FALSE", "T", "F", "Inf",
         "NaN", "NA_integer_", "NA_real_", "NA_character_",
         "NA_complex_"
      ]);

      var reIdentifier = "[a-zA-Z.][a-zA-Z0-9._]*";

      var rules = {};

      // Define rule sub-blocks that can be included to create
      // full rule states.
      rules["#comment"] = [
         {
            token : "comment.sectionhead",
            regex : "#+(?!').*(?:----|====|####)\\s*$",
            next  : "start"
         },
         {
            // Roxygen
            token : "comment",
            regex : "#+'",
            next  : "rd-start"
         },
         {
            token : "comment",
            regex : "#.*$",
            next  : "start"
         }
      ];

      rules["#string"] = [
         {
            token : "string", // single line
            regex : '["](?:(?:\\\\.)|(?:[^"\\\\]))*?["]',
            next  : "start"
         },
         {
            token : "string", // single line
            regex : "['](?:(?:\\\\.)|(?:[^'\\\\]))*?[']",
            next  : "start"
         },
         {
            token : "string", // multi line string start
            merge : true,
            regex : '["]',
            next : "qqstring"
         },
         {
            token : "string", // multi line string start
            merge : true,
            regex : "[']",
            next : "qstring"
         }
      ];

      rules["#number"] = [
         {
            token : "constant.numeric", // hex
            regex : "0[xX][0-9a-fA-F]+[Li]?\\b",
            merge : false,
            next  : "start"
         },
         {
            token : "constant.numeric", // number + integer
            regex : "\\d+(?:\\.\\d*)?(?:[eE][+\\-]?\\d*)?[iL]?\\b",
            merge : false,
            next  : "start"
         },
         {
            token : "constant.numeric", // number + integer with leading decimal
            regex : "\\.\\d+(?:[eE][+\\-]?\\d*)?[iL]?\\b",
            merge : false,
            next  : "start"
         }
      ];

      rules["#quoted-identifier"] = [
         {
            token : "identifier",
            regex : "`.*?`",
            merge : false,
            next  : "start"
         }
      ];

      rules["#identifier"] = [
         {
            token : function(value)
            {
               if (builtinConstants[value])
                  return "constant.language";
               else if (value.match(/^\.\.\d+$/))
                  return "variable.language";
               else
                  return "identifier";
            },
            regex : reIdentifier,
            next  : "start"
         }
      ];

      rules["#keyword-or-identifier"] = [
         {
            token : function(value)
            {
               if (builtinConstants[value])
                  return "constant.language";
               else if (keywords[value])
                  return "keyword";
               else if (value.match(/^\.\.\d+$/))
                  return "variable.language";
               else
                  return "identifier";
            },
            regex : reIdentifier,
            next  : "start"
         }
      ];

      rules["#package-access"] = [
         {
            token : function(value) {
               if ($colorFunctionCalls)
                  return "identifier.support.class";
               else
                  return "identifier";
            },
            regex : reIdentifier + "(?=\\s*::)",
            next  : "start"
         }
      ];

      rules["#function-call"] = [
         {
            token : function(value) {
               if ($colorFunctionCalls)
                  return "identifier.support.function";
               else
                  return "identifier";
            },
            regex : reIdentifier + "(?=\\s*\\()",
            next  : "start"
         }
      ];

      rules["#function-call-or-keyword"] = [
         {
            token : function(value) {
               if (specialFunctions[value] || keywords[value])
                  return "keyword";
               else if ($colorFunctionCalls)
                  return "identifier.support.function";
               else
                  return "identifier";
            },
            regex : reIdentifier + "(?=\\s*\\()",
            next  : "start"
         }
      ];

      rules["#operator"] = [
         {
            token : "keyword.operator",
            regex : "\\$|@",
            merge : false,
            next  : "after-dollar"
         },
         {
            token : "keyword.operator",
            regex : ":::|::|:=|%%|>=|<=|==|!=|\\->|<\\-|<<\\-|\\|\\||&&|=|\\+|\\-|\\*|/|\\^|>|<|!|&|\\||~|\\$|:|@",
            merge : false,
            next  : "start"
         },
         {
            token : "keyword.operator.infix", // infix operators
            regex : "%.*?%",
            merge : false,
            next  : "start"
         },
         {
            // Obviously these are neither keywords nor operators, but
            // labelling them as such was the easiest way to get them
            // to be colored distinctly from regular text
            token : "paren.keyword.operator",
            merge : false,
            regex : "[[({]",
            next  : "start"
         },
         {
            // Obviously these are neither keywords nor operators, but
            // labelling them as such was the easiest way to get them
            // to be colored distinctly from regular text
            token : "paren.keyword.operator",
            merge : false,
            regex : "[\\])}]",
            next  : "start"
         },
         {
            token : function(value) {
               return $colorFunctionCalls ?
                  "punctuation.keyword.operator" :
                  "punctuation";
            },
            regex : "[;]",
            merge : false,
            next  : "start"
         },
         {
            token : function(value) {
               return $colorFunctionCalls ?
                  "punctuation.keyword.operator" :
                  "punctuation";
            },
            regex : "[,]",
            merge : false,
            next  : "start"
         }
      ];

      rules["#text"] = [
         {
            token : "text",
            regex : "\\s+"
         }
      ];

      // Construct rules from previously defined blocks.
      rules["start"] = include(
         "#comment", "#string", "#number",
         "#package-access", "#quoted-identifier",
         "#function-call-or-keyword", "#keyword-or-identifier",
         "#operator", "#text"
      );

      rules["after-dollar"] = include(
         "#comment", "#string", "#number",
         "#quoted-identifier",
         "#function-call", "#keyword-or-identifier",
         "#operator", "#text"
      );

      rules["qqstring"] = [
         {
            token : "string",
            regex : '(?:(?:\\\\.)|(?:[^"\\\\]))*?"',
            next  : "start"
         },
         {
            token : "string",
            regex : '.+',
            merge : true
         }
      ];

      rules["qstring"] = [
         {
            token : "string",
            regex : "(?:(?:\\\\.)|(?:[^'\\\\]))*?'",
            next  : "start"
         },
         {
            token : "string",
            regex : '.+',
            merge : true
         }
      ];

      this.$rules = rules;

      // Embed 'Rd' comment rules, for Roxygen.
      var rdRules = new TexHighlightRules("comment").getRules();

      // Make all embedded TeX virtual-comment so they don't interfere with
      // auto-indent.
      for (var i = 0; i < rdRules["start"].length; i++) {
         rdRules["start"][i].token += ".virtual-comment";
      }

      this.addRules(rdRules, "rd-");
      this.$rules["rd-start"].unshift({
          token: "text",
          regex: "^",
          next: "start"
      });
      this.$rules["rd-start"].unshift({
         token : "keyword",
         regex : "@(?!@)[^ ]*"
      });
      this.$rules["rd-start"].unshift({
         token : "comment",
         regex : "@@"
      });
      this.$rules["rd-start"].push({
         token : "comment",
         regex : "[^%\\\\[({\\])}]+"
      });

      this.normalizeRules();
   };

   oop.inherits(RHighlightRules, TextHighlightRules);

   exports.RHighlightRules = RHighlightRules;
   exports.setHighlightRFunctionCalls = function(value) {
      $colorFunctionCalls = value;
   };

});
<|endoftext|>