<commit_before>/*! ------------------------------------------------------------------------
//                                   Fusion
//  ------------------------------------------------------------------------
//
//                       Copyright 2014 Nicholas Gaulin
//
//       Licensed under the Apache License, Version 2.0 (the "License");
//      you may not use this file except in compliance with the License.
//                   You may obtain a copy of the License at
//
//                 http://www.apache.org/licenses/LICENSE-2.0
//
//     Unless required by applicable law or agreed to in writing, software
//      distributed under the License is distributed on an "AS IS" BASIS,
//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//     See the License for the specific language governing permissions and
//                       limitations under the License.
*/

// ########## HIGHLIGHTER ##########
var Highlighter   = function($source, $language)
{
    // Call the base constructor
    Lexer.apply(this, arguments);

    // If a language wasn't provided, return
    if (!$language)
        return;

    // Create the CSS scope chain
    this.styleChain = $language == 'fjs'
                   || $language == 'fhtml'
                   || $language == 'fcss' ?
                      [] :
                      null;
},
  __Highlighter__ = Highlighter.prototype = $__create(__Lexer__);

// --- HELPERS ---
var $_highlighter_clone = function($v, $i, $a)
{
    // Return a clone of the value
    return $v.clone();
};

// --- PROTOTYPE ---
__Highlighter__._clone     = Highlighter;
__Highlighter__._token     = null;
__Highlighter__.styleChain = null;

$_data(__Highlighter__, 'clone',  function()
{
    // Create a clone of the lexer as a highlighter
    var $highlighter = __Lexer__.clone.apply(this, arguments);

    // Copy the highlighter parameters
    $highlighter._token     = this._token ?
                              this._token.clone() :
                              null;
    $highlighter.styleChain = this.styleChain ?
                              this.styleChain.map($_highlighter_clone) :
                              null;

    return $highlighter;
}, true);
$_data(__Highlighter__, 'equals', function($highlighter)
{
    // If a highlighter wasn't provided, return false
    if (!($highlighter instanceof Highlighter))
        return false;

    // If the highlighters don't have matching lexers, return false
    if (!__Lexer__.equals.apply(this, arguments))
        return false;

    // Get the CSS scope chains
    var $chainHighlighter = $highlighter.styleChain,
        $chainThis        = this.styleChain;

    // If the highlighters don't have matching CSS scope chain lengths, return false
    if (($chainThis && $chainThis.length || 0) != ($chainHighlighter && $chainHighlighter.length || 0))
        return false;

    // If the highlighters don't have matching cached tokens, return false
    if (!this._token != !$highlighter._token || this._token && !this._token.equals($highlighter._token))
        return false;

    // If the highlighters have CSS scope chains, return false if they are not equal
    if ($chainThis)
        for (var $i = 0, $j = $chainThis.length; $i < $j; $i++)
            if (!$chainThis[$i].equals($chainHighlighter[$i]))
                return false;

    return true;
}, true);
$_data(__Highlighter__, 'hack',   function()
{
    // If a CSS scope chain already exists, return false
    if (this.styleChain)
        return false;

    // Hack the lexer
    __Lexer__.hack.apply(this, arguments);

    // Get the language
    var $language = this.language;

    // Create the CSS scope chain
    this.styleChain = $language == 'fjs'
                   || $language == 'fhtml'
                   || $language == 'fcss' ?
                      [] :
                      null;

    // Return true if a CSS scope chain was created
    return !!this.styleChain;
}, true);
$_data(__Highlighter__, 'next',   function()
{
    // Get the next token from the lexer
    var $previous = this.token,
        $token    = this._token || __Lexer__.next.apply(this, arguments),
        $chain    = this.styleChain;

    // If there's no token, return null
    if (!$token)
        return null;

    // Get the token type
    var $type = $token.type;

    // If the token opens a DOCTYPE
    if (!this._token && $type == 'HTMLDOCTYPEOpen')
    {
         // Create a cached copy of the token
        this._token = $token.clone();

        // Trim the first character from the cached token
        this._token.start++;
        this._token.type = 'HTMLDOCTYPEDeclaration';

        // Trim all but the first character from the token
        $token.end = $token.start + 1;
    }
    // If there's a cached token, remove it
    else if (this._token)
        this._token = null;

    // If the highlighter doesn't have a CSS scope chain, return the token
    if (!$chain)
        return $token;

    // If the token isn't a CSS token
    if (!$_startsWith($type, 'CSS'))
    {
        // If there's no previous token, the previous token was not a CSS token, or the current token is a fusion substitution opening token
        if (!$previous || !$_startsWith($previous.type, 'CSS') || $type == "FusionObjectSubstitutionOpen"
                                                               || $type == "FusionSelectorSubstitutionOpen"
                                                               || $type == "FusionStyleSubstitutionOpen")
            return $token;

        var $last = null;

        do
        {
            // If the CSS scope chain is empty, break
            if (!$chain.length)
                break;

            // Pop the last scope from the chain
            $last = $chain.pop();
        }
        // Continue if the removed scope wasn't a selector context
        while ($last && $last != '*');

        return $token;
    }

    // If the token is either whitespace or a comment, return it
    if ($_lexer_whitespace($type) || $_lexer_comment($type))
        return $token;

    // If there's no previous token or the previous token was neither a CSS token nor a fusion substitution closing token
    if (!$previous || !$_startsWith($previous.type, 'CSS') && $previous.type != 'FusionObjectSubstitutionClose'
                                                           && $previous.type != 'FusionSelectorSubstitutionClose'
                                                           && $previous.type != 'FusionStyleSubstitutionClose')
        // Push the selector context into the scope chain
        $chain.push('*');

    // If the previous token was a fusion object opening punctuator, push a qualified rule context into the scope chain
    if ($previous.type == 'FusionObjectOpen')
        $chain.push('*{');

    // Get the current context and CSS punctuator
    var $current    = $chain.length - 1,
        $scope      = $current >= 0 ?
                      $chain[$current] :
                      null,
        $punctuator = $type == 'CSSPunctuator' ?
                      $token.text() :
                      null;

    // If the current token is an opening brace
    if ($punctuator == '{')
    {
        // If the current context is an at-rule scope, set the scope as an at-rule block
        if ($scope == '@')
            $chain[$current] = $scope + '{';
        // Push a qualified rule context into the scope chain
        else
            $chain.push('*{');
    }
    // If the current token is a closing brace
    else if ($punctuator == '}')
    {
        // If the current context isn't a selector context, pop it from the scope chain
        if ($scope && $scope != '*')
            $chain.pop();
    }
    // If the current token is a semi-colon
    else if ($type == 'CSSSemicolon')
    {
        // If the current context is a declaration value, set the current context as a declaration name context
        if ($scope == '*{:' || $scope == '@{:')
            $chain[$current] = $scope[0] + '{';
        // If the current context is an at-rule without a block, pop it from the scope chain
        else if ($scope == '@')
            $chain.pop();
    }
    // If the current token is a colon and the current context is a declaration name, append the current character to the current context
    else if ($type == 'CSSColon' && ($scope == '*{' || $scope == '@{'))
        $chain[$current] = $scope + ':';
    else
    {
        // Get the next token if the current context is an at-rule declaration name
        var $peek = $scope == '@{' && $type == 'CSSIdentifier' ?
                    this.peek($token.end) :
                    null;

        // If the current token is an at-keyword and the current context can open an at-rule
        if ($type == 'CSSAtKeyword' && ($scope == '*'
                                     || $scope == '*{'
                                     || $scope == '@{'))
        {
            // Push an at-rule context into the scope chain
            $chain.push('@');

            // Set the token type
            $token.type = 'CSSAtRule';
        }
        // Set the token type from the tokens and current context
        else
            $token.type = $scope == '*{' && $type == 'CSSIdentifier' || $peek && $peek.type == 'CSSColon' ?
                          'CSSDeclarationName' :
                          $scope == '*{:' || $scope == '@{:' ?
                          'CSSDeclarationValue' :
                          $scope == '*' || $scope == '@{' ?
                          'CSSSelector' :
                          $scope == '@' ?
                          'CSSAtRule' :
                          'CSSInvalidCharacters';

        // If the current context is a declaration value context
        if ($scope == '*{:' || $scope == '@{:')
        {
            // If the current token is the `!` delimiter
            if ($type == 'CSSDelimiter' && $token.text() == '!')
            {
                // Get the next token
                $peek = this.peek($token.end);

                // If the next token is the `important` identifier
                if ($peek && $peek.type == 'CSSIdentifier' && $peek.text().toLowerCase() == 'important')
                {
                    // Get the next token
                    $peek = this.peek($peek.end);

                    // If the next token is either a semi-colon or closing brace, set the important declaration type
                    if ($peek && ($peek.type == 'CSSSemicolon' || $peek.type == 'CSSPunctuator' && $peek.text() == '}'))
                        $token.type = 'CSSDeclarationImportant';
                }
            }
            // If the previous token was the `!` delimiter and the current token is the `important` identifier
            else if ($previous && $previous.type == 'CSSDelimiter' && $previous.text() == '!' && $type == 'CSSIdentifier' && $token.text().toLowerCase() == 'important')
            {
                // Get the next token
                $peek = this.peek($token.end);

                // If the next token is either a semi-colon or closing brace, set the important declaration type
                if ($peek && ($peek.type == 'CSSSemicolon' || $peek.type == 'CSSPunctuator' && $peek.text() == '}'))
                    $token.type = 'CSSDeclarationImportant';
            }
        }
    }

    return $token;
}, true);
$_data(__Highlighter__, 'reset',  function()
{
    // Reset the lexer
    __Lexer__.reset.apply(this, arguments);

    // Reset the CSS scope chain and cached token
    this._token     = null;
    this.styleChain = null;
}, true);<commit_msg>update Highlighter.js<commit_after>/*! ------------------------------------------------------------------------
//                                   Fusion
//  ------------------------------------------------------------------------
//
//                       Copyright 2014 Nicholas Gaulin
//
//       Licensed under the Apache License, Version 2.0 (the "License");
//      you may not use this file except in compliance with the License.
//                   You may obtain a copy of the License at
//
//                 http://www.apache.org/licenses/LICENSE-2.0
//
//     Unless required by applicable law or agreed to in writing, software
//      distributed under the License is distributed on an "AS IS" BASIS,
//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//     See the License for the specific language governing permissions and
//                       limitations under the License.
*/

// ########## HIGHLIGHTER ##########
var Highlighter   = function($source, $language)
{
    // Call the base constructor
    Lexer.apply(this, arguments);

    // If a language wasn't provided, return
    if (!$language)
        return;

    // Create the CSS scope chain
    this.styleChain = $language == 'fjs'
                   || $language == 'fhtml'
                   || $language == 'fcss' ?
                      [] :
                      null;
},
  __Highlighter__ = Highlighter.prototype = $__create(__Lexer__);

// --- HELPERS ---
var $_highlighter_clone = function($v, $i, $a)
{
    // Return a clone of the value
    return $v.clone();
};

// --- PROTOTYPE ---
__Highlighter__._clone     = Highlighter;
__Highlighter__._token     = null;
__Highlighter__.styleChain = null;

$_data(__Highlighter__, 'clone',  function()
{
    // Create a clone of the lexer as a highlighter
    var $highlighter = __Lexer__.clone.apply(this, arguments);

    // Copy the highlighter parameters
    $highlighter._token     = this._token ?
                              this._token.clone() :
                              null;
    $highlighter.styleChain = this.styleChain ?
                              this.styleChain.map($_highlighter_clone) :
                              null;

    return $highlighter;
}, true);
$_data(__Highlighter__, 'equals', function($highlighter)
{
    // If a highlighter wasn't provided, return false
    if (!($highlighter instanceof Highlighter))
        return false;

    // If the highlighters don't have matching lexers, return false
    if (!__Lexer__.equals.apply(this, arguments))
        return false;

    // Get the CSS scope chains
    var $chainHighlighter = $highlighter.styleChain,
        $chainThis        = this.styleChain;

    // If the highlighters don't have matching CSS scope chain lengths, return false
    if (($chainThis && $chainThis.length || 0) != ($chainHighlighter && $chainHighlighter.length || 0))
        return false;

    // If the highlighters don't have matching cached tokens, return false
    if (!this._token != !$highlighter._token || this._token && !this._token.equals($highlighter._token))
        return false;

    // If the highlighters have CSS scope chains, return false if they are not equal
    if ($chainThis)
        for (var $i = 0, $j = $chainThis.length; $i < $j; $i++)
            if (!$chainThis[$i].equals($chainHighlighter[$i]))
                return false;

    return true;
}, true);
$_data(__Highlighter__, 'hack',   function()
{
    // If a CSS scope chain already exists, return false
    if (this.styleChain)
        return false;

    // Hack the lexer
    __Lexer__.hack.apply(this, arguments);

    // Get the language
    var $language = this.language;

    // Create the CSS scope chain
    this.styleChain = $language == 'fjs'
                   || $language == 'html'
                   || $language == 'fhtml'
                   || $language == 'css'
                   || $language == 'fcss' ?
                      [] :
                      null;

    // Return true if a CSS scope chain was created
    return !!this.styleChain;
}, true);
$_data(__Highlighter__, 'next',   function()
{
    // Get the next token from the lexer
    var $previous = this.token,
        $token    = this._token || __Lexer__.next.apply(this, arguments),
        $chain    = this.styleChain;

    // If there's no token, return null
    if (!$token)
        return null;

    // Get the token type
    var $type = $token.type;

    // If the token opens a DOCTYPE
    if (!this._token && $type == 'HTMLDOCTYPEOpen')
    {
         // Create a cached copy of the token
        this._token = $token.clone();

        // Trim the first character from the cached token
        this._token.start++;
        this._token.type = 'HTMLDOCTYPEDeclaration';

        // Trim all but the first character from the token
        $token.end = $token.start + 1;
    }
    // If there's a cached token, remove it
    else if (this._token)
        this._token = null;

    // If the highlighter doesn't have a CSS scope chain, return the token
    if (!$chain)
        return $token;

    // If the token isn't a CSS token
    if (!$_startsWith($type, 'CSS'))
    {
        // If there's no previous token, the previous token was not a CSS token, or the current token is a fusion substitution opening token
        if (!$previous || !$_startsWith($previous.type, 'CSS') || $type == "FusionObjectSubstitutionOpen"
                                                               || $type == "FusionSelectorSubstitutionOpen"
                                                               || $type == "FusionStyleSubstitutionOpen")
            return $token;

        var $last = null;

        do
        {
            // If the CSS scope chain is empty, break
            if (!$chain.length)
                break;

            // Pop the last scope from the chain
            $last = $chain.pop();
        }
        // Continue if the removed scope wasn't a selector context
        while ($last && $last != '*');

        return $token;
    }

    // If the token is either whitespace or a comment, return it
    if ($_lexer_whitespace($type) || $_lexer_comment($type))
        return $token;

    // If there's no previous token or the previous token was neither a CSS token nor a fusion substitution closing token
    if (!$previous || !$_startsWith($previous.type, 'CSS') && $previous.type != 'FusionObjectSubstitutionClose'
                                                           && $previous.type != 'FusionSelectorSubstitutionClose'
                                                           && $previous.type != 'FusionStyleSubstitutionClose')
        // Push the selector context into the scope chain
        $chain.push('*');

    // If the previous token was a fusion object opening punctuator, push a qualified rule context into the scope chain
    if ($previous && $previous.type == 'FusionObjectOpen')
        $chain.push('*{');

    // Get the current context and CSS punctuator
    var $current    = $chain.length - 1,
        $scope      = $current >= 0 ?
                      $chain[$current] :
                      null,
        $punctuator = $type == 'CSSPunctuator' ?
                      $token.text() :
                      null;

    // If the current token is an opening brace
    if ($punctuator == '{')
    {
        // If the current context is an at-rule scope, set the scope as an at-rule block
        if ($scope == '@')
            $chain[$current] = $scope + '{';
        // Push a qualified rule context into the scope chain
        else
            $chain.push('*{');
    }
    // If the current token is a closing brace
    else if ($punctuator == '}')
    {
        // If the current context isn't a selector context, pop it from the scope chain
        if ($scope && $scope != '*')
            $chain.pop();
    }
    // If the current token is a semi-colon
    else if ($type == 'CSSSemicolon')
    {
        // If the current context is a declaration value, set the current context as a declaration name context
        if ($scope == '*{:' || $scope == '@{:')
            $chain[$current] = $scope[0] + '{';
        // If the current context is an at-rule without a block, pop it from the scope chain
        else if ($scope == '@')
            $chain.pop();
    }
    // If the current token is a colon and the current context is a declaration name, append the current character to the current context
    else if ($type == 'CSSColon' && ($scope == '*{' || $scope == '@{'))
        $chain[$current] = $scope + ':';
    else
    {
        // Get the next token if the current context is an at-rule declaration name
        var $peek = $scope == '@{' && $type == 'CSSIdentifier' ?
                    this.peek($token.end) :
                    null;

        // If the current token is an at-keyword and the current context can open an at-rule
        if ($type == 'CSSAtKeyword' && ($scope == '*'
                                     || $scope == '*{'
                                     || $scope == '@{'))
        {
            // Push an at-rule context into the scope chain
            $chain.push('@');

            // Set the token type
            $token.type = 'CSSAtRule';
        }
        // Set the token type from the tokens and current context
        else
            $token.type = $scope == '*{' && $type == 'CSSIdentifier' || $peek && $peek.type == 'CSSColon' ?
                          'CSSDeclarationName' :
                          $scope == '*{:' || $scope == '@{:' ?
                          'CSSDeclarationValue' :
                          $scope == '*' || $scope == '@{' ?
                          'CSSSelector' :
                          $scope == '@' ?
                          'CSSAtRule' :
                          'CSSInvalidCharacters';

        // If the current context is a declaration value context
        if ($scope == '*{:' || $scope == '@{:')
        {
            // If the current token is the `!` delimiter
            if ($type == 'CSSDelimiter' && $token.text() == '!')
            {
                // Get the next token
                $peek = this.peek($token.end);

                // If the next token is the `important` identifier
                if ($peek && $peek.type == 'CSSIdentifier' && $peek.text().toLowerCase() == 'important')
                {
                    // Get the next token
                    $peek = this.peek($peek.end);

                    // If the next token is either a semi-colon or closing brace, set the important declaration type
                    if ($peek && ($peek.type == 'CSSSemicolon' || $peek.type == 'CSSPunctuator' && $peek.text() == '}'))
                        $token.type = 'CSSDeclarationImportant';
                }
            }
            // If the previous token was the `!` delimiter and the current token is the `important` identifier
            else if ($previous && $previous.type == 'CSSDelimiter' && $previous.text() == '!' && $type == 'CSSIdentifier' && $token.text().toLowerCase() == 'important')
            {
                // Get the next token
                $peek = this.peek($token.end);

                // If the next token is either a semi-colon or closing brace, set the important declaration type
                if ($peek && ($peek.type == 'CSSSemicolon' || $peek.type == 'CSSPunctuator' && $peek.text() == '}'))
                    $token.type = 'CSSDeclarationImportant';
            }
        }
    }

    return $token;
}, true);
$_data(__Highlighter__, 'reset',  function()
{
    // Reset the lexer
    __Lexer__.reset.apply(this, arguments);

    // Reset the CSS scope chain and cached token
    this._token     = null;
    this.styleChain = null;
}, true);<|endoftext|>


<commit_before>import parseProps from '../helpers/parseProps';
import parseFunctional from '../helpers/parseFunctional';
import { parseVariables } from '../features/variables';
import { logSubscription } from '../helpers/logger';

import { runMixin, getMixin } from '../features/mixins';
import { parseKeyValue, parseRawCss, parseRawInline } from '../features/plugins';

import testCondition from '../utils/testCondition';
import printToCss from '../utils/printToCss';

export default class StyleParser {
  constructor(subscription) {
    this.sub = subscription;
  }

  run() {
    const startTime = new Date();
    let rawCss = this.sub.printedCss;
    const { options } = this.sub;
    if(options.styles && !options.dontParse) {
      this.printStyleArray = [];
      this.runningQueue = [...options.styles];
      this.nextQueue = [];

      this.sub.inlineStyles = {};

      this.sub.touched = {
        mixins: {},
        variables: {},
      }
      this.runQueue();
      if(this.printStyleArray.length) {
        this.replacePropsAndVarForSelectors(this.printStyleArray);
        rawCss = printToCss(this.printStyleArray);
      }
    }

    // Run post plugins. (parseRawInline, parseRawCss)
    if(options.inline) {
      this.sub.inlineStyles = parseRawInline(this.sub.inlineStyles, this.sub.props);
    } else {
      this.sub.printedCss = parseRawCss(rawCss || '', this.sub.props);
    }

    if(options.debug) {
      // Hack to make sure that render has been called and we know which props was forwarded and which was not.
      setTimeout(() => {
        logSubscription(this.sub, startTime);
      }, 1);
    }

  }
  runQueue() {
    while (this.runningQueue.length) {
      const node = this.runningQueue.shift();
      const { props, touched } = this.sub;
      switch(node.type) {
        case 'mixin': {
          // On mixins, inject on current queue, to keep hierachy
          const mixinValue = runMixin(node, props, touched);
          touched.mixins[node.key] = true;
          if(Array.isArray(mixinValue)) {
            this.runningQueue = mixinValue.concat(this.runningQueue);
          }
          break;
        }
        case 'array': {
          // On array, inject on current queue, to keep hierachy
          this.runningQueue = node.value.concat(this.runningQueue);
          break;
        }
        case 'nested': {
          // Only parse the children if condition is met
          if(!node.condition || testCondition(node.condition, props)) {
            this.nextQueue = this.nextQueue.concat(node.value);
          }
          break;
        }
        case 'node': {
          this.handleNode(node);
          break;
        }
      }
    }
    if(this.nextQueue.length) {
      this.runningQueue = this.nextQueue;
      this.nextQueue = [];
      this.runQueue();
    }
  }
  replacePropsAndVarForSelectors(array) {
    const { props, className, touched } = this.sub;
    array.forEach((obj) => {
      obj.selector = parseProps(obj.selector, props);
      obj.selector = parseVariables(obj.selector, touched.variables);
      // TODO: support comma separated stuff.
      obj.selector = className.split(/,\ ?/g).map(s => obj.selector.replace(/&/gi, s)).join(', ');
      if(obj.children.length) {
        this.replacePropsAndVarForSelectors(obj.children);
      }
    });
  }
  findTargetFromSelectors(selectors) {
    // Support iterative structere with children
    let dRes = { children: this.printStyleArray };
    selectors.forEach((selector) => {
      let index = dRes.children.findIndex(o => o.selector === selector);
      if(index === -1) {
        index = dRes.children.push({
          selector,
          children: [],
          styles: [],
        }) - 1;
      }
      dRes = dRes.children[index];
    })
    return dRes;
  }
  handleNode(node) {
    let key = node.key;
    let value = node.value;

    const { props, touched, options, inlineStyles } = this.sub;

    value = parseFunctional(value, props);
    value = parseProps(value, props);
    value = parseVariables(value, touched.variables);

    const res = parseKeyValue(key, value, props);
    key = res[0];
    value = res[1];

    // value is nothing, but accept 0
    if(typeof value !== 'number' && !value) return;

    // If inline, just override the values.
    if(options.inline) {
      inlineStyles[key] = value;
    } else {
      // Else figure out order and stuff
      const target = this.findTargetFromSelectors(node.selectors);

      const sIndex = target.styles.findIndex(o => o.key === key);
      if(sIndex > -1 && !node.append) {
        target.styles.splice(sIndex, 1);
      }

      target.styles.push({
        key,
        value,
      });
    }
  }
}<commit_msg>fixed parsing order<commit_after>import parseProps from '../helpers/parseProps';
import parseFunctional from '../helpers/parseFunctional';
import { parseVariables } from '../features/variables';
import { logSubscription } from '../helpers/logger';

import { runMixin, getMixin } from '../features/mixins';
import { parseKeyValue, parseRawCss, parseRawInline } from '../features/plugins';

import testCondition from '../utils/testCondition';
import printToCss from '../utils/printToCss';

export default class StyleParser {
  constructor(subscription) {
    this.sub = subscription;
  }

  run() {
    const startTime = new Date();
    let rawCss = this.sub.printedCss;
    const { options } = this.sub;
    if(options.styles && !options.dontParse) {
      this.printStyleArray = [];
      this.runningQueue = [...options.styles];
      this.nextQueue = [];

      this.sub.inlineStyles = {};

      this.sub.touched = {
        mixins: {},
        variables: {},
      }
      this.runQueue();
      if(this.printStyleArray.length) {
        this.replacePropsAndVarForSelectors(this.printStyleArray);
        rawCss = printToCss(this.printStyleArray);
      }
    }

    // Run post plugins. (parseRawInline, parseRawCss)
    if(options.inline) {
      this.sub.inlineStyles = parseRawInline(this.sub.inlineStyles, this.sub.props);
    } else {
      this.sub.printedCss = parseRawCss(rawCss || '', this.sub.props);
    }

    if(options.debug) {
      // Hack to make sure that render has been called and we know which props was forwarded and which was not.
      setTimeout(() => {
        logSubscription(this.sub, startTime);
      }, 1);
    }

  }
  runQueue() {
    while (this.runningQueue.length) {
      const node = this.runningQueue.shift();
      const { props, touched } = this.sub;
      switch(node.type) {
        case 'mixin': {
          // On mixins, inject on current queue, to keep hierachy
          const mixinValue = runMixin(node, props, touched);
          if(Array.isArray(mixinValue)) {
            this.runningQueue = mixinValue.concat(this.runningQueue);
          }
          break;
        }
        case 'array': {
          // On array, inject on current queue, to keep hierachy
          this.runningQueue = node.value.concat(this.runningQueue);
          break;
        }
        case 'nested': {
          // Only parse the children if condition is met
          if(!node.condition || testCondition(node.condition, props)) {

            if(node.condition) {
              node.value.forEach((n) => {
                n.selectors[n.selectors.length - 1] += `, .sw_${node.condition.key}`;
              })
            }
            this.nextQueue = this.nextQueue.concat(node.value);
          }
          break;
        }
        case 'node': {
          this.handleNode(node);
          break;
        }
      }
    }
    if(this.nextQueue.length) {
      this.runningQueue = this.nextQueue;
      this.nextQueue = [];
      this.runQueue();
    }
  }
  replacePropsAndVarForSelectors(array) {
    const { props, className, touched } = this.sub;
    array.forEach((obj) => {
      obj.selector = parseProps(obj.selector, props);
      obj.selector = parseVariables(obj.selector, touched.variables);
      // TODO: support comma separated stuff.
      obj.selector = className.split(/,\ ?/g).map(s => obj.selector.replace(/&/gi, s)).join(', ');
      if(obj.children.length) {
        this.replacePropsAndVarForSelectors(obj.children);
      }
    });
  }
  findTargetFromSelectors(selectors) {
    // Support iterative structere with children
    let dRes = { children: this.printStyleArray };
    selectors.forEach((selector) => {
      let index = 0;
      const length = dRes.children.length;
      if(!length || dRes.children[length - 1].selector !== selector) {
        index = dRes.children.push({
          selector,
          children: [],
          styles: [],
        }) - 1;
      }
      dRes = dRes.children[index];
    })
    return dRes;
  }
  handleNode(node) {
    let key = node.key;
    let value = node.value;

    const { props, touched, options, inlineStyles } = this.sub;

    value = parseFunctional(value, props);
    value = parseProps(value, props);
    value = parseVariables(value, touched.variables);

    const res = parseKeyValue(key, value, props);
    key = res[0];
    value = res[1];

    // value is nothing, but accept 0
    if(typeof value !== 'number' && !value) return;

    // If inline, just override the values.
    if(options.inline) {
      inlineStyles[key] = value;
    } else {
      // Else figure out order and stuff
      const target = this.findTargetFromSelectors(node.selectors);

      const sIndex = target.styles.findIndex(o => o.key === key);
      if(sIndex > -1 && !node.append) {
        target.styles.splice(sIndex, 1);
      }

      target.styles.push({
        key,
        value,
      });

    }
  }
}<|endoftext|>


<commit_before>import React from 'react'
import PropTypes from 'prop-types'
import { connect } from 'react-redux'
import { push } from 'react-router-redux'

export default function requireAuthentication(Component) {
  class AuthenticatedComponent extends React.Component {
    static propTypes = {
      isAuthenticated: PropTypes.bool.isRequired,
      location: PropTypes.object.isRequired,
      dispatch: PropTypes.func.isRequired,
    }

    componentWillMount() {
      this.checkAuth()
    }

    componentWillReceiveProps(nextProps) {
      this.checkAuth()
    }

    checkAuth() {
      if (!this.props.isAuthenticated) {
        const redirectAfterLogin = this.props.location.pathname
        this.props.dispatch(push(`/login?next=${redirectAfterLogin}`))
      }
    }

    render() {
      return (
        <div>
          {this.props.isAuthenticated === true
            ? <Component {...this.props} />
            : null
          }
        </div>
      )
    }
  }

  const mapStateToProps = (state) => {
    return {
      isAuthenticated: state.auth.isAuthenticated,
      token: state.auth.token,
    }
  }

  return connect(mapStateToProps)(AuthenticatedComponent)
}
<commit_msg>cleanup requireAuthentication HOC<commit_after>import React from 'react'
import { connect } from 'react-redux'
import { bindActionCreators } from 'redux'
import { push } from 'react-router-redux'

export default function requireAuthentication(Component) {
  class AuthenticatedComponent extends React.Component {
    componentWillMount() {
      this.checkAuth(this.props)
    }

    componentWillReceiveProps(nextProps) {
      this.checkAuth(nextProps)
    }

    checkAuth(props) {
      const { isAuthenticated, location, push } = props
      if (!isAuthenticated) {
        push(`/login?next=${location.pathname}`)
      }
    }

    render() {
      const { isAuthenticated, location, push, ...props } = this.props
      return isAuthenticated
        ? <Component {...props} />
        : null
    }
  }

  return connect(
    (state) => ({ isAuthenticated: state.auth.isAuthenticated }),
    (dispatch) => bindActionCreators({ push }, dispatch),
  )(AuthenticatedComponent)
}
<|endoftext|>


<commit_before>/*
Copyright (c) 2014, EDINA.
All rights reserved.

Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:

1. Redistributions of source code must retain the above copyright notice, this
   list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice, this
   list of conditions and the following disclaimer in the documentation and/or
   other materials provided with the distribution.
3. All advertising materials mentioning features or use of this software must
   display the following acknowledgement: This product includes software
   developed by the EDINA.
4. Neither the name of the EDINA nor the names of its contributors may be used to
   endorse or promote products derived from this software without specific prior
   written permission.

THIS SOFTWARE IS PROVIDED BY EDINA ''AS IS'' AND ANY EXPRESS OR IMPLIED
WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
SHALL EDINA BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
DAMAGE.
*/

"use strict";

/* global asyncTest, expect, ok, start, stop, test */

define(['map', 'ui', 'utils', './cache'], function(// jshint ignore:line
    map, ui, utils, cache) {

return {

unit: {
    run: function() {
        module("Offline Maps");

        if(utils.isMobileDevice() || utils.isChrome()){
            asyncTest("Delete Map", function(){
                var MAP_NAME = "name-name";
                expect( 2 );
                var success = true;
                console.log("*" + MAP_NAME);
                cache.deleteSavedMapDetails(MAP_NAME);
                //stop();
                setTimeout(function(){
                    ok(success, "Database Map Deleted");
                    //check local storage
                    var maps = cache.getSavedMaps() || {};
                    ok(!(maps[MAP_NAME]), "Map should not be in localStorage" );
                    console.log("*");
                    start();
                }, 2000);
            });
        }

        if(utils.isMobileDevice()){
            test("Download North edinburgh test", function() {
                var MAP_NAME = "name-name";
                expect(3);
                var bounds = new OpenLayers.Bounds(
                    322499.99340785,
                    676321.01823658,
                    323715.99340785,
                    677925.01823658
                );

                //first map is container
                map.zoomToExtent(bounds);

                cache.saveMap(MAP_NAME, 8, 9);

                stop();
                setTimeout(function(){
                    var url ='';
                    var tileExists = false;

                    function fileExists(fileEntry){
                        console.log("File " + fileEntry.fullPath + " exists!");
                        tileExists = true;
                    }
                    function fileDoesNotExist(){
                        console.log("file does not exist");
                    }
                    function getFSFail(evt) {
                        console.log(evt.target.error.code);
                    }

                    function checkIfFileExists(path){
                        window.requestFileSystem(
                            LocalFileSystem.PERSISTENT,
                            0,
                            function(fileSystem){
                                fileSystem.root.getFile(path, { create: false }, fileExists, fileDoesNotExist);
                            },
                            getFSFail); //of requestFileSystem
                    }

                    var callback = function(fileLocation){
                        url = fileLocation;

                        console.log(fileLocation);
                        checkIfFileExists(fileLocation);
                    };

                    webdb.getCachedTilePath(callback, null, 316,661, 8, 'dummyUrl');

                    setTimeout( function() {
                        var maps = ui.cache.getSavedMaps();
                        ok((maps[MAP_NAME]), "Map should  be in localStorage" );
                        ok(url.indexOf('edina/cache/name-name/1/open_8_316_661.jpg') !== -1, 'Tile Stored in correct directory');
                        ok(tileExists === true, "Actual tile stored in phone directory");

                        start();
                    }, 2000);
                }, 2000);
            });
        }
    }
},
sys:{
    run: function() {
        module("Offline Maps");
    }
}

};

});
<commit_msg>Issue edina/fieldtrip-gb#8 add save map sys test<commit_after>/*
Copyright (c) 2014, EDINA.
All rights reserved.

Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:

1. Redistributions of source code must retain the above copyright notice, this
   list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice, this
   list of conditions and the following disclaimer in the documentation and/or
   other materials provided with the distribution.
3. All advertising materials mentioning features or use of this software must
   display the following acknowledgement: This product includes software
   developed by the EDINA.
4. Neither the name of the EDINA nor the names of its contributors may be used to
   endorse or promote products derived from this software without specific prior
   written permission.

THIS SOFTWARE IS PROVIDED BY EDINA ''AS IS'' AND ANY EXPRESS OR IMPLIED
WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
SHALL EDINA BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
DAMAGE.
*/

"use strict";

/* global asyncTest, expect, ok, start, stop, test */

define(['map', 'ui', 'utils', './cache', 'tests/systests'], function(// jshint ignore:line
    map, ui, utils, cache, sys) {

return {

unit: {
    run: function() {
        module("Offline Maps");

        if(utils.isMobileDevice() || utils.isChrome()){
            asyncTest("Delete Map", function(){
                var MAP_NAME = "name-name";
                expect( 2 );
                var success = true;
                console.log("*" + MAP_NAME);
                cache.deleteSavedMapDetails(MAP_NAME);
                //stop();
                setTimeout(function(){
                    ok(success, "Database Map Deleted");
                    //check local storage
                    var maps = cache.getSavedMaps() || {};
                    ok(!(maps[MAP_NAME]), "Map should not be in localStorage" );
                    console.log("*");
                    start();
                }, 2000);
            });
        }

        if(utils.isMobileDevice()){
            test("Download North edinburgh test", function() {
                var MAP_NAME = "name-name";
                expect(3);
                var bounds = new OpenLayers.Bounds(
                    322499.99340785,
                    676321.01823658,
                    323715.99340785,
                    677925.01823658
                );

                //first map is container
                map.zoomToExtent(bounds);

                cache.saveMap(MAP_NAME, 8, 9);

                stop();
                setTimeout(function(){
                    var url ='';
                    var tileExists = false;

                    function fileExists(fileEntry){
                        console.log("File " + fileEntry.fullPath + " exists!");
                        tileExists = true;
                    }
                    function fileDoesNotExist(){
                        console.log("file does not exist");
                    }
                    function getFSFail(evt) {
                        console.log(evt.target.error.code);
                    }

                    function checkIfFileExists(path){
                        window.requestFileSystem(
                            LocalFileSystem.PERSISTENT,
                            0,
                            function(fileSystem){
                                fileSystem.root.getFile(path, { create: false }, fileExists, fileDoesNotExist);
                            },
                            getFSFail); //of requestFileSystem
                    }

                    var callback = function(fileLocation){
                        url = fileLocation;

                        console.log(fileLocation);
                        checkIfFileExists(fileLocation);
                    };

                    webdb.getCachedTilePath(callback, null, 316,661, 8, 'dummyUrl');

                    setTimeout( function() {
                        var maps = ui.cache.getSavedMaps();
                        ok((maps[MAP_NAME]), "Map should  be in localStorage" );
                        ok(url.indexOf('edina/cache/name-name/1/open_8_316_661.jpg') !== -1, 'Tile Stored in correct directory');
                        ok(tileExists === true, "Actual tile stored in phone directory");

                        start();
                    }, 2000);
                }, 2000);
            });
        }
    }
},
sys:{
    run: function() {
        module("Offline Maps");

        sys.asyncTest("Save Map", function(){
            if(utils.isMobileDevice() || utils.isChrome()){
                var savedMaps = cache.getSavedMaps();

                if(savedMaps !== undefined){
                    if(cache.getSavedMapsCount() === 3){
                        // delete one of the maps
                        $.each(cache.getSavedMaps(), function(name, map){
                            console.debug("delete " + name);
                            cache.deleteSavedMapDetails(name);
                            return;
                        });
                    }
                }
                var mapCount = cache.getSavedMapsCount();

                sys.changePageByFile('save-map.html', '#save-map', function(){
                    ok(true, 'navigate to save map page');

                    sys.clickAndTest({
                        'id': '#save-map-buttons-ok',
                        'test': function(){
                            return $('#cache-controls').is(':visible');
                        },
                        'cb': function(success){
                            ok(success, 'Save button');
                            sys.clickAndTest({
                                'id': '#cache-save-details-button-div a',
                                'test': function(){
                                    return $.mobile.activePage[0].id === 'save-map-name-dialog';
                                },
                                'cb': function(success){
                                    ok(success, 'Map name dialog');
                                    sys.clickAndTest({
                                        'id': '#saved-map-name-dialog-btn',
                                        'delay': 2500,
                                        'poll': 1000,
                                        'test': function(){
                                            // test has passed when map count has been incremented
                                            return (mapCount + 1)  === cache.getSavedMapsCount();
                                        },
                                        'cb': function(success){
                                            ok(success, 'Save Map');
                                            start();
                                        }
                                    });
                                }
                            });
                        }
                    });
                });
            }
        });
    }
}

};

});
<|endoftext|>


<commit_before>//
//remember that
//	value is always not-array (may be object)
//
const pseudo = {

	"empty" : function(value){
		if (typeof value !== 'object') return value;
	}

}

module.exports = { pseudo };
<commit_msg>refactoring<commit_after>//
//remember that
//	1. arrValue is always array
//	2. pseudo should return array
//
const pseudo = {

	"empty" : function(arrValue){
		return arrValue.filter(_itm => {
			return (typeof _itm !== 'object')
		})
	}

}

module.exports = { pseudo };
<|endoftext|>


<commit_before>'use strict';

var Playlist = require('../models/Playlist.js');
var bodyparser = require('body-parser');
var eatAuth = require('../lib/eat_auth.js')(process.env.APP_SECRET);
var Song = require('../models/Song');

var http = require('http');

//all these routes will be under /api

module.exports = function(router) {
  router.use(bodyparser.json());

  //Finds all playlists with a certain word (or words) in its name
  //send POST to /api/playlist/search
  //http headers: searchString=searchstring, eat=token
  router.post('/playlist/search', eatAuth, function(req, res) {
    //basic regular expression: true if playlist name contains our string
    Playlist.find({name: new RegExp(req.body.searchString, 'i')},
    function(err, data) {
      if (err) {
        console.log(err);
        return res.status(500).json({msg: 'internal server error'});
      }
      res.json(data);
    });
  });

  //Gets all of a user's playlists
  //send POST to /api/playlist/mine
  //message body: {eat: token}
  router.post('/playlist/mine', eatAuth, function(req, res) {
    Playlist.find({createdBy: req.user._id}, function(err, data) {
      if (err) {
        console.log(err);
        return res.status(500).json({msg: 'internal server error'});
      }
      res.json(data);
    });
  });

  //Gets all playlists a user is contributing to
  //send POST to /api/playlist/contrib
  //message body: {eat: token}
  router.post('/playlist/contrib', eatAuth, function(req, res) {
    Playlist.find({'collaborators.$': req.user._id}, function(err, data) {
      if (err) {
        console.log(err);
        return res.status(500).json({msg: 'internal server error'});
      }
      res.json(data);
    });
  });

  //Create playlist
  //send POST to /api/create_playlist/
  //message body: {name: "playlistname", eat: token}
  router.post('/create_playlist', eatAuth, function(req, res) {
    var newPlaylist = new Playlist(req.body);
    newPlaylist.dateCreated = (new Date()).toDateString();
    newPlaylist.createdBy = req.user._id;
    //newPlaylist.collaborators.push(req.user._id);
    newPlaylist.save(function(err, data) {
      if (err) {
        console.log(err);
        return res.status(500).json({msg: 'internal server error'});
      }
      //respond with the playlist
      res.json(data);
    });
  });

  //Delete playlist - DELETE
  //send DEL to /api/delete_playlist/
  //message body: {id: playlistID, eat: token}
  router.delete('/delete_playlist', eatAuth, function(req, res) {
    Playlist.remove({_id: req.params.id}, function(err, data) {
      if (err) {
        console.log(err);
        return res.status(500).json({msg: 'internal server error'});
      }

      res.json({msg: 'success'});
    });
  });

  //Add song to playlist - POST
  //send POST to /api/playlist/
  //message body: {eat: token, id: playlistID, song: spotifySong}
  router.post('/playlist', eatAuth, function(req, res) {
    Playlist.findOne({_id: req.body.id}, function(err, pl) {
      if (err) {
        console.log(err);
        return res.status(500).json({msg: 'internal server error'});
      }
      //console.log("ADD SONG BODY: ");
      //console.log(req.body);
      var options = {
        host: 'localhost',
        port: 3000,
        path: '/api/songs/' + req.body.uri,
        method: 'GET',
        headers: {
          "Content-Type": "application/json"
        }
      };

      var req2 = http.request(options, function(res2) {
        var body2 = '';
        res2.on('data', function(data) { body2 += data;});
        res2.on('end', function(data) {
          //If the song wasn't present in the Songs database, add a new entry
          if (!JSON.parse(body2)) //(this can be any of the properties we have in songs in our database)
          {
            var songBody =
              {artist: req.body.artistName,
               name: req.body.trackName,
               duration: req.body.duration,
               album: req.body.albumName,
               spotifyID: req.body.uri,
               album_artwork_url: req.body.albumArtworkURL};
            options.path = '/api/songs';
            options.method = 'POST';
            var req3 = http.request(options, function(res3) {
              //add the new song's ID to our playlist and save it
              // Continuously update stream with data
              var body3 = '';
              res3.on('data', function(data) {body3 += data;});
              res3.on('end', function() {
                pl.addSong(JSON.parse(body3)._id);
                pl.save(function(err) {
                  if (err) {
                    console.log(err);
                    return res.status(500).json({msg: 'internal server error'});
                  }
                  res.json({msg: 'success'});
                }); //end save
              });//end response
            });
            req3.write(JSON.stringify(songBody));
            req3.end();
          }
          else //The song WAS present in the Songs database
          {
            //add the song's ID to our playlist and save it
            pl.addSong(JSON.parse(body2)._id);
            pl.save(function(err) {
              if (err) {
                console.log(err);
                return res.status(500).json({msg: 'internal server error'});
              }
              res.json({msg: 'success'});
            }); //end save
          }
        });
      }); //end GET request
      req2.end();
    }); //end findOne
  });

  //Remove song from playlist - DELETE
  //send DEL to /api/playlist/
  //message body: {eat: token, id: playlistID, song: songID}
  //Will respond with the updated playlist
  router.delete('/playlist', eatAuth, function(req, res) {
    Playlist.findOne({_id: req.body.id}, function(err, pl) {
      if (err) {
        console.log(err);
        return res.status(500).json({msg: 'internal server error'});
      }
      pl.removeSong(req.body.song);
      pl.save(function(err, data) {
        if (err) {
          console.log(err);
          return res.status(500).json({msg: 'internal server error'});
        }
        res.json(data);
      }); //end save
    }); //end findOne
  });

  //Update playlist - POST with collaborators, or song to add
  //send POST to /api/playlist/
  //message body: {name: "playlistname", song: {songObject}}
  router.post('/playlist', function(req, res) {
    res.json({msg: 'unimplemented'});
  });

};
<commit_msg>small fix<commit_after>'use strict';

var Playlist = require('../models/Playlist.js');
var bodyparser = require('body-parser');
var eatAuth = require('../lib/eat_auth.js')(process.env.APP_SECRET);
var Song = require('../models/Song');

var http = require('http');

//all these routes will be under /api

module.exports = function(router) {
  router.use(bodyparser.json());

  //Finds all playlists with a certain word (or words) in its name
  //send POST to /api/playlist/search
  //http headers: searchString=searchstring, eat=token
  router.post('/playlist/search', eatAuth, function(req, res) {
    //basic regular expression: true if playlist name contains our string
    Playlist.find({name: new RegExp(req.body.searchString, 'i')},
    function(err, data) {
      if (err) {
        console.log(err);
        return res.status(500).json({msg: 'internal server error'});
      }
      res.json(data);
    });
  });

  //Gets all of a user's playlists
  //send POST to /api/playlist/mine
  //message body: {eat: token}
  router.post('/playlist/mine', eatAuth, function(req, res) {
    Playlist.find({createdBy: req.user._id}, function(err, data) {
      if (err) {
        console.log(err);
        return res.status(500).json({msg: 'internal server error'});
      }
      res.json(data);
    });
  });

  //Gets all playlists a user is contributing to
  //send POST to /api/playlist/contrib
  //message body: {eat: token}
  router.post('/playlist/contrib', eatAuth, function(req, res) {
    Playlist.find({'collaborators.$': req.user._id}, function(err, data) {
      if (err) {
        console.log(err);
        return res.status(500).json({msg: 'internal server error'});
      }
      res.json(data);
    });
  });

  //Create playlist
  //send POST to /api/create_playlist/
  //message body: {name: "playlistname", eat: token}
  router.post('/create_playlist', eatAuth, function(req, res) {
    var newPlaylist = new Playlist(req.body);
    newPlaylist.dateCreated = (new Date()).toDateString();
    newPlaylist.createdBy = req.user._id;
    //newPlaylist.collaborators.push(req.user._id);
    newPlaylist.save(function(err, data) {
      if (err) {
        console.log(err);
        return res.status(500).json({msg: 'internal server error'});
      }
      //respond with the playlist
      res.json(data);
    });
  });

  //Delete playlist - DELETE
  //send DEL to /api/delete_playlist/
  //message body: {id: playlistID, eat: token}
  router.delete('/delete_playlist', eatAuth, function(req, res) {
    Playlist.remove({_id: req.params.id}, function(err, data) {
      if (err) {
        console.log(err);
        return res.status(500).json({msg: 'internal server error'});
      }

      res.json({msg: 'success'});
    });
  });

  //Add song to playlist - POST
  //send POST to /api/playlist/
  //message body: {eat: token, id: playlistID, song: spotifySong}
  router.post('/playlist', eatAuth, function(req, res) {
    Playlist.findOne({_id: req.body.id}, function(err, pl) {
      if (err) {
        console.log(err);
        return res.status(500).json({msg: 'internal server error'});
      }
      //console.log("ADD SONG BODY: ");
      //console.log(req.body);
      var options = {
        host: 'localhost',
        port: 3000,
        path: '/api/songs/' + req.body.uri,
        method: 'GET',
        headers: {
          "Content-Type": "application/json"
        }
      };

      var req2 = http.request(options, function(res2) {
        var body2 = '';
        res2.on('data', function(data) { body2 += data;});
        res2.on('end', function(data) {
          //If the song wasn't present in the Songs database, add a new entry
          if (!JSON.parse(body2)) //(this can be any of the properties we have in songs in our database)
          {
            var songBody =
              {artist: req.body.artistName,
               name: req.body.trackName,
               duration: req.body.duration,
               album: req.body.albumName,
               spotifyID: req.body.uri,
               album_artwork_url: req.body.albumArtworkURL};
            options.path = '/api/songs';
            options.method = 'POST';
            var req3 = http.request(options, function(res3) {
              //add the new song's ID to our playlist and save it
              // Continuously update stream with data
              var body3 = '';
              res3.on('data', function(data) {body3 += data;});
              res3.on('end', function() {
                pl.addSong(JSON.parse(body3)._id);
                pl.save(function(err) {
                  if (err) {
                    console.log(err);
                    return res.status(500).json({msg: 'internal server error'});
                  }
                  res.json({msg: 'success'});
                }); //end save
              });//end response
            });
            req3.write(JSON.stringify(songBody));
            req3.end();
          }
          else //The song WAS present in the Songs database
          {
            //add the song's ID to our playlist and save it
            pl.addSong(JSON.parse(body2)._id);
            pl.save(function(err) {
              if (err) {
                console.log(err);
                return res.status(500).json({msg: 'internal server error'});
              }
              res.json({msg: 'success'});
            }); //end save
          }
        });
      }); //end GET request
      req2.end();
    }); //end findOne
  });

  //Remove song from playlist - DELETE
  //send DEL to /api/playlist/
  //message body: {eat: token, id: playlistID, song: songID}
  //Will respond with the updated playlist
  router.delete('/playlist', eatAuth, function(req, res) {
    Playlist.findOne({_id: req.body.id}, function(err, pl) {
      if (err) {
        console.log(err);
        return res.status(500).json({msg: 'internal server error'});
      }
      pl.removeSong(req.body.song);
      pl.save(function(err, data) {
        if (err) {
          console.log(err);
          return res.status(500).json({msg: 'internal server error'});
        }
        res.json(data);
      }); //end save
    }); //end findOne
  });

  //Update playlist - POST with collaborators, or song to add
  //send POST to /api/playlist/
  //message body: {name: "playlistname", song: {songObject}}
  //router.post('/playlist', function(req, res) {
   // res.json({msg: 'unimplemented'});
  //});

};
<|endoftext|>


<commit_before>////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////Builds the HTML Table from ElectionIDs

function buildElectionTable() {

	 var electionConf = JSON.parse(electionConfigRaw);
	 var sAddresses = JSON.parse(sAddressesRaw);
	 var elections = electionConf.elections;

	 var lastMix = "http://localhost:"+electionConf["nginx-port"];
	 //don't use port 80 if it's not deployed
	 if(electionConf.deployment === true){
		 lastMix = sAddresses["server-address"].mix2;
	 }

	 document.getElementById("elections").innerHTML = "";

	 var head$ = $('
');
	 head$.append($('').html(" Election IDs "));
	 head$.append($('').html(" Election Title "));
	 head$.append($('').html(" Starting Time "));
	 head$.append($('').html(" Ending Time "));
	 head$.append($('').html(" Election State "));
  $("#elections").append(head$);

  for (var i = 0 ; i < elections.length ; i++) {
 	 var elecID = elections[i].electionID;
 	 var elecStatus = 'waiting';

      var row$ = $('
');

      row$.append($('').html("   "+elections[i].electionID+"    "));
      row$.append($('').html("   "+elections[i].electionTitle+"   "));
      row$.append($('').html("   "+elections[i].startTime.substring(0, elections[i].startTime.length-9)+"   "));
      row$.append($('').html("   "+elections[i].endTime.substring(0, elections[i].endTime.length-9)+"   "));
      row$.append($('').html("   "+elecStatus+"   "));
      $("#elections").append(row$);

      getElectionStatus(elecID, function (eleID, stat){
 		 document.getElementById(eleID).innerHTML = stat;
 	 });
  }

  function getElectionStatus(eleID, callback) {
      // Detemine the status of the system: (not-yet) open/closed,
      // by quering the final mix server.
      // Depending on the state, either the voting tab or the
      // verification tab will be opened.
      //
      // The state is detemined in a (too?) simple way, by
      // checking if the final server has ready result.
      //
 	 var stat = 'what';
 	 var url = lastMix+'/'+eleID+'/mix/03/status';
      $.get(url)
       .fail(function () {
          var stat = 'no response';
          callback(eleID, stat)
        })
       .done(function (result) {  // we have some response
          var stat = 'pending';
     	 if (result.status==='result ready'){
         	 stat = 'closed';
          }
          else {
         	 stat = 'ready';
          }
          callback(eleID, stat)
        });

  }

  // Returns a promise of the state of the final mix server
  // The promise resolves to true if the result is ready and
  // to false otherwise.
  // The promise is rejected if the final server is down of
  // works for a different election.
  //
  function resultOfFinalServerReady(eleID) {
 	 var url = lastMix+'/'+eleID+'/mix/03/status';
      $.get(url)
       .fail(function () {
          return 'pending';
        })
       .done(function (result) {  // we have some response
          if (result.electionID.substring(0, 5).toUpperCase() !== electionID.toUpperCase()) {
              reject('wrong election ID')
          }
          else if (result.status==='result ready'){
         		 return 'closed';
          }
          else {return 'ready';}
        });
  }

  ////////////////////////////////////////////////////////////////
  ///////// Refresh State

	/* Refresh */
  var electionStates = window.setInterval(function() {
 	 for (var i = 0 ; i < elections.length ; i++) {
     	 var elecID = elections[i].electionID;
     	 getElectionStatus(elecID, function (eleID, stat){
     		 document.getElementById(eleID).innerHTML = stat;
     	 });
 	 }
  }, 1000);

  $('#welcome').show();

}<commit_msg>changed state from "ready" to "open"<commit_after>////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////Builds the HTML Table from ElectionIDs

function buildElectionTable() {

	 var electionConf = JSON.parse(electionConfigRaw);
	 var sAddresses = JSON.parse(sAddressesRaw);
	 var elections = electionConf.elections;

	 var lastMix = "http://localhost:"+electionConf["nginx-port"];
	 //don't use port 80 if it's not deployed
	 if(electionConf.deployment === true){
		 lastMix = sAddresses["server-address"].mix2;
	 }

	 document.getElementById("elections").innerHTML = "";

	 var head$ = $('
');
	 head$.append($('').html(" Election IDs "));
	 head$.append($('').html(" Election Title "));
	 head$.append($('').html(" Starting Time "));
	 head$.append($('').html(" Ending Time "));
	 head$.append($('').html(" Election State "));
  $("#elections").append(head$);

  for (var i = 0 ; i < elections.length ; i++) {
 	 var elecID = elections[i].electionID;
 	 var elecStatus = 'waiting';

      var row$ = $('
');

      row$.append($('').html("   "+elections[i].electionID+"    "));
      row$.append($('').html("   "+elections[i].electionTitle+"   "));
      row$.append($('').html("   "+elections[i].startTime.substring(0, elections[i].startTime.length-9)+"   "));
      row$.append($('').html("   "+elections[i].endTime.substring(0, elections[i].endTime.length-9)+"   "));
      row$.append($('').html("   "+elecStatus+"   "));
      $("#elections").append(row$);

      getElectionStatus(elecID, function (eleID, stat){
 		 document.getElementById(eleID).innerHTML = stat;
 	 });
  }

  function getElectionStatus(eleID, callback) {
      // Detemine the status of the system: (not-yet) open/closed,
      // by quering the final mix server.
      // Depending on the state, either the voting tab or the
      // verification tab will be opened.
      //
      // The state is detemined in a (too?) simple way, by
      // checking if the final server has ready result.
      //
 	 var stat = 'what';
 	 var url = lastMix+'/'+eleID+'/mix/03/status';
      $.get(url)
       .fail(function () {
          var stat = 'no response';
          callback(eleID, stat)
        })
       .done(function (result) {  // we have some response
          var stat = 'pending';
     	 if (result.status==='result ready'){
         	 stat = 'closed';
          }
          else {
         	 stat = 'open';
          }
          callback(eleID, stat)
        });

  }

  // Returns a promise of the state of the final mix server
  // The promise resolves to true if the result is ready and
  // to false otherwise.
  // The promise is rejected if the final server is down of
  // works for a different election.
  //
  function resultOfFinalServerReady(eleID) {
 	 var url = lastMix+'/'+eleID+'/mix/03/status';
      $.get(url)
       .fail(function () {
          return 'pending';
        })
       .done(function (result) {  // we have some response
          if (result.electionID.substring(0, 5).toUpperCase() !== electionID.toUpperCase()) {
              reject('wrong election ID')
          }
          else if (result.status==='result ready'){
         		 return 'closed';
          }
          else {return 'ready';}
        });
  }

  ////////////////////////////////////////////////////////////////
  ///////// Refresh State

	/* Refresh */
  var electionStates = window.setInterval(function() {
 	 for (var i = 0 ; i < elections.length ; i++) {
     	 var elecID = elections[i].electionID;
     	 getElectionStatus(elecID, function (eleID, stat){
     		 document.getElementById(eleID).innerHTML = stat;
     	 });
 	 }
  }, 1000);

  $('#welcome').show();

}<|endoftext|>


<commit_before>/**
* Frost ___ Plugin
* Informational commands
* by: panpawn
*/

exports.commands = {
	coldfront: 'frostmag',
	frostmag: function (target, room, user, connection) {
		if (room.id !== 'coldfront') return false;
		if (!this.canTalk()) return;
		var mag_name = "Cold Front";
		var allowed_set_usernames = ['flicette','panpawn']; //users who are allowed to change the newest issue link
		var mag1 = toId(target.slice(0, target.indexOf(', ')));
		var mag2 = target.slice(target.indexOf(', ') + 1).trim();
		var link_to_newest_issue = Rooms.rooms.lobby.chatRoomData.frostMagLink;
		if (mag1 === 'set') {
			if (allowed_set_usernames.indexOf(toId(user.name)) === -1 || !user.group === '~') return false;
			Rooms.rooms.lobby.chatRoomData.frostMagLink = mag2;
			Rooms.global.writeChatRoomData();
			this.sendReply("The link to the newest issue has been set to: " + mag2);
			this.logModCommand(user.name + " has set the link of the newest issue of the " + mag_name + " to be: " + mag2);
		} else {
			if (!this.canBroadcast()) return;
			this.sendReplyBox(
				"" + mag_name + "
 " +
				"- A link to the site of the " + mag_name + " can be found here.
" +
				"- For information on how to become a contrubutor, go here.
" +
				"- A link to our newest issue can be found here."
			);
		}
	}
};
<commit_msg>Update coldfront.js<commit_after>/**
* Frost ___ Plugin
* Informational commands
* by: panpawn
*/

exports.commands = {
	coldfront: 'frostmag',
	frostmag: function (target, room, user, connection) {
		if (room.id !== 'coldfront') return false;
		if (!this.canTalk()) return;
		var mag_name = "Cold Front";
		var allowed_set_usernames = ['flicette','panpawn']; //users who are allowed to change the newest issue link
		var mag1 = toId(target.slice(0, target.indexOf(', ')));
		var mag2 = target.slice(target.indexOf(', ') + 1).trim();
		var link_to_newest_issue = Rooms.rooms.lobby.chatRoomData.frostMagLink;
		if (mag1 === 'set') {
			if (allowed_set_usernames.indexOf(toId(user.name)) === -1 || !user.group === '~') return false;
			if (mag2 === link_to_newest_issue) return this.sendReply("That link is already set...")
			Rooms.rooms.lobby.chatRoomData.frostMagLink = mag2;
			Rooms.global.writeChatRoomData();
			this.sendReply("The link to the newest issue has been set to: " + mag2);
			this.logModCommand(user.name + " has set the link of the newest issue of the " + mag_name + " to be: " + mag2);
		} else {
			if (!this.canBroadcast()) return;
			this.sendReplyBox(
				"" + mag_name + "
 " +
				"- A link to the site of the " + mag_name + " can be found here.
" +
				"- For information on how to become a contrubutor, go here.
" +
				"- A link to our newest issue can be found here."
			);
		}
	}
};
<|endoftext|>


<commit_before>/*!
 Copyright (C) 2017 Google Inc.
 Licensed under http://www.apache.org/licenses/LICENSE-2.0 <see LICENSE file>
 */
(function (can, GGRC, CMS) {
  'use strict';

  can.Model.Cacheable('CMS.Models.Assessment', {
    root_object: 'assessment',
    root_collection: 'assessments',
    findOne: 'GET /api/assessments/{id}',
    findAll: 'GET /api/assessments',
    update: 'PUT /api/assessments/{id}',
    destroy: 'DELETE /api/assessments/{id}',
    create: 'POST /api/assessments',
    mixins: [
      'ownable', 'contactable', 'unique_title',
      'autoStatusChangeable', 'timeboxed', 'mapping-limit',
      'inScopeObjects'
    ],
    is_custom_attributable: true,
    attributes: {
      related_sources: 'CMS.Models.Relationship.stubs',
      related_destinations: 'CMS.Models.Relationship.stubs',
      context: 'CMS.Models.Context.stub',
      modified_by: 'CMS.Models.Person.stub',
      finished_date: 'date',
      verified_date: 'date'
    },
    defaults: {
      status: 'Not Started',
      send_by_default: true,  // notifications when a comment is added
      recipients: 'Assessor,Creator,Verifier'  // user roles to be notified
    },
    statuses: ['Not Started', 'In Progress', 'Ready for Review',
        'Verified', 'Completed'],
    tree_view_options: {
      add_item_view: GGRC.mustache_path +
      '/base_objects/tree_add_item.mustache',
      attr_view: GGRC.mustache_path + '/base_objects/tree-item-attr.mustache',
      attr_list: [{
        attr_title: 'Title',
        attr_name: 'title'
      }, {
        attr_title: 'Code',
        attr_name: 'slug'
      }, {
        attr_title: 'State',
        attr_name: 'status'
      }, {
        attr_title: 'Verified',
        attr_name: 'verified'
      }, {
        attr_title: 'Last Updated',
        attr_name: 'updated_at'
      }, {
        attr_title: 'Conclusion: Design',
        attr_name: 'design'
      }, {
        attr_title: 'Conclusion: Operation',
        attr_name: 'operationally'
      }, {
        attr_title: 'Finished Date',
        attr_name: 'finished_date'
      }, {
        attr_title: 'Verified Date',
        attr_name: 'verified_date'
      }, {
        attr_title: 'URL',
        attr_name: 'url'
      }, {
        attr_title: 'Reference URL',
        attr_name: 'reference_url'
      }]
    },
    info_pane_options: {
      mapped_objects: {
        model: can.Model.Cacheable,
        mapping: 'info_related_objects',
        show_view: GGRC.mustache_path + '/base_templates/subtree.mustache'
      },
      evidence: {
        model: CMS.Models.Document,
        mapping: 'all_documents',
        show_view: GGRC.mustache_path + '/base_templates/attachment.mustache',
        sort_function: GGRC.Utils.sortingHelpers.commentSort
      },
      comments: {
        model: can.Model.Cacheable,
        mapping: 'comments',
        show_view: GGRC.mustache_path +
        '/base_templates/comment_subtree.mustache',
        sort_function: GGRC.Utils.sortingHelpers.commentSort
      },
      urls: {
        model: CMS.Models.Document,
        mapping: 'all_urls',
        show_view: GGRC.mustache_path + '/base_templates/urls.mustache'
      }
    },
    confirmEditModal: {
      title: 'Confirm moving Request to "In Progress"',
      description: 'You are about to move request from ' +
      '"{{status}}" to "In Progress" - are you sure about that?',
      button: 'Confirm'
    },
    assignable_list: [{
      title: 'Creator(s)',
      type: 'creator',
      mapping: 'related_creators',
      required: true
    }, {
      title: 'Assignee(s)',
      type: 'assessor',
      mapping: 'related_assessors',
      required: true
    }, {
      title: 'Verifier(s)',
      type: 'verifier',
      mapping: 'related_verifiers',
      required: false
    }],
    conflicts: [
      ['assessor', 'verifier']
    ],
    conclusions: ['Effective', 'Ineffective', 'Needs improvement',
      'Not Applicable'],
    init: function () {
      if (this._super) {
        this._super.apply(this, arguments);
      }
      this.validatePresenceOf('audit');
      this.validateNonBlank('title');

      this.validate(
        'validate_creator',
        function () {
          if (!this.validate_creator) {
            return 'You need to specify at least one creator';
          }
        }
      );
      this.validate(
        'validate_assessor',
        function () {
          if (!this.validate_assessor) {
            return 'You need to specify at least one assignee';
          }
        }
      );
    },
    prepareAttributes: function (attrs) {
      return attrs[this.root_object] ? attrs[this.root_object] : attrs;
    },
    /**
     * Assessment specific parsing logic to simplify business logic of Custom Attributes
     * @param {Array} definitions - original list of Custom Attributes Definition
     * @param {Array} values - original list of Custom Attributes Values
     * @return {Array} Updated Custom attributes
     */
    prepareCustomAttributes: function (definitions, values) {
      return definitions.map(function (def) {
        var valueData = false;
        var id = def.id;
        var type = GGRC.Utils.mapCAType(def.attribute_type);
        var stub = {
          id: null,
          custom_attribute_id: id,
          attribute_value: null,
          attribute_object: null,
          validation: {
            empty: true,
            mandatory: def.mandatory,
            valid: true
          },
          def: def,
          attributeType: type
        };

        values.forEach(function (value) {
          var errors = [];
          if (value.custom_attribute_id === id) {
            errors = value.preconditions_failed || [];
            value.def = def;
            value.attributeType = type;
            value.validation = {
              empty: errors.indexOf('value') > -1,
              mandatory: def.mandatory,
              valid: errors.indexOf('comment') < 0 &&
              errors.indexOf('evidence') < 0
            };

            valueData = value;
          }
        });

        return valueData || stub;
      });
    },
    /**
     * Assessment specific AJAX data parsing logic
     * @param {Object} attributes - hash of Model key->values
     * @return {Object} - parsed object with normalized data
     */
    parseModel: function (attributes) {
      var values;
      var definitions;
      attributes = this.prepareAttributes(attributes);
      values = attributes.custom_attribute_values || [];
      definitions = attributes.custom_attribute_definitions || [];

      if (!definitions.length) {
        return attributes;
      }

      attributes.custom_attribute_values =
        this.prepareCustomAttributes(definitions, values);
      return attributes;
    },
    model: function (attributes, oldModel) {
      var model;
      var id;
      if (!attributes) {
        return;
      }

      if (typeof attributes.serialize === 'function') {
        attributes = attributes.serialize();
      } else {
        attributes = this.parseModel(attributes);
      }

      id = attributes[this.id];
      if ((id || id === 0) && this.store[id]) {
        oldModel = this.store[id];
      }

      model = oldModel && can.isFunction(oldModel.attr) ?
        oldModel.attr(attributes) :
        new this(attributes);

      return model;
    },
    /**
     * Replace Cacheble#findInCacheById method with the latest feature of can.Model - store
     * @param {String} id - Id of requested Model
     * @return {CMS.Models.Assessment} - already existing model
     */
    findInCacheById: function (id) {
      return this.store[id];
    }
  }, {
    init: function () {
      if (this._super) {
        this._super.apply(this, arguments);
      }
    },
    save: function () {
      if (!this.attr('program')) {
        this.attr('program', this.attr('audit.program'));
      }
      return this._super.apply(this, arguments);
    },
    after_save: function () {
      this.updateValidation();
      if (this.audit && this.audit.selfLink) {
        this.audit.refresh();
      }
    },
    updateValidation: function () {
      var values = this.attr('custom_attribute_values');
      var definitions = this.attr('custom_attribute_definitions');
      var errorsList = {
        attachment: [],
        comment: [],
        value: []
      };
      this.validateValues(definitions, values, errorsList);
      this.setErrorMessages(errorsList);
      this.setAggregatedErrorMessage();
    },
    validateValues: function (definitions, values, errorsList) {
      can.each(definitions, function (cad) {
        var cav;
        var value;

        can.each(values, function (item) {
          if (item.custom_attribute_id === cad.id) {
            cav = item;
            value = cav.attribute_value;
          }
        });
        if (cad.mandatory &&
          GGRC.Utils.isEmptyCA(value, cad.attribute_type, cav)) {
          // If Custom Attribute is mandatory and empty
          errorsList.value.push(cad.title);
        } else if (cav) {
          // If Custom Attribute Value is presented - do all required checks
          cav.preconditions_failed = cav.preconditions_failed || [];
          if (cav.preconditions_failed.indexOf('comment') > -1) {
            errorsList.comment.push(cad.title + ': ' + value);
          }
          if (cav.preconditions_failed.indexOf('evidence') > -1) {
            errorsList.attachment.push(cad.title + ': ' + value);
          }
        }
      });
    },
    setErrorMessages: function (needed) {
      if (needed.comment.length) {
        this.attr('_mandatory_comment_msg',
          'Comment required by: ' + needed.comment.join(', '));
      } else {
        this.removeAttr('_mandatory_comment_msg');
      }

      if (needed.attachment.length) {
        this.attr('_mandatory_attachment_msg',
          'Evidence required by: ' + needed.attachment.join(', '));
      } else {
        this.removeAttr('_mandatory_attachment_msg');
      }

      if (needed.value.length) {
        this.attr(
          '_mandatory_value_msg',
          'Values required for: ' + needed.value.join(', ')
        );
      } else {
        this.removeAttr('_mandatory_value_msg');
      }
    },
    setAggregatedErrorMessage: function () {
      this.attr('_mandatory_msg',
        _.filter([
          this.attr('_mandatory_value_msg'),
          this.attr('_mandatory_attachment_msg'),
          this.attr('_mandatory_comment_msg')
        ]).join('; <br />') || false
      );
    },
    form_preload: function (newObjectForm) {
      var pageInstance = GGRC.page_instance();
      var currentUser = CMS.Models.get_instance('Person',
        GGRC.current_user.id, GGRC.current_user);
      var auditLead;

      if (pageInstance && (!this.audit || !this.audit.id || !this.audit.type)) {
        if (pageInstance.type === 'Audit') {
          this.attr('audit', pageInstance);
        } else if (this.scopeObject) {
          this.audit = this.scopeObject;
        }
      }

      if (!newObjectForm) {
        return;
      }

      if (this.audit) {
        auditLead = this.audit.contact.reify();
        if (currentUser === auditLead) {
          markForAddition(this, auditLead, 'Creator,Assessor');
        } else {
          markForAddition(this, auditLead, 'Assessor');
          markForAddition(this, currentUser, 'Creator');
        }
      } else {
        markForAddition(this, currentUser, 'Creator');
      }

      function markForAddition(instance, user, type) {
        instance.mark_for_addition('related_objects_as_destination', user, {
          attrs: {
            AssigneeType: type
          }
        });
      }
    },
    refreshInstance: function () {
      return this.refresh().then(function () {
        this.updateValidation();
      }.bind(this));
    },
    info_pane_preload: function () {
      if (!this._pane_preloaded) {
        this.get_mapping('comments').bind('length',
          this.refreshInstance.bind(this));
        this.get_mapping('all_documents').bind('length',
          this.refreshInstance.bind(this));
        this.refreshInstance();
        this._pane_preloaded = true;
      }
    }
  });
})(window.can, window.GGRC, window.CMS);
<commit_msg>Make sure context is set when saving assessment<commit_after>/*!
 Copyright (C) 2017 Google Inc.
 Licensed under http://www.apache.org/licenses/LICENSE-2.0 <see LICENSE file>
 */
(function (can, GGRC, CMS) {
  'use strict';

  can.Model.Cacheable('CMS.Models.Assessment', {
    root_object: 'assessment',
    root_collection: 'assessments',
    findOne: 'GET /api/assessments/{id}',
    findAll: 'GET /api/assessments',
    update: 'PUT /api/assessments/{id}',
    destroy: 'DELETE /api/assessments/{id}',
    create: 'POST /api/assessments',
    mixins: [
      'ownable', 'contactable', 'unique_title',
      'autoStatusChangeable', 'timeboxed', 'mapping-limit',
      'inScopeObjects'
    ],
    is_custom_attributable: true,
    attributes: {
      related_sources: 'CMS.Models.Relationship.stubs',
      related_destinations: 'CMS.Models.Relationship.stubs',
      context: 'CMS.Models.Context.stub',
      modified_by: 'CMS.Models.Person.stub',
      finished_date: 'date',
      verified_date: 'date'
    },
    defaults: {
      status: 'Not Started',
      send_by_default: true,  // notifications when a comment is added
      recipients: 'Assessor,Creator,Verifier'  // user roles to be notified
    },
    statuses: ['Not Started', 'In Progress', 'Ready for Review',
        'Verified', 'Completed'],
    tree_view_options: {
      add_item_view: GGRC.mustache_path +
      '/base_objects/tree_add_item.mustache',
      attr_view: GGRC.mustache_path + '/base_objects/tree-item-attr.mustache',
      attr_list: [{
        attr_title: 'Title',
        attr_name: 'title'
      }, {
        attr_title: 'Code',
        attr_name: 'slug'
      }, {
        attr_title: 'State',
        attr_name: 'status'
      }, {
        attr_title: 'Verified',
        attr_name: 'verified'
      }, {
        attr_title: 'Last Updated',
        attr_name: 'updated_at'
      }, {
        attr_title: 'Conclusion: Design',
        attr_name: 'design'
      }, {
        attr_title: 'Conclusion: Operation',
        attr_name: 'operationally'
      }, {
        attr_title: 'Finished Date',
        attr_name: 'finished_date'
      }, {
        attr_title: 'Verified Date',
        attr_name: 'verified_date'
      }, {
        attr_title: 'URL',
        attr_name: 'url'
      }, {
        attr_title: 'Reference URL',
        attr_name: 'reference_url'
      }]
    },
    info_pane_options: {
      mapped_objects: {
        model: can.Model.Cacheable,
        mapping: 'info_related_objects',
        show_view: GGRC.mustache_path + '/base_templates/subtree.mustache'
      },
      evidence: {
        model: CMS.Models.Document,
        mapping: 'all_documents',
        show_view: GGRC.mustache_path + '/base_templates/attachment.mustache',
        sort_function: GGRC.Utils.sortingHelpers.commentSort
      },
      comments: {
        model: can.Model.Cacheable,
        mapping: 'comments',
        show_view: GGRC.mustache_path +
        '/base_templates/comment_subtree.mustache',
        sort_function: GGRC.Utils.sortingHelpers.commentSort
      },
      urls: {
        model: CMS.Models.Document,
        mapping: 'all_urls',
        show_view: GGRC.mustache_path + '/base_templates/urls.mustache'
      }
    },
    confirmEditModal: {
      title: 'Confirm moving Request to "In Progress"',
      description: 'You are about to move request from ' +
      '"{{status}}" to "In Progress" - are you sure about that?',
      button: 'Confirm'
    },
    assignable_list: [{
      title: 'Creator(s)',
      type: 'creator',
      mapping: 'related_creators',
      required: true
    }, {
      title: 'Assignee(s)',
      type: 'assessor',
      mapping: 'related_assessors',
      required: true
    }, {
      title: 'Verifier(s)',
      type: 'verifier',
      mapping: 'related_verifiers',
      required: false
    }],
    conflicts: [
      ['assessor', 'verifier']
    ],
    conclusions: ['Effective', 'Ineffective', 'Needs improvement',
      'Not Applicable'],
    init: function () {
      if (this._super) {
        this._super.apply(this, arguments);
      }
      this.validatePresenceOf('audit');
      this.validateNonBlank('title');

      this.validate(
        'validate_creator',
        function () {
          if (!this.validate_creator) {
            return 'You need to specify at least one creator';
          }
        }
      );
      this.validate(
        'validate_assessor',
        function () {
          if (!this.validate_assessor) {
            return 'You need to specify at least one assignee';
          }
        }
      );
    },
    prepareAttributes: function (attrs) {
      return attrs[this.root_object] ? attrs[this.root_object] : attrs;
    },
    /**
     * Assessment specific parsing logic to simplify business logic of Custom Attributes
     * @param {Array} definitions - original list of Custom Attributes Definition
     * @param {Array} values - original list of Custom Attributes Values
     * @return {Array} Updated Custom attributes
     */
    prepareCustomAttributes: function (definitions, values) {
      return definitions.map(function (def) {
        var valueData = false;
        var id = def.id;
        var type = GGRC.Utils.mapCAType(def.attribute_type);
        var stub = {
          id: null,
          custom_attribute_id: id,
          attribute_value: null,
          attribute_object: null,
          validation: {
            empty: true,
            mandatory: def.mandatory,
            valid: true
          },
          def: def,
          attributeType: type
        };

        values.forEach(function (value) {
          var errors = [];
          if (value.custom_attribute_id === id) {
            errors = value.preconditions_failed || [];
            value.def = def;
            value.attributeType = type;
            value.validation = {
              empty: errors.indexOf('value') > -1,
              mandatory: def.mandatory,
              valid: errors.indexOf('comment') < 0 &&
              errors.indexOf('evidence') < 0
            };

            valueData = value;
          }
        });

        return valueData || stub;
      });
    },
    /**
     * Assessment specific AJAX data parsing logic
     * @param {Object} attributes - hash of Model key->values
     * @return {Object} - parsed object with normalized data
     */
    parseModel: function (attributes) {
      var values;
      var definitions;
      attributes = this.prepareAttributes(attributes);
      values = attributes.custom_attribute_values || [];
      definitions = attributes.custom_attribute_definitions || [];

      if (!definitions.length) {
        return attributes;
      }

      attributes.custom_attribute_values =
        this.prepareCustomAttributes(definitions, values);
      return attributes;
    },
    model: function (attributes, oldModel) {
      var model;
      var id;
      if (!attributes) {
        return;
      }

      if (typeof attributes.serialize === 'function') {
        attributes = attributes.serialize();
      } else {
        attributes = this.parseModel(attributes);
      }

      id = attributes[this.id];
      if ((id || id === 0) && this.store[id]) {
        oldModel = this.store[id];
      }

      model = oldModel && can.isFunction(oldModel.attr) ?
        oldModel.attr(attributes) :
        new this(attributes);

      return model;
    },
    /**
     * Replace Cacheble#findInCacheById method with the latest feature of can.Model - store
     * @param {String} id - Id of requested Model
     * @return {CMS.Models.Assessment} - already existing model
     */
    findInCacheById: function (id) {
      return this.store[id];
    }
  }, {
    init: function () {
      if (this._super) {
        this._super.apply(this, arguments);
      }
    },
    before_create: function (dfd) {
      if (!this.audit) {
        throw new Error('Cannot save assessment, audit not set.');
      } else if (!this.audit.program || !this.audit.context) {
        throw new Error(
          'Cannot save assessment, audit context or program not set.');
      }
      this.attr('program', this.attr('audit.program'));
      this.attr('context', this.attr('audit.context'));
    },
    after_save: function () {
      this.updateValidation();
      if (this.audit && this.audit.selfLink) {
        this.audit.refresh();
      }
    },
    updateValidation: function () {
      var values = this.attr('custom_attribute_values');
      var definitions = this.attr('custom_attribute_definitions');
      var errorsList = {
        attachment: [],
        comment: [],
        value: []
      };
      this.validateValues(definitions, values, errorsList);
      this.setErrorMessages(errorsList);
      this.setAggregatedErrorMessage();
    },
    validateValues: function (definitions, values, errorsList) {
      can.each(definitions, function (cad) {
        var cav;
        var value;

        can.each(values, function (item) {
          if (item.custom_attribute_id === cad.id) {
            cav = item;
            value = cav.attribute_value;
          }
        });
        if (cad.mandatory &&
          GGRC.Utils.isEmptyCA(value, cad.attribute_type, cav)) {
          // If Custom Attribute is mandatory and empty
          errorsList.value.push(cad.title);
        } else if (cav) {
          // If Custom Attribute Value is presented - do all required checks
          cav.preconditions_failed = cav.preconditions_failed || [];
          if (cav.preconditions_failed.indexOf('comment') > -1) {
            errorsList.comment.push(cad.title + ': ' + value);
          }
          if (cav.preconditions_failed.indexOf('evidence') > -1) {
            errorsList.attachment.push(cad.title + ': ' + value);
          }
        }
      });
    },
    setErrorMessages: function (needed) {
      if (needed.comment.length) {
        this.attr('_mandatory_comment_msg',
          'Comment required by: ' + needed.comment.join(', '));
      } else {
        this.removeAttr('_mandatory_comment_msg');
      }

      if (needed.attachment.length) {
        this.attr('_mandatory_attachment_msg',
          'Evidence required by: ' + needed.attachment.join(', '));
      } else {
        this.removeAttr('_mandatory_attachment_msg');
      }

      if (needed.value.length) {
        this.attr(
          '_mandatory_value_msg',
          'Values required for: ' + needed.value.join(', ')
        );
      } else {
        this.removeAttr('_mandatory_value_msg');
      }
    },
    setAggregatedErrorMessage: function () {
      this.attr('_mandatory_msg',
        _.filter([
          this.attr('_mandatory_value_msg'),
          this.attr('_mandatory_attachment_msg'),
          this.attr('_mandatory_comment_msg')
        ]).join('; <br />') || false
      );
    },
    form_preload: function (newObjectForm) {
      var pageInstance = GGRC.page_instance();
      var currentUser = CMS.Models.get_instance('Person',
        GGRC.current_user.id, GGRC.current_user);
      var auditLead;

      if (pageInstance && (!this.audit || !this.audit.id || !this.audit.type)) {
        if (pageInstance.type === 'Audit') {
          this.attr('audit', pageInstance);
        } else if (this.scopeObject) {
          this.audit = this.scopeObject;
        }
      }

      if (!newObjectForm) {
        return;
      }

      if (this.audit) {
        auditLead = this.audit.contact.reify();
        if (currentUser === auditLead) {
          markForAddition(this, auditLead, 'Creator,Assessor');
        } else {
          markForAddition(this, auditLead, 'Assessor');
          markForAddition(this, currentUser, 'Creator');
        }
      } else {
        markForAddition(this, currentUser, 'Creator');
      }

      function markForAddition(instance, user, type) {
        instance.mark_for_addition('related_objects_as_destination', user, {
          attrs: {
            AssigneeType: type
          }
        });
      }
    },
    refreshInstance: function () {
      return this.refresh().then(function () {
        this.updateValidation();
      }.bind(this));
    },
    info_pane_preload: function () {
      if (!this._pane_preloaded) {
        this.get_mapping('comments').bind('length',
          this.refreshInstance.bind(this));
        this.get_mapping('all_documents').bind('length',
          this.refreshInstance.bind(this));
        this.refreshInstance();
        this._pane_preloaded = true;
      }
    }
  });
})(window.can, window.GGRC, window.CMS);
<|endoftext|>


<commit_before>export class NameExpression {
  constructor(name, mode){
    this.property = name;
    this.discrete = true;
    this.mode = mode.replace(/-([a-z])/g, (m, w) => w.toUpperCase());
  }

  createBinding(target){
    return new NameBinder(this.property, target, this.mode);
  }
}

class NameBinder {
  constructor(property, target, mode){
    this.property = property;

    if(mode === 'element'){
      this.target = target;
    } else {
      this.target = target[mode];

      if(this.target === undefined){
        throw new Error(`Attempted to reference "${mode}", but it was not found on the target element.`)
      }else{
        this.target = this.target.executionContext || this.target;
      }
    }
  }

  bind(source){
    if(this.source){
      if(this.source === source){
        return;
      }

      this.unbind();
    }

    this.source = source;
    source[this.property] = this.target;
  }

  unbind(){
    this.source[this.property] = null;
  }
}
<commit_msg>fix(name-expression): enable ref binding to view-model and html behaviors<commit_after>export class NameExpression {
  constructor(name, mode){
    this.property = name;
    this.discrete = true;
    this.mode = mode.replace(/-([a-z])/g, (m, w) => w.toUpperCase());
  }

  createBinding(target){
    return new NameBinder(this.property, target, this.mode);
  }
}

class NameBinder {
  constructor(property, target, mode){
    this.property = property;

    switch (mode) {
      case 'element':
        this.target = target;
        break;
      case 'view-model':
        this.target = target.primaryBehavior.executionContext;
        break;
      default:
        this.target = target[mode];

        if(this.target === undefined){
          throw new Error(`Attempted to reference "${mode}", but it was not found on the target element.`)
        }else{
          this.target = this.target.executionContext || this.target;
        }

        break;
    }
  }

  bind(source){
    if(this.source){
      if(this.source === source){
        return;
      }

      this.unbind();
    }

    this.source = source;
    source[this.property] = this.target;
  }

  unbind(){
    this.source[this.property] = null;
  }
}
<|endoftext|>