<commit_before>(function() {
    'use strict'

    angular
        .module('microphone')
        .factory('displaymetaFactory', displaymetaFactory);
    displaymetaFactory.$inject = ['SceneFactory'];

    function displaymetaFactory(SceneFactory) {
        var factory = {
            showResults: showResults,
            initDisplayMetadata: initDisplayMetadata
        };

        const INITIAL_OFFSET_X = 30;
        const INITIAL_OFFSET_Y = 30;
        const fontSize = 16;
        const delta_y = 2 * fontSize;
        const radius = 5;
        const space = 4;
        const hstep = 32;
        const timeout = 500;
        const defaultFont = fontSize + 'px Arial';
        const boldFont = 'bold ' + fontSize + 'px Arial';
        const italicFont = 'italic ' + fontSize + 'px Arial';
        const opacity = '0.6';

        var showAllHypotheses = true;
        var keywordsInputDirty = false;
        var keywords_to_search = [];
        var detected_keywords = {};
        var canvas = document.getElementById('canvas');
        var ctx = canvas.getContext('2d');
        var hslider = document.getElementById('hslider');
        var vslider = document.getElementById('vslider');
        var leftArrowEnabled = false;
        var rightArrowEnabled = false;
        window.worker = null;
        var runTimer = false;
        var scrolled = false;
        // var textScrolled = false;
        var pushed = 0;
        var popped = 0;
        var scene = new SceneFactory;

        return factory;

        function initDisplayMetadata() {
            console.log('intiDisplayMetadata() called');
            // initTextScroll();
            keywordsInputDirty = false;
            hslider.min = 0;
            hslider.max = 0;
            hslider.value = hslider.min;
            vslider.min = 0;
            vslider.max = 0;
            vslider.value = vslider.max;
            $('#vslider').css('display', 'none');
            $('#hslider').on('change mousemove', function() {
                onHScroll();
            });
            $('#vslider').on('change mousemove', function() {
                onVScroll();
            });

            $('#canvas').css('display', 'none');
            $('#canvas-placeholder').css('display', 'block');
            $('#left-arrow').css('display', 'none');
            $('#right-arrow').css('display', 'none');

            onResize(); // to adjust the canvas size

            var workerScriptBody =
                'var fifo = [];\n' +
                'var onmessage = function(event) {\n' +
                '  var payload = event.data;\n' +
                '  var type = payload.type;\n' +
                '  if(type == \'push\') {\n' +
                '    fifo.push(payload.msg);\n' +
                '  }\n' +
                '  else if(type == \'shift\' && fifo.length > 0) {\n' +
                '    var msg = fifo.shift();\n' +
                '    postMessage({\n' +
                '     bins:msg.results[0].word_alternatives,\n' +
                '     kws:msg.results[0].keywords_result\n' +
                '    });\n' +
                '  }\n' +
                '  else if(type == \'clear\') {\n' +
                '    fifo = [];\n' +
                '    console.log(\'worker: fifo cleared\');\n' +
                '  }\n' +
                '}\n';

            var blobURL = window.URL.createObjectURL(new Blob([workerScriptBody]));
            console.log("BLOB URL", blobURL);
            worker = new Worker(blobURL);
            worker.onmessage = function(event) {
                var data = event.data;
                // eslint-disable-next-line no-use-before-define
                // showCNsKWS(data.bins, data.kws);
                popped++;
                console.log('----> popped', popped);
            };
            console.log('worker instantiate');
        }

        function showResults(msg, baseString, model) {
            if (msg.results && msg.results.length > 0) {
                //var alternatives = msg.results[0].alternatives;
                var text = msg.results[0].alternatives[0].transcript || '';

                // apply mappings to beautify
                text = text.replace(/%HESITATION\s/g, '');
                //text = text.replace(/([^*])\1{2,}/g, '');   // seems to be getting in the way of smart formatting, 1000101 is converted to 1101

                if (msg.results[0].final) {
                    console.log('-> ' + text);
                    worker.postMessage({
                        type: 'push',
                        msg: msg
                    });
                    pushed++;
                    console.log('----> pushed', pushed);
                    if (runTimer == false) {
                        runTimer = true;
                        setTimeout(onTimer, timeout);
                    }
                }
                text = text.replace(/D_[^\s]+/g, '');

                // if all words are mapped to nothing then there is nothing else to do
                if ((text.length == 0) || (/^\s+$/.test(text))) {
                    return baseString;
                }

                var japanese = ((model.substring(0, 5) == 'ja-JP') || (model.substring(0, 5) == 'zh-CN'));
                var result;
                // capitalize first word
                // if final results, append a new paragraph
                if (msg.results && msg.results[0] && msg.results[0].final) {
                    text = text.slice(0, -1);
                    text = text.charAt(0).toUpperCase() + text.substring(1);
                    if (japanese) {
                        text = text.trim() + '。';
                        text = text.replace(/ /g, '');
                    } else {
                        text = text.trim() + '. ';
                    }

                    baseString += text;
                    result = baseString;
                } else {
                    if (japanese) {
                        text = text.replace(/ /g, ''); // remove whitespaces
                    } else {
                        text = text.charAt(0).toUpperCase() + text.substring(1);
                    }
                    result = baseString + text;
                }

                $('#resultsText').val(result);
                localStorage.setItem('result', result);
            }
            updateTextScroll();
            return baseString;
        }

        function updateTextScroll() {
            if (!scrolled) {
                var element = $('#resultsText').get(0);
                element.scrollTop = element.scrollHeight;
            }
        }

        function onHScroll() {
            if (hslider.value == 0) {
                leftArrowEnabled = false;
                rightArrowEnabled = true;
                $('#left-arrow').attr('src', 'images/arrow-left-icon-disabled.svg');
                $('#left-arrow').css('background-color', 'transparent');
                $('#right-arrow').attr('src', 'images/arrow-right-icon.svg');
                $('#right-arrow').css('background-color', '#C7C7C7');
            } else if (hslider.value == Math.floor(hslider.max)) {
                leftArrowEnabled = true;
                rightArrowEnabled = false;
                $('#left-arrow').attr('src', 'images/arrow-left-icon.svg');
                $('#left-arrow').css('background-color', '#C7C7C7');
                $('#right-arrow').attr('src', 'images/arrow-right-icon-disabled.svg');
                $('#right-arrow').css('background-color', 'transparent');
            } else {
                leftArrowEnabled = true;
                rightArrowEnabled = true;
                $('#left-arrow').attr('src', 'images/arrow-left-icon.svg');
                $('#left-arrow').css('background-color', '#C7C7C7');
                $('#right-arrow').attr('src', 'images/arrow-right-icon.svg');
                $('#right-arrow').css('background-color', '#C7C7C7');
            }
            scene._offset_X = INITIAL_OFFSET_X - hslider.value;
            draw();
        }

        function onVScroll() {
            scene._offset_Y = INITIAL_OFFSET_Y + Number(vslider.value);
            draw();
        }

        function onResize() {
            var dpr = window.devicePixelRatio || 1;
            var bsr = ctx.webkitBackingStorePixelRatio ||
                ctx.mozBackingStorePixelRatio ||
                ctx.msBackingStorePixelRatio ||
                ctx.oBackingStorePixelRatio ||
                ctx.backingStorePixelRatio || 1;
            var ratio = dpr / bsr;
            console.log('dpr/bsr =', ratio);
            var w = $('#canvas').width();
            var h = $('#canvas').height();
            canvas.width = w * ratio;
            canvas.height = h * ratio;
            ctx.setTransform(ratio, 0, 0, ratio, 0, 0);
            draw();
        }

        function draw() {
            ctx.clearRect(0, 0, 970, 370);
            scene.draw();
        }

        function clearScene() {
            scene._bins = [];
            scene._width = 0;
            scene._height = 0;
            scene._offset_X = INITIAL_OFFSET_X;
            scene._offset_Y = INITIAL_OFFSET_Y;
            hslider.max = 0;
            hslider.value = hslider.max;
            vslider.max = 0;
            vslider.min = 0;
            vslider.value = vslider.max;
            $('#hslider').css('display', 'none');
            $('#vslider').css('display', 'none');
            $('#show_alternate_words').css('display', 'none');
            $('#canvas').css('display', 'none');
            $('#canvas-placeholder').css('display', 'block');
            $('#left-arrow').css('display', 'none');
            $('#right-arrow').css('display', 'none');

            showAllHypotheses = true;
            $('#show_alternate_words').text('Hide alternate words');
            ctx.clearRect(0, 0, canvas.width, canvas.height);
        }

        function resetWorker() {
            runTimer = false;
            worker.postMessage({
                type: 'clear'
            });
            pushed = 0;
            popped = 0;
            console.log('---> resetWorker called');
        }

        $rootScope.on('clearscreen', function() {
            clearScene();
            resetWorker();
        });
    }
})();
<commit_msg>fixed display metadata<commit_after>(function() {
    'use strict'

    angular
        .module('microphone')
        .factory('displaymetaFactory', displaymetaFactory);
    displaymetaFactory.$inject = ['SceneFactory'];

    function displaymetaFactory(SceneFactory) {
        var factory = {
            showResults: showResults,
            initDisplayMetadata: initDisplayMetadata
        };

        return factory;

        function initDisplayMetadata() {
            console.log('intiDisplayMetadata() called');
            // initTextScroll();
            keywordsInputDirty = false;
            hslider.min = 0;
            hslider.max = 0;
            hslider.value = hslider.min;
            vslider.min = 0;
            vslider.max = 0;
            vslider.value = vslider.max;
            $('#vslider').css('display', 'none');
            $('#hslider').on('change mousemove', function() {
                onHScroll();
            });
            $('#vslider').on('change mousemove', function() {
                onVScroll();
            });

            $('#canvas').css('display', 'none');
            $('#canvas-placeholder').css('display', 'block');
            $('#left-arrow').css('display', 'none');
            $('#right-arrow').css('display', 'none');

            onResize(); // to adjust the canvas size

            var workerScriptBody =
                'var fifo = [];\n' +
                'var onmessage = function(event) {\n' +
                '  var payload = event.data;\n' +
                '  var type = payload.type;\n' +
                '  if(type == \'push\') {\n' +
                '    fifo.push(payload.msg);\n' +
                '  }\n' +
                '  else if(type == \'shift\' && fifo.length > 0) {\n' +
                '    var msg = fifo.shift();\n' +
                '    postMessage({\n' +
                '     bins:msg.results[0].word_alternatives,\n' +
                '     kws:msg.results[0].keywords_result\n' +
                '    });\n' +
                '  }\n' +
                '  else if(type == \'clear\') {\n' +
                '    fifo = [];\n' +
                '    console.log(\'worker: fifo cleared\');\n' +
                '  }\n' +
                '}\n';

            var blobURL = window.URL.createObjectURL(new Blob([workerScriptBody]));
            console.log("BLOB URL", blobURL);
            worker = new Worker(blobURL);
            worker.onmessage = function(event) {
                var data = event.data;
                // eslint-disable-next-line no-use-before-define
                // showCNsKWS(data.bins, data.kws);
                popped++;
                console.log('----> popped', popped);
            };
            console.log('worker instantiate');
        }

        function showResults(msg, baseString, model) {
            if (msg.results && msg.results.length > 0) {
                //var alternatives = msg.results[0].alternatives;
                var text = msg.results[0].alternatives[0].transcript || '';

                // apply mappings to beautify
                text = text.replace(/%HESITATION\s/g, '');
                //text = text.replace(/([^*])\1{2,}/g, '');   // seems to be getting in the way of smart formatting, 1000101 is converted to 1101

                if (msg.results[0].final) {
                    console.log('-> ' + text);
                    worker.postMessage({
                        type: 'push',
                        msg: msg
                    });
                    pushed++;
                    console.log('----> pushed', pushed);
                    if (runTimer == false) {
                        runTimer = true;
                        setTimeout(onTimer, timeout);
                    }
                }
                text = text.replace(/D_[^\s]+/g, '');

                // if all words are mapped to nothing then there is nothing else to do
                if ((text.length == 0) || (/^\s+$/.test(text))) {
                    return baseString;
                }

                var japanese = ((model.substring(0, 5) == 'ja-JP') || (model.substring(0, 5) == 'zh-CN'));
                var result;
                // capitalize first word
                // if final results, append a new paragraph
                if (msg.results && msg.results[0] && msg.results[0].final) {
                    text = text.slice(0, -1);
                    text = text.charAt(0).toUpperCase() + text.substring(1);
                    if (japanese) {
                        text = text.trim() + '。';
                        text = text.replace(/ /g, '');
                    } else {
                        text = text.trim() + '. ';
                    }

                    baseString += text;
                    result = baseString;
                } else {
                    if (japanese) {
                        text = text.replace(/ /g, ''); // remove whitespaces
                    } else {
                        text = text.charAt(0).toUpperCase() + text.substring(1);
                    }
                    result = baseString + text;
                }

                $('#resultsText').val(result);
                localStorage.setItem('result', result);
            }
            updateTextScroll();
            return baseString;
        }

        function updateTextScroll() {
            if (!scrolled) {
                var element = $('#resultsText').get(0);
                element.scrollTop = element.scrollHeight;
            }
        }

        function onHScroll() {
            if (hslider.value == 0) {
                leftArrowEnabled = false;
                rightArrowEnabled = true;
                $('#left-arrow').attr('src', 'images/arrow-left-icon-disabled.svg');
                $('#left-arrow').css('background-color', 'transparent');
                $('#right-arrow').attr('src', 'images/arrow-right-icon.svg');
                $('#right-arrow').css('background-color', '#C7C7C7');
            } else if (hslider.value == Math.floor(hslider.max)) {
                leftArrowEnabled = true;
                rightArrowEnabled = false;
                $('#left-arrow').attr('src', 'images/arrow-left-icon.svg');
                $('#left-arrow').css('background-color', '#C7C7C7');
                $('#right-arrow').attr('src', 'images/arrow-right-icon-disabled.svg');
                $('#right-arrow').css('background-color', 'transparent');
            } else {
                leftArrowEnabled = true;
                rightArrowEnabled = true;
                $('#left-arrow').attr('src', 'images/arrow-left-icon.svg');
                $('#left-arrow').css('background-color', '#C7C7C7');
                $('#right-arrow').attr('src', 'images/arrow-right-icon.svg');
                $('#right-arrow').css('background-color', '#C7C7C7');
            }
            scene._offset_X = INITIAL_OFFSET_X - hslider.value;
            draw();
        }

        function onVScroll() {
            scene._offset_Y = INITIAL_OFFSET_Y + Number(vslider.value);
            draw();
        }

        function onResize() {
            var dpr = window.devicePixelRatio || 1;
            var bsr = ctx.webkitBackingStorePixelRatio ||
                ctx.mozBackingStorePixelRatio ||
                ctx.msBackingStorePixelRatio ||
                ctx.oBackingStorePixelRatio ||
                ctx.backingStorePixelRatio || 1;
            var ratio = dpr / bsr;
            console.log('dpr/bsr =', ratio);
            var w = $('#canvas').width();
            var h = $('#canvas').height();
            canvas.width = w * ratio;
            canvas.height = h * ratio;
            ctx.setTransform(ratio, 0, 0, ratio, 0, 0);
            draw();
        }

        function draw() {
            ctx.clearRect(0, 0, 970, 370);
            scene.draw();
        }

        function clearScene() {
            scene._bins = [];
            scene._width = 0;
            scene._height = 0;
            scene._offset_X = INITIAL_OFFSET_X;
            scene._offset_Y = INITIAL_OFFSET_Y;
            hslider.max = 0;
            hslider.value = hslider.max;
            vslider.max = 0;
            vslider.min = 0;
            vslider.value = vslider.max;
            $('#hslider').css('display', 'none');
            $('#vslider').css('display', 'none');
            $('#show_alternate_words').css('display', 'none');
            $('#canvas').css('display', 'none');
            $('#canvas-placeholder').css('display', 'block');
            $('#left-arrow').css('display', 'none');
            $('#right-arrow').css('display', 'none');

            showAllHypotheses = true;
            $('#show_alternate_words').text('Hide alternate words');
            ctx.clearRect(0, 0, canvas.width, canvas.height);
        }

        function resetWorker() {
            runTimer = false;
            worker.postMessage({
                type: 'clear'
            });
            pushed = 0;
            popped = 0;
            console.log('---> resetWorker called');
        }

        $rootScope.on('clearscreen', function() {
            clearScene();
            resetWorker();
        });
    }
})();
<|endoftext|>


<commit_before>import React, { PropTypes } from 'react';
import { createStyleSheet } from 'stylishly/lib/styleSheet';
import ClassNames from 'classnames';

export const styleSheet = createStyleSheet('Paper', (theme) => {
  const { palette } = theme;
  const shadows = {};

  theme.shadows.forEach((shadow, index) => {
    shadows[`dp${index}`] = {
      boxShadow: shadow,
    };
  });

  return {
    root: {
      backgroundColor: palette.background.paper,
      '& rounded': {
        borderRadius: '2px',
      },
    },
    ...shadows,
  };
});

export default function Paper(props, context) {
  const { className, rounded, zDepth, ...other } = props;
  const classes = context.styleManager.render(styleSheet, { group: 'mui' });

  const classNames = ClassNames(classes.root, {
    [classes.rounded]: rounded,
    [classes[`dp${zDepth >= 0 ? zDepth : 0}`]]: true,
  }, className);

  return (

  );
}

Paper.propTypes = {
  className: PropTypes.string,
  rounded: PropTypes.bool,
  zDepth: PropTypes.number,
};

Paper.defaultProps = {
  rounded: true,
  zDepth: 2,
};

Paper.contextTypes = {
  styleManager: PropTypes.object.isRequired,
};
<commit_msg>[Paper] Add component docs/comments<commit_after>import React, { PropTypes } from 'react';
import { createStyleSheet } from 'stylishly/lib/styleSheet';
import ClassNames from 'classnames';

export const styleSheet = createStyleSheet('Paper', (theme) => {
  const { palette } = theme;
  const shadows = {};

  theme.shadows.forEach((shadow, index) => {
    shadows[`dp${index}`] = {
      boxShadow: shadow,
    };
  });

  return {
    root: {
      backgroundColor: palette.background.paper,
      '& rounded': {
        borderRadius: '2px',
      },
    },
    ...shadows,
  };
});

/**
 * A piece of material paper.
 *
 * ```js
 * import Paper from 'material-ui/Paper';
 *
 * const Component = () => <Paper zDepth={8}>Hello World</Paper>;
 * ```
 */
export default function Paper(props, context) {
  const { className, rounded, zDepth, ...other } = props;
  const classes = context.styleManager.render(styleSheet, { group: 'mui' });

  const classNames = ClassNames(classes.root, {
    [classes.rounded]: rounded,
    [classes[`dp${zDepth >= 0 ? zDepth : 0}`]]: true,
  }, className);

  return (

  );
}

Paper.propTypes = {
  /**
   * The CSS class name of the root element.
   */
  className: PropTypes.string,
  /**
   * Set to false to disable rounded corners
   */
  rounded: PropTypes.bool,
  /**
   * Shadow depth, corresponds to `dp` in the spec
   */
  zDepth: PropTypes.number,
};

Paper.defaultProps = {
  rounded: true,
  zDepth: 2,
};

Paper.contextTypes = {
  styleManager: PropTypes.object.isRequired,
};
<|endoftext|>


<commit_before>var DISCORD = (function(){
  var getTopMessageViewElement = function(){
    let view = DOM.queryReactClass("messages");
    return view && view.children.length && view.children[0];
  };

  var observerTimer = 0, waitingForCleanup = 0;

  return {
    /*
     * Sets up a callback hook to trigger whenever the list of messages is updated. The callback is given a boolean value that is true if there are more messages to load.
     */
    setupMessageUpdateCallback: function(callback){
      var onTimerFinished = function(){
        let topEle = getTopMessageViewElement();

        if (!topEle){
          restartTimer(500);
        }
        else if (!topEle.getAttribute("class").includes("loadingMore-")){
          let messages = DOM.queryReactClass("messages").children.length;

          if (messages < 100){
            waitingForCleanup = 0;
          }

          if (waitingForCleanup > 0){
            --waitingForCleanup;
            restartTimer(750);
          }
          else{
            if (messages > 300){
              waitingForCleanup = 6;

              DOM.setTimer(() => {
                let view = DOM.queryReactClass("messages");
                view.scrollTop = view.scrollHeight/2;
              }, 1);
            }

            callback(topEle.getAttribute("class").includes("hasMore-"));
            restartTimer(200);
          }
        }
        else{
          restartTimer(25);
        }
      };

      var restartTimer = function(delay){
        observerTimer = DOM.setTimer(onTimerFinished, delay);
      };

      onTimerFinished();
      window.DHT_ON_UNLOAD.push(() => window.clearInterval(observerTimer));
    },

    /*
     * Returns internal React state object of an element.
     */
    getReactProps: function(ele){
      var key = Object.keys(ele || {}).find(key => key.startsWith("__reactInternalInstance"));
      return key ? ele[key].memoizedProps : null;
    },

    /*
     * Returns an object containing the selected server name, selected channel name and ID, and the object type.
     * For types DM and GROUP, the server and channel names are identical.
     * For SERVER type, the channel has to be in view, otherwise Discord unloads it.
     */
    getSelectedChannel: function(){
      try{
        var obj;
        var channelListEle = document.querySelector("[class|='privateChannels']");

        if (channelListEle){
          var channel = DOM.queryReactClass("selected", channelListEle);

          if (!channel){
            return null;
          }

          var linkSplit = channel.querySelector("a[href*='/@me/']").href.split("/");
          var link = linkSplit[linkSplit.length-1];

          if (!(/^\d+$/.test(link))){
            return null;
          }

          var name = Array.prototype.find.call(channel.querySelector("[class|='name']").childNodes, node => node.nodeType === Node.TEXT_NODE).nodeValue;

          obj = {
            "server": name,
            "channel": name,
            "id": link,
            "type": !!DOM.queryReactClass("status", channel) ? "DM" : "GROUP"
          };
        }
        else{
          channelListEle = document.querySelector("[class|='channels']");

          var channel = channelListEle.querySelector("[class|='wrapperSelectedText']").parentElement;
          var props = DISCORD.getReactProps(channel);

          if (!props){
            return null;
          }

          var channelObj = props.children.props.channel;

          if (!channelObj){
            return null;
          }

          obj = {
            "server": channelListEle.querySelector("header > span").innerHTML,
            "channel": channelObj.name,
            "id": channelObj.id,
            "type": "SERVER"
          };
        }

        return obj.channel.length === 0 ? null : obj;
      }catch(e){
        return null;
      }
    },

    /*
     * Returns an array containing currently loaded messages.
     */
    getMessages: function(){
      var props = DISCORD.getReactProps(DOM.queryReactClass("messages"));
      var array = props && props.children.find(ele => ele && ele.length);
      var messages = [];

      if (array){
        for(let obj of array){
          if (obj.props.messages){
            Array.prototype.push.apply(messages, obj.props.messages);
          }
        }
      }

      return messages;
    },

    /*
     * Returns true if the message view is visible.
     */
    isInMessageView: () => !!DOM.queryReactClass("messages"),

    /*
     * Returns true if there are more messages available or if they're still loading.
     */
    hasMoreMessages: function(){
      let classes = getTopMessageViewElement().getAttribute("class");
      return classes.includes("hasMore-") || classes.includes("loadingMore-");
    },

    /*
     * Forces the message view to load older messages by scrolling all the way up.
     */
    loadOlderMessages: function(){
      let view = DOM.queryReactClass("messages");
      view.scrollTop = view.scrollHeight/2;
      view.scrollTop = 0;
    },

    /*
     * Selects the next text channel and returns true, otherwise returns false if there are no more channels.
     */
    selectNextTextChannel: function(){
      var dms = document.querySelector("[class|='privateChannels']");

      if (dms){
        var currentChannel = DOM.queryReactClass("selected", dms);
        var nextChannel = currentChannel && currentChannel.nextElementSibling;
        var nextLink = nextChannel && nextChannel.querySelector("a[href*='/@me/']");

        if (!nextChannel || !nextLink || !nextChannel.getAttribute("class").includes("channel-")){
          return false;
        }
        else{
          nextLink.click();
          nextChannel.scrollIntoView(true);
          return true;
        }
      }
      else{
        var isValidChannel = ele => ele.childElementCount > 0 && /wrapper([a-zA-Z]+?)Text/.test(ele.children[0].className);
        var allChannels = Array.prototype.filter.call(document.querySelector("[class|='channels']").querySelectorAll("[class|='containerDefault']"), isValidChannel);

        var nextChannel = null;

        for(var index = 0; index < allChannels.length-1; index++){
          if (allChannels[index].children[0].className.includes("wrapperSelectedText")){
            nextChannel = allChannels[index+1];
            break;
          }
        }

        if (nextChannel === null){
          return false;
        }
        else{
          nextChannel.children[0].click();
          nextChannel.scrollIntoView(true);
          return true;
        }
      }
    }
  };
})();
<commit_msg>Fix not finding messages after a Discord update<commit_after>var DISCORD = (function(){
  var getTopMessageViewElement = function(){
    let view = DOM.queryReactClass("messages");
    return view && view.children.length && view.children[0];
  };

  var observerTimer = 0, waitingForCleanup = 0;

  return {
    /*
     * Sets up a callback hook to trigger whenever the list of messages is updated. The callback is given a boolean value that is true if there are more messages to load.
     */
    setupMessageUpdateCallback: function(callback){
      var onTimerFinished = function(){
        let topEle = getTopMessageViewElement();

        if (!topEle){
          restartTimer(500);
        }
        else if (!topEle.getAttribute("class").includes("loadingMore-")){
          let messages = DOM.queryReactClass("messages").children.length;

          if (messages < 100){
            waitingForCleanup = 0;
          }

          if (waitingForCleanup > 0){
            --waitingForCleanup;
            restartTimer(750);
          }
          else{
            if (messages > 300){
              waitingForCleanup = 6;

              DOM.setTimer(() => {
                let view = DOM.queryReactClass("messages");
                view.scrollTop = view.scrollHeight/2;
              }, 1);
            }

            callback(topEle.getAttribute("class").includes("hasMore-"));
            restartTimer(200);
          }
        }
        else{
          restartTimer(25);
        }
      };

      var restartTimer = function(delay){
        observerTimer = DOM.setTimer(onTimerFinished, delay);
      };

      onTimerFinished();
      window.DHT_ON_UNLOAD.push(() => window.clearInterval(observerTimer));
    },

    /*
     * Returns internal React state object of an element.
     */
    getReactProps: function(ele){
      var key = Object.keys(ele || {}).find(key => key.startsWith("__reactInternalInstance"));
      return key ? ele[key].memoizedProps : null;
    },

    /*
     * Returns an object containing the selected server name, selected channel name and ID, and the object type.
     * For types DM and GROUP, the server and channel names are identical.
     * For SERVER type, the channel has to be in view, otherwise Discord unloads it.
     */
    getSelectedChannel: function(){
      try{
        var obj;
        var channelListEle = document.querySelector("[class|='privateChannels']");

        if (channelListEle){
          var channel = DOM.queryReactClass("selected", channelListEle);

          if (!channel){
            return null;
          }

          var linkSplit = channel.querySelector("a[href*='/@me/']").href.split("/");
          var link = linkSplit[linkSplit.length-1];

          if (!(/^\d+$/.test(link))){
            return null;
          }

          var name = Array.prototype.find.call(channel.querySelector("[class|='name']").childNodes, node => node.nodeType === Node.TEXT_NODE).nodeValue;

          obj = {
            "server": name,
            "channel": name,
            "id": link,
            "type": !!DOM.queryReactClass("status", channel) ? "DM" : "GROUP"
          };
        }
        else{
          channelListEle = document.querySelector("[class|='channels']");

          var channel = channelListEle.querySelector("[class|='wrapperSelectedText']").parentElement;
          var props = DISCORD.getReactProps(channel);

          if (!props){
            return null;
          }

          var channelObj = props.children.props.channel;

          if (!channelObj){
            return null;
          }

          obj = {
            "server": channelListEle.querySelector("header > span").innerHTML,
            "channel": channelObj.name,
            "id": channelObj.id,
            "type": "SERVER"
          };
        }

        return obj.channel.length === 0 ? null : obj;
      }catch(e){
        return null;
      }
    },

    /*
     * Returns an array containing currently loaded messages.
     */
    getMessages: function(){
      var props = DISCORD.getReactProps(DOM.queryReactClass("messages"));
      var array = props && props.children.find(ele => ele && ele.length);
      var messages = [];

      if (array){
        for(let obj of array){
          let nested = obj.props.children;

          if (nested && nested.props && nested.props.messages){
            Array.prototype.push.apply(messages, nested.props.messages);
          }
        }
      }

      return messages;
    },

    /*
     * Returns true if the message view is visible.
     */
    isInMessageView: () => !!DOM.queryReactClass("messages"),

    /*
     * Returns true if there are more messages available or if they're still loading.
     */
    hasMoreMessages: function(){
      let classes = getTopMessageViewElement().getAttribute("class");
      return classes.includes("hasMore-") || classes.includes("loadingMore-");
    },

    /*
     * Forces the message view to load older messages by scrolling all the way up.
     */
    loadOlderMessages: function(){
      let view = DOM.queryReactClass("messages");
      view.scrollTop = view.scrollHeight/2;
      view.scrollTop = 0;
    },

    /*
     * Selects the next text channel and returns true, otherwise returns false if there are no more channels.
     */
    selectNextTextChannel: function(){
      var dms = document.querySelector("[class|='privateChannels']");

      if (dms){
        var currentChannel = DOM.queryReactClass("selected", dms);
        var nextChannel = currentChannel && currentChannel.nextElementSibling;
        var nextLink = nextChannel && nextChannel.querySelector("a[href*='/@me/']");

        if (!nextChannel || !nextLink || !nextChannel.getAttribute("class").includes("channel-")){
          return false;
        }
        else{
          nextLink.click();
          nextChannel.scrollIntoView(true);
          return true;
        }
      }
      else{
        var isValidChannel = ele => ele.childElementCount > 0 && /wrapper([a-zA-Z]+?)Text/.test(ele.children[0].className);
        var allChannels = Array.prototype.filter.call(document.querySelector("[class|='channels']").querySelectorAll("[class|='containerDefault']"), isValidChannel);

        var nextChannel = null;

        for(var index = 0; index < allChannels.length-1; index++){
          if (allChannels[index].children[0].className.includes("wrapperSelectedText")){
            nextChannel = allChannels[index+1];
            break;
          }
        }

        if (nextChannel === null){
          return false;
        }
        else{
          nextChannel.children[0].click();
          nextChannel.scrollIntoView(true);
          return true;
        }
      }
    }
  };
})();
<|endoftext|>


<commit_before>'use strict';

var core = require('./core');
var chan = require('./channels');

var channelReducer = function(ch) {
  return {
    init: function() {
      return true;
    },
    result: function(open) {
      return open;
    },
    step: function(open, val) {
      return core.go(function*() {
        var success;

        open = yield open;
        val = yield val;
        if (val !== undefined)
          return yield ch.push(val);
        else
          return open;
      });
    }
  };
};

module.exports = function(xform, buf, isReduced, deref) {
  var ch = chan.chan(buf);
  var xf = xform(channelReducer(ch));
  var open = xf.init();

  return {
    push: function(val, handler) {
      return core.go(function*() {
        open = yield xf.step(open, val);
        if (isReduced(open)) {
          yield xf.result(yield deref(open));
          open = false;
          ch.close();
        };
        return open;
      });
    },

    pull: function(handler) {
      return ch.pull(handler);
    },

    close: function() {
      core.go(function*() {
        yield xf.result(open);
        open = false;
        ch.close();
      });
    }
  };
};
<commit_msg>further work on the tachan code<commit_after>'use strict';

var core = require('./core');
var chan = require('./channels');

var channelReducer = function(ch) {
  return {
    init: function() {
      return true;
    },
    result: function(t) {
      return t;
    },
    step: function(t, val) {
      return core.go(function*() {
        t = yield t;
        val = yield val;
        if (val !== undefined)
          return yield ch.push(val);
      });
    }
  };
};

module.exports = function(xform, buf, isReduced, deref) {
  var ch = chan.chan(buf);
  var open = true;
  var xf = xform(channelReducer(ch));
  xf.init();

  return {
    push: function(val, handler) {
      return core.go(function*() {
        if (open) {
          var result = yield xf.step(null, val);
          if (isReduced(result)) {
            yield deref(yield xf.result(yield deref(result)));
            open = false;
            ch.close();
          };
          return true;
        } else
          return false;
      });
    },

    pull: function(handler) {
      return ch.pull(handler);
    },

    close: function() {
      core.top(core.go(function*() {
        yield deref(yield xf.result());
        open = false;
        ch.close();
      }));
    }
  };
};
<|endoftext|>


<commit_before>// Backbone.NativeView.js 0.3.3
// ---------------

//     (c) 2015 Adam Krebs, Jimmy Yuen Ho Wong
//     Backbone.NativeView may be freely distributed under the MIT license.
//     For all details and documentation:
//     https://github.com/akre54/Backbone.NativeView

(function (factory) {
  if (typeof define === 'function' && define.amd) { define(['backbone'], factory);
  } else if (typeof module === 'object') { module.exports = factory(require('backbone'));
  } else { factory(Backbone); }
}(function (Backbone) {
  // Cached regex to match an opening '<' of an HTML tag, possibly left-padded
  // with whitespace.
  var paddedLt = /^\s*</;

  // Caches a local reference to `Element.prototype` for faster access.
  var ElementProto = (typeof Element !== 'undefined' && Element.prototype) || {};

  // Cross-browser event listener shims
  var elementAddEventListener = ElementProto.addEventListener || function(eventName, listener) {
    return this.attachEvent('on' + eventName, listener);
  }
  var elementRemoveEventListener = ElementProto.removeEventListener || function(eventName, listener) {
    return this.detachEvent('on' + eventName, listener);
  }

  var indexOf = function(array, item) {
    for (var i = 0, len = array.length; i < len; i++) if (array[i] === item) return i;
    return -1;
  }

  // Find the right `Element#matches` for IE>=9 and modern browsers.
  var matchesSelector = ElementProto.matches ||
      ElementProto.webkitMatchesSelector ||
      ElementProto.mozMatchesSelector ||
      ElementProto.msMatchesSelector ||
      ElementProto.oMatchesSelector ||
      // Make our own `Element#matches` for IE8
      function(selector) {
        // Use querySelectorAll to find all elements matching the selector,
        // then check if the given element is included in that list.
        // Executing the query on the parentNode reduces the resulting nodeList,
        // (document doesn't have a parentNode).
        var nodeList = (this.parentNode || document).querySelectorAll(selector) || [];
        return ~indexOf(nodeList, this);
      };

  // Cache Backbone.View for later access in constructor
  var BBView = Backbone.View;

  // To extend an existing view to use native methods, extend the View prototype
  // with the mixin: _.extend(MyView.prototype, Backbone.NativeViewMixin);
  Backbone.NativeViewMixin = {

    _domEvents: null,

    constructor: function() {
      this._domEvents = [];
      return BBView.apply(this, arguments);
    },

    $: function(selector) {
      return this.el.querySelectorAll(selector);
    },

    _removeElement: function() {
      this.undelegateEvents();
      if (this.el.parentNode) this.el.parentNode.removeChild(this.el);
    },

    // Apply the `element` to the view. `element` can be a CSS selector,
    // a string of HTML, or an Element node.
    _setElement: function(element) {
      if (typeof element == 'string') {
        if (paddedLt.test(element)) {
          var el = document.createElement('div');
          el.innerHTML = element;
          this.el = el.firstChild;
        } else {
          this.el = document.querySelector(element);
        }
      } else {
        this.el = element;
      }
    },

    // Set a hash of attributes to the view's `el`. We use the "prop" version
    // if available, falling back to `setAttribute` for the catch-all.
    _setAttributes: function(attrs) {
      for (var attr in attrs) {
        attr in this.el ? this.el[attr] = attrs[attr] : this.el.setAttribute(attr, attrs[attr]);
      }
    },

    // Make a event delegation handler for the given `eventName` and `selector`
    // and attach it to `this.el`.
    // If selector is empty, the listener will be bound to `this.el`. If not, a
    // new handler that will recursively traverse up the event target's DOM
    // hierarchy looking for a node that matches the selector. If one is found,
    // the event's `delegateTarget` property is set to it and the return the
    // result of calling bound `listener` with the parameters given to the
    // handler.
    delegate: function(eventName, selector, listener) {
      if (typeof selector === 'function') {
        listener = selector;
        selector = null;
      }

      var root = this.el;
      var handler = selector ? function (e) {
        var node = e.target || e.srcElement;
        for (; node && node != root; node = node.parentNode) {
          if (matchesSelector.call(node, selector)) {
            e.delegateTarget = node;
            listener(e);
          }
        }
      } : listener;

      elementAddEventListener.call(this.el, eventName, handler, false);
      this._domEvents.push({eventName: eventName, handler: handler, listener: listener, selector: selector});
      return handler;
    },

    // Remove a single delegated event. Either `eventName` or `selector` must
    // be included, `selector` and `listener` are optional.
    undelegate: function(eventName, selector, listener) {
      if (typeof selector === 'function') {
        listener = selector;
        selector = null;
      }

      if (this.el) {
        var handlers = this._domEvents.slice();
        for (var i = 0, len = handlers.length; i < len; i++) {
          var item = handlers[i];

          var match = item.eventName === eventName &&
              (listener ? item.listener === listener : true) &&
              (selector ? item.selector === selector : true);

          if (!match) continue;

          elementRemoveEventListener.call(this.el, item.eventName, item.handler, false);
          this._domEvents.splice(indexOf(handlers, item), 1);
        }
      }
      return this;
    },

    // Remove all events created with `delegate` from `el`
    undelegateEvents: function() {
      if (this.el) {
        for (var i = 0, len = this._domEvents.length; i < len; i++) {
          var item = this._domEvents[i];
          elementRemoveEventListener.call(this.el, item.eventName, item.handler, false);
        };
        this._domEvents.length = 0;
      }
      return this;
    }
  };

  Backbone.NativeView = Backbone.View.extend(Backbone.NativeViewMixin);

  return Backbone.NativeView;
}));
<commit_msg>Use i instead of indexOf function call for splice<commit_after>// Backbone.NativeView.js 0.3.3
// ---------------

//     (c) 2015 Adam Krebs, Jimmy Yuen Ho Wong
//     Backbone.NativeView may be freely distributed under the MIT license.
//     For all details and documentation:
//     https://github.com/akre54/Backbone.NativeView

(function (factory) {
  if (typeof define === 'function' && define.amd) { define(['backbone'], factory);
  } else if (typeof module === 'object') { module.exports = factory(require('backbone'));
  } else { factory(Backbone); }
}(function (Backbone) {
  // Cached regex to match an opening '<' of an HTML tag, possibly left-padded
  // with whitespace.
  var paddedLt = /^\s*</;

  // Caches a local reference to `Element.prototype` for faster access.
  var ElementProto = (typeof Element !== 'undefined' && Element.prototype) || {};

  // Cross-browser event listener shims
  var elementAddEventListener = ElementProto.addEventListener || function(eventName, listener) {
    return this.attachEvent('on' + eventName, listener);
  }
  var elementRemoveEventListener = ElementProto.removeEventListener || function(eventName, listener) {
    return this.detachEvent('on' + eventName, listener);
  }

  var indexOf = function(array, item) {
    for (var i = 0, len = array.length; i < len; i++) if (array[i] === item) return i;
    return -1;
  }

  // Find the right `Element#matches` for IE>=9 and modern browsers.
  var matchesSelector = ElementProto.matches ||
      ElementProto.webkitMatchesSelector ||
      ElementProto.mozMatchesSelector ||
      ElementProto.msMatchesSelector ||
      ElementProto.oMatchesSelector ||
      // Make our own `Element#matches` for IE8
      function(selector) {
        // Use querySelectorAll to find all elements matching the selector,
        // then check if the given element is included in that list.
        // Executing the query on the parentNode reduces the resulting nodeList,
        // (document doesn't have a parentNode).
        var nodeList = (this.parentNode || document).querySelectorAll(selector) || [];
        return ~indexOf(nodeList, this);
      };

  // Cache Backbone.View for later access in constructor
  var BBView = Backbone.View;

  // To extend an existing view to use native methods, extend the View prototype
  // with the mixin: _.extend(MyView.prototype, Backbone.NativeViewMixin);
  Backbone.NativeViewMixin = {

    _domEvents: null,

    constructor: function() {
      this._domEvents = [];
      return BBView.apply(this, arguments);
    },

    $: function(selector) {
      return this.el.querySelectorAll(selector);
    },

    _removeElement: function() {
      this.undelegateEvents();
      if (this.el.parentNode) this.el.parentNode.removeChild(this.el);
    },

    // Apply the `element` to the view. `element` can be a CSS selector,
    // a string of HTML, or an Element node.
    _setElement: function(element) {
      if (typeof element == 'string') {
        if (paddedLt.test(element)) {
          var el = document.createElement('div');
          el.innerHTML = element;
          this.el = el.firstChild;
        } else {
          this.el = document.querySelector(element);
        }
      } else {
        this.el = element;
      }
    },

    // Set a hash of attributes to the view's `el`. We use the "prop" version
    // if available, falling back to `setAttribute` for the catch-all.
    _setAttributes: function(attrs) {
      for (var attr in attrs) {
        attr in this.el ? this.el[attr] = attrs[attr] : this.el.setAttribute(attr, attrs[attr]);
      }
    },

    // Make a event delegation handler for the given `eventName` and `selector`
    // and attach it to `this.el`.
    // If selector is empty, the listener will be bound to `this.el`. If not, a
    // new handler that will recursively traverse up the event target's DOM
    // hierarchy looking for a node that matches the selector. If one is found,
    // the event's `delegateTarget` property is set to it and the return the
    // result of calling bound `listener` with the parameters given to the
    // handler.
    delegate: function(eventName, selector, listener) {
      if (typeof selector === 'function') {
        listener = selector;
        selector = null;
      }

      var root = this.el;
      var handler = selector ? function (e) {
        var node = e.target || e.srcElement;
        for (; node && node != root; node = node.parentNode) {
          if (matchesSelector.call(node, selector)) {
            e.delegateTarget = node;
            listener(e);
          }
        }
      } : listener;

      elementAddEventListener.call(this.el, eventName, handler, false);
      this._domEvents.push({eventName: eventName, handler: handler, listener: listener, selector: selector});
      return handler;
    },

    // Remove a single delegated event. Either `eventName` or `selector` must
    // be included, `selector` and `listener` are optional.
    undelegate: function(eventName, selector, listener) {
      if (typeof selector === 'function') {
        listener = selector;
        selector = null;
      }

      if (this.el) {
        var handlers = this._domEvents.slice();
        for (var i = 0, len = handlers.length; i < len; i++) {
          var item = handlers[i];

          var match = item.eventName === eventName &&
              (listener ? item.listener === listener : true) &&
              (selector ? item.selector === selector : true);

          if (!match) continue;

          elementRemoveEventListener.call(this.el, item.eventName, item.handler, false);
          this._domEvents.splice(i, 1);
        }
      }
      return this;
    },

    // Remove all events created with `delegate` from `el`
    undelegateEvents: function() {
      if (this.el) {
        for (var i = 0, len = this._domEvents.length; i < len; i++) {
          var item = this._domEvents[i];
          elementRemoveEventListener.call(this.el, item.eventName, item.handler, false);
        };
        this._domEvents.length = 0;
      }
      return this;
    }
  };

  Backbone.NativeView = Backbone.View.extend(Backbone.NativeViewMixin);

  return Backbone.NativeView;
}));
<|endoftext|>


<commit_before>var TextBooks = {
    term: undefined,

    show_books: function(term, textbook) {
        //Navbar.render_navbar("nav-sub");
        TextBooks.term = term;
        showLoading();
        CommonLoading.render_init();
        WSData.fetch_book_data(term, TextBooks.render_books, TextBooks.render_error);
        WSData.fetch_course_data_for_term(TextBooks.term, TextBooks.render_books, TextBooks.render_error);
        WSData.fetch_instructed_course_data_for_term(TextBooks.term, TextBooks.render_books, TextBooks.render_error);
    },

    render_error: function() {
        var book_err_status = WSData.book_data_error_code(TextBooks.term);
        var course_err_status = WSData.course_data_error_code(TextBooks.term);
        var instructed_course_err_status = WSData.instructed_course_data_error_code(TextBooks.term);

        if (book_err_status === 543 || course_err_status === 543 || instructed_course_err_status === 543) {
            var raw = CardWithError.render("Textbooks");
            $("#main-content").html(raw);
        } else {
            TextBooks.render_books();
        }
    },

    process_book_data: function(book_data, course_data, instructed_course_data) {
        var template_data = {
            "teaching_sections": [],
            "enrolled_sections": [],
            "quarter": course_data ? course_data.quarter : instructed_course_data.quarter,
            "year": course_data ? course_data.year: instructed_course_data.year,
            "summer_term": course_data ? course_data.summer_term : instructed_course_data.summer_term
        };

        var section_data = function (i, section, instructor) {
            return {
                index: i,
                section_title: section.course_title,
                curriculum: section.curriculum_abbr,
                course_number: section.course_number,
                section_id: section.section_id,
                color_id: section.color_id,
                sln: section.sln,
                books: book_data ? book_data[section.sln] : [],
                has_books: book_data ? (book_data[section.sln].length > 0) : false,
                is_instructor: instructor,
                bothell_campus: section.course_campus.toLowerCase() === 'bothell',
                tacoma_campus: section.course_campus.toLowerCase() === 'tacoma'
            };
        };

        if (course_data) {
            $.each(course_data.sections, function (index) {
                var section = section_data(index, this, false);
                template_data.enrolled_sections.push(section);
            });
        }

        if (myuwFeatureEnabled('instructor_textbooks') && instructed_course_data) {
            $.each(instructed_course_data.sections, function (index) {
                var section = section_data(index, this, true);
                template_data.teaching_sections.push(section);
            });
        }

        // Determine if we need to collapse the textbook sections and whether the user is teaching
        var num_sections = template_data.enrolled_sections.length + template_data.teaching_sections.length;
        template_data.collapse_sections = num_sections > 10;
        template_data.is_teaching = template_data.teaching_sections.length > 0;

        template_data.verba_link = book_data ? book_data.verba_link : null;
        return template_data;
    },

    _has_all_data: function () {
        var book_data = WSData.book_data(TextBooks.term);
        var course_data = WSData.course_data_for_term(TextBooks.term);
        var instructed_course_data = WSData.instructed_course_data_for_term(TextBooks.term);
        var book_data_err_status = WSData.course_data_error_code(TextBooks.term);
        var course_err_status = WSData.course_data_error_code(TextBooks.term);
        var instructed_course_err_status = WSData.instructed_course_data_error_code(TextBooks.term);

        return ((book_data ||
                 (book_data === undefined && book_data_err_status === 404)) &&
                (course_data ||
                 (course_data === undefined && course_err_status === 404)) &&
                (instructed_course_data ||
                 (instructed_course_data === undefined && instructed_course_err_status === 404)));
    },

    render_books: function() {
        if(!TextBooks._has_all_data()){
            return;
        }
        var term = TextBooks.term;
        $('html,body').animate({scrollTop: 0}, 'fast');
        var source   = $("#textbooks").html();
        var template = Handlebars.compile(source);
        var template_data = TextBooks.process_book_data(WSData.book_data(term),
                                                        WSData.course_data_for_term(term),
                                                        WSData.instructed_course_data_for_term(term));
        if (template_data !== undefined){
            $("#main-content").html(template(template_data));
        }

        // Scroll to correct section
        element = $("a[name='" + location.hash.substring(1) + "']");
        if (element.length > 0) {
                $('html, body').animate({
                scrollTop: element.offset().top
            });
        }
    }
};

/* node.js exports */
if (typeof exports == "undefined") {
    var exports = {};
}
exports.TextBooks = TextBooks;
<commit_msg>Fixed bug with future quarter textbooks<commit_after>var TextBooks = {
    term: undefined,

    show_books: function(term, textbook) {
        //Navbar.render_navbar("nav-sub");
        TextBooks.term = term;
        showLoading();
        CommonLoading.render_init();
        WSData.fetch_book_data(term, TextBooks.render_books, TextBooks.render_error);
        WSData.fetch_course_data_for_term(TextBooks.term, TextBooks.render_books, TextBooks.render_error);
        WSData.fetch_instructed_course_data_for_term(TextBooks.term, TextBooks.render_books, TextBooks.render_error);
    },

    render_error: function() {
        var book_err_status = WSData.book_data_error_code(TextBooks.term);
        var course_err_status = WSData.course_data_error_code(TextBooks.term);
        var instructed_course_err_status = WSData.instructed_course_data_error_code(TextBooks.term);

        if (book_err_status === 543 || course_err_status === 543 || instructed_course_err_status === 543) {
            var raw = CardWithError.render("Textbooks");
            $("#main-content").html(raw);
        } else {
            TextBooks.render_books();
        }
    },

    process_book_data: function(book_data, course_data, instructed_course_data) {
        var template_data = {
            "teaching_sections": [],
            "enrolled_sections": [],
            "sections": [],
            "quarter": course_data ? course_data.quarter : instructed_course_data.quarter,
            "year": course_data ? course_data.year: instructed_course_data.year,
            "summer_term": course_data ? course_data.summer_term : instructed_course_data.summer_term
        };

        var section_data = function (i, section, instructor) {
            return {
                index: i,
                section_title: section.course_title,
                curriculum: section.curriculum_abbr,
                course_number: section.course_number,
                section_id: section.section_id,
                color_id: section.color_id,
                sln: section.sln,
                books: book_data ? book_data[section.sln] : [],
                has_books: book_data ? (book_data[section.sln].length > 0) : false,
                is_instructor: instructor,
                bothell_campus: section.course_campus.toLowerCase() === 'bothell',
                tacoma_campus: section.course_campus.toLowerCase() === 'tacoma'
            };
        };

        if (course_data) {
            $.each(course_data.sections, function (index) {
                var section = section_data(index, this, false);
                template_data.enrolled_sections.push(section);
                template_data.sections.push(section);
            });
        }

        if (myuwFeatureEnabled('instructor_textbooks') && instructed_course_data) {
            $.each(instructed_course_data.sections, function (index) {
                var section = section_data(index, this, true);
                template_data.teaching_sections.push(section);
                template_data.sections.push(section);
            });
        }

        // Determine if we need to collapse the textbook sections and whether the user is teaching
        var num_sections = template_data.enrolled_sections.length + template_data.teaching_sections.length;
        template_data.collapse_sections = num_sections > 10;
        template_data.is_teaching = template_data.teaching_sections.length > 0;

        template_data.verba_link = book_data ? book_data.verba_link : null;
        return template_data;
    },

    _has_all_data: function () {
        var book_data = WSData.book_data(TextBooks.term);
        var course_data = WSData.course_data_for_term(TextBooks.term);
        var instructed_course_data = WSData.instructed_course_data_for_term(TextBooks.term);
        var book_data_err_status = WSData.course_data_error_code(TextBooks.term);
        var course_err_status = WSData.course_data_error_code(TextBooks.term);
        var instructed_course_err_status = WSData.instructed_course_data_error_code(TextBooks.term);

        return ((book_data ||
                 (book_data === undefined && book_data_err_status === 404)) &&
                (course_data ||
                 (course_data === undefined && course_err_status === 404)) &&
                (instructed_course_data ||
                 (instructed_course_data === undefined && instructed_course_err_status === 404)));
    },

    render_books: function() {
        if(!TextBooks._has_all_data()){
            return;
        }
        var term = TextBooks.term;
        $('html,body').animate({scrollTop: 0}, 'fast');
        var source   = $("#textbooks").html();
        var template = Handlebars.compile(source);
        var template_data = TextBooks.process_book_data(WSData.book_data(term),
                                                        WSData.course_data_for_term(term),
                                                        WSData.instructed_course_data_for_term(term));
        if (template_data !== undefined){
            $("#main-content").html(template(template_data));
        }

        // Scroll to correct section
        element = $("a[name='" + location.hash.substring(1) + "']");
        if (element.length > 0) {
                $('html, body').animate({
                scrollTop: element.offset().top
            });
        }
    }
};

/* node.js exports */
if (typeof exports == "undefined") {
    var exports = {};
}
exports.TextBooks = TextBooks;
<|endoftext|>


<commit_before>
describe("oboe integration (real http)", function(){

   it('gets all expected callbacks by time request finishes',  function() {

      var asserter = givenAnOboeInstance('/testServer/tenSlowNumbers')
         .andWeAreListeningForNodes('![*]');

      waitsFor( asserter.toComplete(), 'the request to complete', ASYNC_TEST_TIMEOUT);

      runs(function(){

         asserter.thenTheInstance(
             matched(0).atPath([0])
         ,   matched(1).atPath([1])
         ,   matched(2).atPath([2])
         ,   matched(3).atPath([3])
         ,   matched(4).atPath([4])
         ,   matched(5).atPath([5])
         ,   matched(6).atPath([6])
         ,   matched(7).atPath([7])
         ,   matched(8).atPath([8])
         ,   matched(9).atPath([9])
         );
      });
   })

   it('can abort once some data has been found in streamed response',  function() {

      var aborted = false;

      var asserter = givenAnOboeInstance('/testServer/tenSlowNumbers')
         .andWeAreListeningForNodes('![5]', function(){
             asserter.andWeAbortTheRequest();
             aborted = true;
          });

      waitsFor( function(){return aborted}, 'the request to be aborted', ASYNC_TEST_TIMEOUT);

      // in case we didn't abort, wait a little longer. If we didn't really abort we'd get the
      // rest of the data now and the test would fail:
      waitsFor( someSecondsToPass(3), ASYNC_TEST_TIMEOUT);

      runs( function(){
         asserter.thenTheInstance(
            // because the request was aborted on index array 5, we got 6 numbers (inc zero)
            // not the whole ten.
            hasRootJson([0,1,2,3,4,5])
         );
      });
   })

   it('can abort once some data has been found in not very streamed response',  function() {

      // like above but we're getting a static file not the streamed numbers. This means
      // we'll almost certainly read in the whole response as one onprogress it is on localhost
      // and the json is very small

      var aborted = false;

      var asserter = givenAnOboeInstance('/testServer/static/json/firstTenNaturalNumbers.json')
         .andWeAreListeningForNodes('![5]', function(){
             asserter.andWeAbortTheRequest();
             aborted = true;
          });

      waitsFor( function(){return aborted}, 'the request to be aborted', ASYNC_TEST_TIMEOUT);

      // in case we didn't abort, wait a little longer. If we didn't really abort we'd get the
      // rest of the data now and the test would fail:
      waitsFor( someSecondsToPass(1), ASYNC_TEST_TIMEOUT);

      runs( function(){
         asserter.thenTheInstance(
            // because the request was aborted on index array 5, we got 6 numbers (inc zero)
            // not the whole ten.
            hasRootJson([0,1,2,3,4,5])
         );
      });
   })

   it('gives full json to callback when request finishes',  function( queue ) {

      var fullResponse = null;

      oboe.doGet(
         '/testServer/static/json/firstTenNaturalNumbers.json',

         function ajaxFinished(obj) {
            fullResponse = obj;
         }
      );

      waitsFor( function(){ return !!fullResponse }, 'the request to complete', ASYNC_TEST_TIMEOUT )

      runs( function(){
         expect(fullResponse).toEqual([0,1,2,3,4,5,6,7,8,9])
      });
   })

   it('gives header to server side',  function( queue ) {

      var done = false;

      oboe.doGet(
         {  url: '/testServer/echoBackHeaders',
            headers: {'snarfu':'SNARF'}
         }
      ).onNode( 'snarfu', function( headerValue ){
         expect( headerValue ).toBe( 'SNARF' )
         done = true;
      })

      waitsFor( function(){ return done }, 'the request to complete', ASYNC_TEST_TIMEOUT )
   })

   function someSecondsToPass(waitSecs) {

      function now(){
         // IE8 doesn't have Date.now()
         return new Date().getTime();
      }

      var waitStart = now(),
          waitMs = waitSecs * 1000;

      return function(){
         return now() > (waitStart + waitMs);
      }
   }

});

<commit_msg>test with 2 headers instead of 1<commit_after>
describe("oboe integration (real http)", function(){

   it('gets all expected callbacks by time request finishes',  function() {

      var asserter = givenAnOboeInstance('/testServer/tenSlowNumbers')
         .andWeAreListeningForNodes('![*]');

      waitsFor( asserter.toComplete(), 'the request to complete', ASYNC_TEST_TIMEOUT);

      runs(function(){

         asserter.thenTheInstance(
             matched(0).atPath([0])
         ,   matched(1).atPath([1])
         ,   matched(2).atPath([2])
         ,   matched(3).atPath([3])
         ,   matched(4).atPath([4])
         ,   matched(5).atPath([5])
         ,   matched(6).atPath([6])
         ,   matched(7).atPath([7])
         ,   matched(8).atPath([8])
         ,   matched(9).atPath([9])
         );
      });
   })

   it('can abort once some data has been found in streamed response',  function() {

      var aborted = false;

      var asserter = givenAnOboeInstance('/testServer/tenSlowNumbers')
         .andWeAreListeningForNodes('![5]', function(){
             asserter.andWeAbortTheRequest();
             aborted = true;
          });

      waitsFor( function(){return aborted}, 'the request to be aborted', ASYNC_TEST_TIMEOUT);

      // in case we didn't abort, wait a little longer. If we didn't really abort we'd get the
      // rest of the data now and the test would fail:
      waitsFor( someSecondsToPass(3), ASYNC_TEST_TIMEOUT);

      runs( function(){
         asserter.thenTheInstance(
            // because the request was aborted on index array 5, we got 6 numbers (inc zero)
            // not the whole ten.
            hasRootJson([0,1,2,3,4,5])
         );
      });
   })

   it('can abort once some data has been found in not very streamed response',  function() {

      // like above but we're getting a static file not the streamed numbers. This means
      // we'll almost certainly read in the whole response as one onprogress it is on localhost
      // and the json is very small

      var aborted = false;

      var asserter = givenAnOboeInstance('/testServer/static/json/firstTenNaturalNumbers.json')
         .andWeAreListeningForNodes('![5]', function(){
             asserter.andWeAbortTheRequest();
             aborted = true;
          });

      waitsFor( function(){return aborted}, 'the request to be aborted', ASYNC_TEST_TIMEOUT);

      // in case we didn't abort, wait a little longer. If we didn't really abort we'd get the
      // rest of the data now and the test would fail:
      waitsFor( someSecondsToPass(1), ASYNC_TEST_TIMEOUT);

      runs( function(){
         asserter.thenTheInstance(
            // because the request was aborted on index array 5, we got 6 numbers (inc zero)
            // not the whole ten.
            hasRootJson([0,1,2,3,4,5])
         );
      });
   })

   it('gives full json to callback when request finishes',  function( queue ) {

      var fullResponse = null;

      oboe.doGet(
         '/testServer/static/json/firstTenNaturalNumbers.json',

         function ajaxFinished(obj) {
            fullResponse = obj;
         }
      );

      waitsFor( function(){ return !!fullResponse }, 'the request to complete', ASYNC_TEST_TIMEOUT )

      runs( function(){
         expect(fullResponse).toEqual([0,1,2,3,4,5,6,7,8,9])
      });
   })

   it('gives header to server side',  function( queue ) {

      var countGotBack = 0;

      oboe.doGet(
         {  url: '/testServer/echoBackHeaders',
            headers: {'x-snarfu':'SNARF', 'x-foo':'BAR'}
         }

      ).onNode( 'x-snarfu', function( headerValue ){

         expect( headerValue ).toBe( 'SNARF' )
         countGotBack++;

      }).onNode( 'x-foo', function( headerValue ){

         expect( headerValue ).toBe( 'BAR' )
         countGotBack++;
      })

      waitsFor( function(){ return countGotBack == 2 }, 'the request to complete', ASYNC_TEST_TIMEOUT )
   })

   function someSecondsToPass(waitSecs) {

      function now(){
         // IE8 doesn't have Date.now()
         return new Date().getTime();
      }

      var waitStart = now(),
          waitMs = waitSecs * 1000;

      return function(){
         return now() > (waitStart + waitMs);
      }
   }

});

<|endoftext|>


<commit_before>import $ from '$';
import _ from 'lodash';
import loginModal from './login';
import changePasswordModal from './change-password';
export default /*@ngInject*/class user{
  constructor($http,$localStorage,$window,$q,$uibModal, api){
    this.api = api;
    this.$http = $http;
    this.$storage = $localStorage;
    this.$q = $q;
    this.$window = $window;
    this.$uibModal = $uibModal;
    this.data = null;
    this.username = $('body').attr('username');
    this.init();
  }
  check(value){
    const config = {
      method: 'HEAD',
      url: 'api/users',
      params: {}
    };
    config.params[this.username] = value;
    return this.$http(config);
  }
  get authenticated(){
    return this.$storage._id && this.$storage.token ? true : false;
  }
  set authenticated(data){
    this.$storage.token = data.token;
    this.$storage._id = data._id;
    this.$storage.role = data.role;
    this.api.setToken(data.token);
  }
  get _id(){
    return this.$storage._id;
  }
  get token(){
    return this.$storage.token;
  }
  get role(){
    return this.$storage.role;
  }
  init(){
    if(!this.authenticated){
      return this.inauthenticate();
    }
    return this.$http.get('api/users/'+this.$storage._id)
    .then(
      (res) => {
        this.data = res.data;
        return res;
      },
      (e) => {
        this.inauthenticate();
        return e;
      }
    );
  }
  basicAuth(form){
    const name = form[this.username];
    const pass = form.password;
    if(!name || !pass){
      throw Error('missing credentials');
    }
    const str = this.$window.btoa(`${name}:${pass}`);
    return `basic ${str}`;
  }
  authenticate(form,init){
    const authorization = this.basicAuth(form);
    const config = {
      method: 'GET',
      url: 'api/tokens/new',
      headers: {authorization}
    };
    return this.$http(config)
    .then((res) => {
      this.authenticated = res.data;
      if(!init){
        return res.data;
      }
      return this.init();
    });
  }
  inauthenticate(){
    this.$storage.$reset();
    this.data = null;
    this.api.token.value = null;
  }
  create(form){
    return this.$http.post('api/users', form)
    .then((res) => {
      this.data = res.data;
      return res;
    });
  }
  addUnit(id){
    const included = _.includes(this.data.done,id);
    if(included){
      return;
    }
    const item = {
      unit: id
    };
    this.$http.post('api/users/'+this.data._id+'/done', item)
    .then((res) => {
      this.data.done.push(res.data);
    });
  }
  complete(unit){
    if(!this.authenticated){
      return false;
    }
    return _.some(this.data.done,{unit: unit});
  }
  requiresComplete(requires){
    if(!this.authenticated){
      return false;
    }
    return _.every(requires,function(val){
      return _.some(this.data.done,{unit: val});
    },this);
  }
  remove(){
    return this.$http.delete('api/users/'+this.data._id)
    .then(() => this.inauthenticate());
  }
  login(){
    return this.$uibModal.open(loginModal);
  }
  changePassword(){
    return this.$uibModal.open(changePasswordModal);
  }
}
<commit_msg>Fix user.addUnit method in client user service<commit_after>import $ from '$';
import _ from 'lodash';
import loginModal from './login';
import changePasswordModal from './change-password';
export default /*@ngInject*/class user{
  constructor($http,$localStorage,$window,$q,$uibModal, api){
    this.api = api;
    this.$http = $http;
    this.$storage = $localStorage;
    this.$q = $q;
    this.$window = $window;
    this.$uibModal = $uibModal;
    this.data = null;
    this.username = $('body').attr('username');
    this.init();
  }
  check(value){
    const config = {
      method: 'HEAD',
      url: 'api/users',
      params: {}
    };
    config.params[this.username] = value;
    return this.$http(config);
  }
  get authenticated(){
    return this.$storage._id && this.$storage.token ? true : false;
  }
  set authenticated(data){
    this.$storage.token = data.token;
    this.$storage._id = data._id;
    this.$storage.role = data.role;
    this.api.setToken(data.token);
  }
  get _id(){
    return this.$storage._id;
  }
  get token(){
    return this.$storage.token;
  }
  get role(){
    return this.$storage.role;
  }
  init(){
    if(!this.authenticated){
      return this.inauthenticate();
    }
    return this.$http.get('api/users/'+this.$storage._id)
    .then(
      (res) => {
        this.data = res.data;
        return res;
      },
      (e) => {
        this.inauthenticate();
        return e;
      }
    );
  }
  basicAuth(form){
    const name = form[this.username];
    const pass = form.password;
    if(!name || !pass){
      throw Error('missing credentials');
    }
    const str = this.$window.btoa(`${name}:${pass}`);
    return `basic ${str}`;
  }
  authenticate(form,init){
    const authorization = this.basicAuth(form);
    const config = {
      method: 'GET',
      url: 'api/tokens/new',
      headers: {authorization}
    };
    return this.$http(config)
    .then((res) => {
      this.authenticated = res.data;
      if(!init){
        return res.data;
      }
      return this.init();
    });
  }
  inauthenticate(){
    this.$storage.$reset();
    this.data = null;
    this.api.token.value = null;
  }
  create(form){
    return this.$http.post('api/users', form)
    .then((res) => {
      this.data = res.data;
      return res;
    });
  }
  addUnit(id){
    if(_.some(this.data.done,{unit: id})){
      return;
    }
    const item = {
      unit: id
    };
    const config = {
      method: 'POST',
      url: `api/users/${this.data._id}/done`,
      data: item
    };
    this.$http(config)
    .then((res) => {
      this.data.done.push(res.data);
    });
  }
  complete(unit){
    if(!this.authenticated){
      return false;
    }
    return _.some(this.data.done,{unit: unit});
  }
  requiresComplete(requires){
    if(!this.authenticated){
      return false;
    }
    return _.every(requires, _.bind(function(val){
      return _.some(this.data.done,{unit: val});
    },this));
  }
  remove(){
    return this.$http.delete('api/users/'+this.data._id)
    .then(() => this.inauthenticate());
  }
  login(){
    return this.$uibModal.open(loginModal);
  }
  changePassword(){
    return this.$uibModal.open(changePasswordModal);
  }
}
<|endoftext|>


<commit_before>var mongoose = require('mongoose-q')(require('mongoose')),
    Schema = mongoose.Schema,
    config = require('../../config/config'),
    jwt = require('jwt-simple'),
    moment = require('moment');

var userSchema = new Schema({
    email: {
        type: String,
        required: 'you should provide an email',
        match: [/^\w+([\.-]?\w+)*@\w+([\.-]?\w+)*(\.\w{2,3})+$/, "You should provide a valid email"],
        unique: true
    },
    studentEmail: {
        type: String,
        match: [/^\w+([\.-]?\w+)*@\w+([\.-]?\w+)*(\.\w{2,3})+$/, "You should provide a valid email"]
    },
    firstname: {
        type: String
    },
    lastname: {
        type: String
    },
    createdAt: {
        type: Date,
        default: Date.now
    }
});

userSchema.methods.createJwtToken = function () {
    var payload = {
        user: this,
        iat: moment().valueOf(),
        exp: moment().add(7, 'days').valueOf()
    };
    return jwt.encode(payload, config.TOKEN_SECRET);
};

userSchema.methods.authData = function () {
    return {
        user: this,
        token: this.createJwtToken()
    };
};

userSchema.statics.authenticate = function (email) {
    return User
        .findByEmail(email)
        .then(function (user) {
            if (!user) { // no existing user, create a new one and return it
                return new User({email: email}).saveQ();
            } else {
                return user;
            }
        })
        .then(function (user) {
            return user.authData();
        });
};

userSchema.statics.findByEmail = function (email) {
    return User.findOne({email: email}).execQ();
};

var User = mongoose.model('User', userSchema);

module.exports = User;
<commit_msg>Remove unique on studentId<commit_after>var mongoose = require('mongoose-q')(require('mongoose')),
    Schema = mongoose.Schema,
    config = require('../../config/config'),
    jwt = require('jwt-simple'),
    moment = require('moment');

var userSchema = new Schema({
    email: {
        type: String,
        required: 'you should provide an email',
        match: [/^\w+([\.-]?\w+)*@\w+([\.-]?\w+)*(\.\w{2,3})+$/, "You should provide a valid email"],
        unique: true
    },
    studentEmail: {
        type: String,
        match: [/^\w+([\.-]?\w+)*@\w+([\.-]?\w+)*(\.\w{2,3})+$/, "You should provide a valid email"],
        unique: true
    },
    firstname: {
        type: String
    },
    lastname: {
        type: String
    },
    createdAt: {
        type: Date,
        default: Date.now
    }
});

userSchema.methods.createJwtToken = function () {
    var payload = {
        user: this,
        iat: moment().valueOf(),
        exp: moment().add(7, 'days').valueOf()
    };
    return jwt.encode(payload, config.TOKEN_SECRET);
};

userSchema.methods.authData = function () {
    return {
        user: this,
        token: this.createJwtToken()
    };
};

userSchema.statics.authenticate = function (email) {
    return User
        .findByEmail(email)
        .then(function (user) {
            if (!user) { // no existing user, create a new one and return it
                return new User({email: email, studentEmail: email}).saveQ();
            } else {
                return user;
            }
        })
        .then(function (user) {
            return user.authData();
        });
};

userSchema.statics.findByEmail = function (email) {
    return User.findOne({email: email}).execQ();
};

var User = mongoose.model('User', userSchema);

module.exports = User;
<|endoftext|>


<commit_before>/*
  backgrid-select-all
  http://github.com/wyuenho/backgrid

  Copyright (c) 2013 Jimmy Yuen Ho Wong and contributors
  Licensed under the MIT @license.
*/
(function (root, factory) {

  // CommonJS
  if (typeof exports == "object") {
    module.exports = factory(require("backbone"), require("backgrid"));
  }
  // Browser
  else factory(root.Backbone, root.Backgrid);

}(this, function (Backbone, Backgrid) {

  "use strict";

  /**
     Renders a checkbox for row selection.

     @class Backgrid.Extension.SelectRowCell
     @extends Backbone.View
  */
  var SelectRowCell = Backgrid.Extension.SelectRowCell = Backbone.View.extend({

    /** @property */
    className: "select-row-cell",

    /** @property */
    tagName: "td",

    /** @property */
    events: {
      "keydown input[type=checkbox]": "onKeydown",
      "change input[type=checkbox]": "onChange",
      "click input[type=checkbox]": "enterEditMode"
    },

    /**
       Initializer. If the underlying model triggers a `select` event, this cell
       will change its checked value according to the event's `selected` value.

       @param {Object} options
       @param {Backgrid.Column} options.column
       @param {Backbone.Model} options.model
    */
    initialize: function (options) {

      this.column = options.column;
      if (!(this.column instanceof Backgrid.Column)) {
        this.column = new Backgrid.Column(this.column);
      }

      var column = this.column, model = this.model, $el = this.$el;
      this.listenTo(column, "change:renderable", function (column, renderable) {
        $el.toggleClass("renderable", renderable);
      });

      if (Backgrid.callByNeed(column.renderable(), column, model)) $el.addClass("renderable");

      this.listenTo(model, "backgrid:select", function (model, selected) {
        this.checkbox().prop("checked", selected).change();
      });
    },

    /**
       Returns the checkbox.
     */
    checkbox: function () {
      return this.$el.find("input[type=checkbox]");
    },

    /**
       Focuses the checkbox.
    */
    enterEditMode: function () {
      this.checkbox().focus();
    },

    /**
       Unfocuses the checkbox.
    */
    exitEditMode: function () {
      this.checkbox().blur();
    },

    /**
       Process keyboard navigation.
    */
    onKeydown: function (e) {
      var command = new Backgrid.Command(e);
      if (command.passThru()) return true; // skip ahead to `change`
      if (command.cancel()) {
        e.stopPropagation();
        this.checkbox().blur();
      }
      else if (command.save() || command.moveLeft() || command.moveRight() ||
               command.moveUp() || command.moveDown()) {
        e.preventDefault();
        e.stopPropagation();
        this.model.trigger("backgrid:edited", this.model, this.column, command);
      }
    },

    /**
       When the checkbox's value changes, this method will trigger a Backbone
       `backgrid:selected` event with a reference of the model and the
       checkbox's `checked` value.
    */
    onChange: function () {
      var checked = this.checkbox().prop("checked");
      this.$el.parent().toggleClass("selected", checked);
      this.model.trigger("backgrid:selected", this.model, checked);
    },

    /**
       Renders a checkbox in a table cell.
    */
    render: function () {
      this.$el.empty().append('<input tabindex="-1" type="checkbox" />');
      this.delegateEvents();
      return this;
    }

  });

  /**
     Renders a checkbox to select all rows on the current page.

     @class Backgrid.Extension.SelectAllHeaderCell
     @extends Backgrid.Extension.SelectRowCell
  */
  var SelectAllHeaderCell = Backgrid.Extension.SelectAllHeaderCell = SelectRowCell.extend({

    /** @property */
    className: "select-all-header-cell",

    /** @property */
    tagName: "th",

    /**
       Initializer. When this cell's checkbox is checked, a Backbone
       `backgrid:select` event will be triggered for each model for the current
       page in the underlying collection. If a `SelectRowCell` instance exists
       for the rows representing the models, they will check themselves. If any
       of the SelectRowCell instances trigger a Backbone `backgrid:selected`
       event with a `false` value, this cell will uncheck its checkbox. In the
       event of a Backbone `backgrid:refresh` event, which is triggered when the
       body refreshes its rows, which can happen under a number of conditions
       such as paging or the columns were reset, this cell will still remember
       the previously selected models and trigger a Backbone `backgrid:select`
       event on them such that the SelectRowCells can recheck themselves upon
       refreshing.

       @param {Object} options
       @param {Backgrid.Column} options.column
       @param {Backbone.Collection} options.collection
    */
    initialize: function (options) {

      this.column = options.column;
      if (!(this.column instanceof Backgrid.Column)) {
        this.column = new Backgrid.Column(this.column);
      }

      var collection = this.collection;
      var selectedModels = this.selectedModels = {};
      this.listenTo(collection.fullCollection || collection,
                    "backgrid:selected", function (model, selected) {
        if (selected) selectedModels[model.id || model.cid] = 1;
        else {
          delete selectedModels[model.id || model.cid];
          this.checkbox().prop("checked", false);
        }
        if (_.keys(selectedModels).length === (collection.fullCollection|| collection).length) {
          this.checkbox().prop("checked", true);
        }
      });

      this.listenTo(collection.fullCollection || collection, "remove", function (model) {
        delete selectedModels[model.id || model.cid];
        if ((collection.fullCollection || collection).length === 0) {
          this.checkbox().prop("checked", false);
        }
      });

      this.listenTo(collection, "backgrid:refresh", function () {
        if ((collection.fullCollection || collection).length === 0) {
          this.checkbox().prop("checked", false);
        }
        else {
          var checked = this.checkbox().prop("checked");
          for (var i = 0; i < collection.length; i++) {
            var model = collection.at(i);
            if (checked || selectedModels[model.id || model.cid]) {
              model.trigger("backgrid:select", model, true);
            }
          }
        }
      });

      var column = this.column, $el = this.$el;
      this.listenTo(column, "change:renderable", function (column, renderable) {
        $el.toggleClass("renderable", renderable);
      });

      if (Backgrid.callByNeed(column.renderable(), column, collection)) $el.addClass("renderable");
    },

    /**
       Propagates the checked value of this checkbox to all the models of the
       underlying collection by triggering a Backbone `backgrid:select` event on
       the models on the current page, passing each model and the current
       `checked` value of the checkbox in each event.

       A `backgrid:selected` event will also be triggered with the current
       `checked` value on all the models regardless of whether they are on the
       current page.

       This method triggers a 'backgrid:select-all' event on the collection
       afterwards.
    */
    onChange: function () {
      var checked = this.checkbox().prop("checked");

      var collection = this.collection;
      collection.each(function (model) {
        model.trigger("backgrid:select", model, checked);
      });

      if (collection.fullCollection) {
        collection.fullCollection.each(function (model) {
          if (!collection.get(model.cid)) {
            model.trigger("backgrid:selected", model, checked);
          }
        });
      }

      this.collection.trigger("backgrid:select-all", this.collection, checked);
    }

  });

  /**
     Convenient method to retrieve a list of selected models. This method only
     exists when the `SelectAll` extension has been included. Selected models
     are retained across pagination.

     @member Backgrid.Grid
     @return {Array.<Backbone.Model>}
  */
  Backgrid.Grid.prototype.getSelectedModels = function () {
    var selectAllHeaderCell;
    var headerCells = this.header.row.cells;
    for (var i = 0, l = headerCells.length; i < l; i++) {
      var headerCell = headerCells[i];
      if (headerCell instanceof SelectAllHeaderCell) {
        selectAllHeaderCell = headerCell;
        break;
      }
    }

    var result = [];
    if (selectAllHeaderCell) {
      var selectedModels = selectAllHeaderCell.selectedModels;
      var collection = this.collection.fullCollection || this.collection;
      for (var modelId in selectedModels) {
        result.push(collection.get(modelId));
      }
    }

    return result;
  };

  /**
     Convenient method to deselect the selected models. This method is only
     available when the `SelectAll` extension has been included.

     @member Backgrid.Grid
   */
  Backgrid.Grid.prototype.clearSelectedModels = function () {
    var selectedModels = this.getSelectedModels();
    for (var i = 0, l = selectedModels.length; i < l; i++) {
      var model = selectedModels[i];
      model.trigger("backgrid:select", model, false);
    }
  };

}));
<commit_msg>commonjs support for underscore<commit_after>/*
  backgrid-select-all
  http://github.com/wyuenho/backgrid

  Copyright (c) 2013 Jimmy Yuen Ho Wong and contributors
  Licensed under the MIT @license.
*/
(function (root, factory) {

  // CommonJS
  if (typeof exports == "object") {
    module.exports = factory(require("backbone"), require("backgrid"), require("underscore"));
  }
  // Browser
  else factory(root.Backbone, root.Backgrid, root._);

}(this, function (Backbone, Backgrid, _) {

  "use strict";

  /**
     Renders a checkbox for row selection.

     @class Backgrid.Extension.SelectRowCell
     @extends Backbone.View
  */
  var SelectRowCell = Backgrid.Extension.SelectRowCell = Backbone.View.extend({

    /** @property */
    className: "select-row-cell",

    /** @property */
    tagName: "td",

    /** @property */
    events: {
      "keydown input[type=checkbox]": "onKeydown",
      "change input[type=checkbox]": "onChange",
      "click input[type=checkbox]": "enterEditMode"
    },

    /**
       Initializer. If the underlying model triggers a `select` event, this cell
       will change its checked value according to the event's `selected` value.

       @param {Object} options
       @param {Backgrid.Column} options.column
       @param {Backbone.Model} options.model
    */
    initialize: function (options) {

      this.column = options.column;
      if (!(this.column instanceof Backgrid.Column)) {
        this.column = new Backgrid.Column(this.column);
      }

      var column = this.column, model = this.model, $el = this.$el;
      this.listenTo(column, "change:renderable", function (column, renderable) {
        $el.toggleClass("renderable", renderable);
      });

      if (Backgrid.callByNeed(column.renderable(), column, model)) $el.addClass("renderable");

      this.listenTo(model, "backgrid:select", function (model, selected) {
        this.checkbox().prop("checked", selected).change();
      });
    },

    /**
       Returns the checkbox.
     */
    checkbox: function () {
      return this.$el.find("input[type=checkbox]");
    },

    /**
       Focuses the checkbox.
    */
    enterEditMode: function () {
      this.checkbox().focus();
    },

    /**
       Unfocuses the checkbox.
    */
    exitEditMode: function () {
      this.checkbox().blur();
    },

    /**
       Process keyboard navigation.
    */
    onKeydown: function (e) {
      var command = new Backgrid.Command(e);
      if (command.passThru()) return true; // skip ahead to `change`
      if (command.cancel()) {
        e.stopPropagation();
        this.checkbox().blur();
      }
      else if (command.save() || command.moveLeft() || command.moveRight() ||
               command.moveUp() || command.moveDown()) {
        e.preventDefault();
        e.stopPropagation();
        this.model.trigger("backgrid:edited", this.model, this.column, command);
      }
    },

    /**
       When the checkbox's value changes, this method will trigger a Backbone
       `backgrid:selected` event with a reference of the model and the
       checkbox's `checked` value.
    */
    onChange: function () {
      var checked = this.checkbox().prop("checked");
      this.$el.parent().toggleClass("selected", checked);
      this.model.trigger("backgrid:selected", this.model, checked);
    },

    /**
       Renders a checkbox in a table cell.
    */
    render: function () {
      this.$el.empty().append('<input tabindex="-1" type="checkbox" />');
      this.delegateEvents();
      return this;
    }

  });

  /**
     Renders a checkbox to select all rows on the current page.

     @class Backgrid.Extension.SelectAllHeaderCell
     @extends Backgrid.Extension.SelectRowCell
  */
  var SelectAllHeaderCell = Backgrid.Extension.SelectAllHeaderCell = SelectRowCell.extend({

    /** @property */
    className: "select-all-header-cell",

    /** @property */
    tagName: "th",

    /**
       Initializer. When this cell's checkbox is checked, a Backbone
       `backgrid:select` event will be triggered for each model for the current
       page in the underlying collection. If a `SelectRowCell` instance exists
       for the rows representing the models, they will check themselves. If any
       of the SelectRowCell instances trigger a Backbone `backgrid:selected`
       event with a `false` value, this cell will uncheck its checkbox. In the
       event of a Backbone `backgrid:refresh` event, which is triggered when the
       body refreshes its rows, which can happen under a number of conditions
       such as paging or the columns were reset, this cell will still remember
       the previously selected models and trigger a Backbone `backgrid:select`
       event on them such that the SelectRowCells can recheck themselves upon
       refreshing.

       @param {Object} options
       @param {Backgrid.Column} options.column
       @param {Backbone.Collection} options.collection
    */
    initialize: function (options) {

      this.column = options.column;
      if (!(this.column instanceof Backgrid.Column)) {
        this.column = new Backgrid.Column(this.column);
      }

      var collection = this.collection;
      var selectedModels = this.selectedModels = {};
      this.listenTo(collection.fullCollection || collection,
                    "backgrid:selected", function (model, selected) {
        if (selected) selectedModels[model.id || model.cid] = 1;
        else {
          delete selectedModels[model.id || model.cid];
          this.checkbox().prop("checked", false);
        }
        if (_.keys(selectedModels).length === (collection.fullCollection|| collection).length) {
          this.checkbox().prop("checked", true);
        }
      });

      this.listenTo(collection.fullCollection || collection, "remove", function (model) {
        delete selectedModels[model.id || model.cid];
        if ((collection.fullCollection || collection).length === 0) {
          this.checkbox().prop("checked", false);
        }
      });

      this.listenTo(collection, "backgrid:refresh", function () {
        if ((collection.fullCollection || collection).length === 0) {
          this.checkbox().prop("checked", false);
        }
        else {
          var checked = this.checkbox().prop("checked");
          for (var i = 0; i < collection.length; i++) {
            var model = collection.at(i);
            if (checked || selectedModels[model.id || model.cid]) {
              model.trigger("backgrid:select", model, true);
            }
          }
        }
      });

      var column = this.column, $el = this.$el;
      this.listenTo(column, "change:renderable", function (column, renderable) {
        $el.toggleClass("renderable", renderable);
      });

      if (Backgrid.callByNeed(column.renderable(), column, collection)) $el.addClass("renderable");
    },

    /**
       Propagates the checked value of this checkbox to all the models of the
       underlying collection by triggering a Backbone `backgrid:select` event on
       the models on the current page, passing each model and the current
       `checked` value of the checkbox in each event.

       A `backgrid:selected` event will also be triggered with the current
       `checked` value on all the models regardless of whether they are on the
       current page.

       This method triggers a 'backgrid:select-all' event on the collection
       afterwards.
    */
    onChange: function () {
      var checked = this.checkbox().prop("checked");

      var collection = this.collection;
      collection.each(function (model) {
        model.trigger("backgrid:select", model, checked);
      });

      if (collection.fullCollection) {
        collection.fullCollection.each(function (model) {
          if (!collection.get(model.cid)) {
            model.trigger("backgrid:selected", model, checked);
          }
        });
      }

      this.collection.trigger("backgrid:select-all", this.collection, checked);
    }

  });

  /**
     Convenient method to retrieve a list of selected models. This method only
     exists when the `SelectAll` extension has been included. Selected models
     are retained across pagination.

     @member Backgrid.Grid
     @return {Array.<Backbone.Model>}
  */
  Backgrid.Grid.prototype.getSelectedModels = function () {
    var selectAllHeaderCell;
    var headerCells = this.header.row.cells;
    for (var i = 0, l = headerCells.length; i < l; i++) {
      var headerCell = headerCells[i];
      if (headerCell instanceof SelectAllHeaderCell) {
        selectAllHeaderCell = headerCell;
        break;
      }
    }

    var result = [];
    if (selectAllHeaderCell) {
      var selectedModels = selectAllHeaderCell.selectedModels;
      var collection = this.collection.fullCollection || this.collection;
      for (var modelId in selectedModels) {
        result.push(collection.get(modelId));
      }
    }

    return result;
  };

  /**
     Convenient method to deselect the selected models. This method is only
     available when the `SelectAll` extension has been included.

     @member Backgrid.Grid
   */
  Backgrid.Grid.prototype.clearSelectedModels = function () {
    var selectedModels = this.getSelectedModels();
    for (var i = 0, l = selectedModels.length; i < l; i++) {
      var model = selectedModels[i];
      model.trigger("backgrid:select", model, false);
    }
  };

}));
<|endoftext|>