<commit_before>// Miscellaneous core Javascript functions for Moodle

function popupchecker(msg) {
    var testwindow = window.open('itestwin.html', '', 'width=1,height=1,left=0,top=0,scrollbars=no');
    if (testwindow == null)
        {alert(msg);}
    else {
        testwindow.close();
    }
}

/*
function popUpProperties(inobj) {
/// Legacy function
  var op = window.open();
  op.document.open('text/plain');
  for (objprop in inobj) {
    op.document.write(objprop + ' => ' + inobj[objprop] + '\n');
  }
  op.document.close();
}

function fillmessagebox(text) {
/// Legacy function
  document.form.message.value = text;
}

function copyrichtext(textname) {
/// Legacy stub for old editor - to be removed soon
  return true;
}
*/

function checkall() {
  var el = document.getElementsByTagName('input');
  for(var i=0; i<el.length; i++) {
    if(el[i].type == 'checkbox') {
      el[i].checked = true;
    }
  }
}

function checknone() {
  var el = document.getElementsByTagName('input');
  for(var i=0; i<el.length; i++) {
    if(el[i].type == 'checkbox') {
      el[i].checked = false;
    }
  }
}

function lockoptions(formid, master, subitems) {
  // Subitems is an array of names of sub items.
  // Optionally, each item in subitems may have a
  // companion hidden item in the form with the
  // same name but prefixed by "h".
  var form = document.forms[formid];

  if (eval("form."+master+".checked")) {
    for (i=0; i<subitems.length; i++) {
      unlockoption(form, subitems[i]);
    }
  } else {
    for (i=0; i<subitems.length; i++) {
      lockoption(form, subitems[i]);
    }
  }
  return(true);
}

function lockoption(form,item) {
  eval("form."+item+".disabled=true");/* IE thing */
  if(form.elements['h'+item]) {
    eval("form.h"+item+".value=1");
  }
}

function unlockoption(form,item) {
  eval("form."+item+".disabled=false");/* IE thing */
  if(form.elements['h'+item]) {
    eval("form.h"+item+".value=0");
  }
}

function lockoptionsall(formid) {
    var form = document.forms[formid];
    var dependons = eval(formid+'items');
    for (var dependon in dependons) {
        var master = form[dependon];
        for (var condition in dependons[dependon]) {
            for (var value in dependons[dependon][condition]) {
                var lock;
                switch (condition) {
                  case 'notchecked':
                      lock = !master.checked; break;
                  case 'checked':
                      lock = master.checked; break;
                  case 'noitemselected':
                      lock = master.selectedIndex==-1; break;
                  case 'eq':
                      lock = master.value==value; break;
                  default:
                      lock = master.value!=value; break;
                }
                for (var ei in dependons[dependon][condition][value]) {
                    var formelement = form[dependons[dependon][condition][value][ei]];
                    formelement.disabled = lock;
                }
            }
        }
    }
    return true;
}

function lockoptionsallsetup(formid) {
    var form = document.forms[formid];
    var dependons = eval(formid+'items');
    for (var dependon in dependons) {
        var master = form[dependon];
        master.onclick  = function() {return lockoptionsall(this.form.getAttribute('id'));};
        master.onblur   = function() {return lockoptionsall(this.form.getAttribute('id'));};
        master.onchange = function() {return lockoptionsall(this.form.getAttribute('id'));};
    }
    for (var i = 0; i < form.elements.length; i++){
        var formelement = form.elements[i];
        if (formelement.type=='reset') {
            formelement.onclick  = function() {this.form.reset();return lockoptionsall(this.form.getAttribute('id'));};
            formelement.onblur   = function() {this.form.reset();return lockoptionsall(this.form.getAttribute('id'));};
            formelement.onchange = function() {this.form.reset();return lockoptionsall(this.form.getAttribute('id'));};
        }
    }
    return lockoptionsall(formid);
}

function submitFormById(id) {
    var theform = document.getElementById(id);
    if(!theform) {
        return false;
    }
    if(theform.tagName.toLowerCase() != 'form') {
        return false;
    }
    if(!theform.onsubmit || theform.onsubmit()) {
        return theform.submit();
    }
}

function select_all_in(elTagName, elClass, elId) {
    var inputs = document.getElementsByTagName('input');
    inputs = filterByParent(inputs, function(el) {return findParentNode(el, elTagName, elClass, elId);});
    for(var i = 0; i < inputs.length; ++i) {
        if(inputs[i].type == 'checkbox' || inputs[i].type == 'radio') {
            inputs[i].checked = 'checked';
        }
    }
}

function deselect_all_in(elTagName, elClass, elId) {
    var inputs = document.getElementsByTagName('INPUT');
    inputs = filterByParent(inputs, function(el) {return findParentNode(el, elTagName, elClass, elId);});
    for(var i = 0; i < inputs.length; ++i) {
        if(inputs[i].type == 'checkbox' || inputs[i].type == 'radio') {
            inputs[i].checked = '';
        }
    }
}

function confirm_if(expr, message) {
    if(!expr) {
        return true;
    }
    return confirm(message);
}

/*
    findParentNode (start, elementName, elementClass, elementID)

    Travels up the DOM hierarchy to find a parent element with the
    specified tag name, class, and id. All conditions must be met,
    but any can be ommitted. Returns the BODY element if no match
    found.
*/
function findParentNode(el, elName, elClass, elId) {
    while(el.nodeName.toUpperCase() != 'BODY') {
        if(
            (!elName || el.nodeName.toUpperCase() == elName) &&
            (!elClass || el.className.indexOf(elClass) != -1) &&
            (!elId || el.id == elId))
        {
            break;
        }
        el = el.parentNode;
    }
    return el;
}
/*
    findChildNode (start, elementName, elementClass, elementID)

    Travels down the DOM hierarchy to find all child elements with the
    specified tag name, class, and id. All conditions must be met,
    but any can be ommitted.
    Doesn't examine children of matches.
*/
function findChildNodes(start, tagName, elementClass, elementID, elementName) {
    var children = new Array();
    for (var i = 0; i < start.childNodes.length; i++) {
        var classfound = false;
        var child = start.childNodes[i];
        if((child.nodeType == 1) &&//element node type
                  (elementClass && (typeof(child.className)=='string'))){
            var childClasses = child.className.split(/\s+/);
            for (var childClassIndex in childClasses){
                if (childClasses[childClassIndex]==elementClass){
                    classfound = true;
                    break;
                }
            }
        }
        if(child.nodeType == 1) { //element node type
            if  ( (!tagName || child.nodeName == tagName) &&
                (!elementClass || classfound)&&
                (!elementID || child.id == elementID) &&
                (!elementName || child.name == elementName))
            {
                children = children.concat(child);
            } else {
                children = children.concat(findChildNodes(child, tagName, elementClass, elementID, elementName));
            }
        }
    }
    return children;
}
/*
    elementSetHide (elements, hide)

    Adds or removes the "hide" class for the specified elements depending on boolean hide.
*/
function elementShowAdvanced(elements, show) {
    for (var elementIndex in elements){
        element = elements[elementIndex];
        element.className = element.className.replace(new RegExp(' ?hide'), '')
        if(!show) {
            element.className += ' hide';
        }
    }
}

function showAdvancedOnClick(button, hidetext, showtext){
    var toSet=findChildNodes(button.form, null, 'advanced');
    var buttontext = '';
    if (button.form.elements['mform_showadvanced_last'].value == '0' ||  button.form.elements['mform_showadvanced_last'].value == '' ) {
        elementShowAdvanced(toSet, true);
        buttontext = hidetext;
        button.form.elements['mform_showadvanced_last'].value = '1';
    } else {
        elementShowAdvanced(toSet, false);
        buttontext = showtext;
        button.form.elements['mform_showadvanced_last'].value = '0';
    }
    var formelements = button.form.elements;
    for (var i in formelements){
        if (formelements[i] && formelements[i].name && (formelements[i].name=='mform_showadvanced')){
            formelements[i].value = buttontext;
        }
    }
    //never submit the form if js is enabled.
    return false;
}

/*
    elementToggleHide (element, elementFinder)

    If elementFinder is not provided, toggles the "hidden" class for the specified element.
    If elementFinder is provided, then the "hidden" class will be toggled for the object
    returned by the function call elementFinder(element).

    If persistent == true, also sets a cookie for this.
*/
function elementToggleHide(el, persistent, elementFinder) {
    if(!elementFinder) {
        var obj = el;
    }
    else {
        var obj = elementFinder(el);
    }
    if(obj.className.indexOf('hidden') == -1) {
        obj.className += ' hidden';
        var shown = 0;
    }
    else {
        obj.className = obj.className.replace(new RegExp(' ?hidden'), '')
        var shown = 1;
    }

    if(persistent == true) {
        new cookie('hide:' + obj.id, 1, (shown ? -1 : 356), '/').set();
    }
}

function elementCookieHide(id) {
    var obj  = document.getElementById(id);
    var cook = new cookie('hide:' + id).read();
    if(cook != null) {
        elementToggleHide(obj, false);
    }
}

function filterByParent(elCollection, parentFinder) {
    var filteredCollection = [];
    for(var i = 0; i < elCollection.length; ++i) {
        var findParent = parentFinder(elCollection[i]);
        if(findParent.nodeName != 'BODY') {
            filteredCollection.push(elCollection[i]);
        }
    }
    return filteredCollection;
}

/*
    All this is here just so that IE gets to handle oversized blocks
    in a visually pleasing manner. It does a browser detect. So sue me.
*/

function fix_column_widths() {
    var agt = navigator.userAgent.toLowerCase();
    if ((agt.indexOf("msie") != -1) && (agt.indexOf("opera") == -1)) {
        fix_column_width('left-column');
        fix_column_width('right-column');
    }
}

function fix_column_width(colName) {
    if(column = document.getElementById(colName)) {
        if(!column.offsetWidth) {
            setTimeout("fix_column_width('" + colName + "')", 20);
            return;
        }

        var width = 0;
        var nodes = column.childNodes;

        for(i = 0; i < nodes.length; ++i) {
            if(nodes[i].className.indexOf("sideblock") != -1 ) {
                if(width < nodes[i].offsetWidth) {
                    width = nodes[i].offsetWidth;
                }
            }
        }

        for(i = 0; i < nodes.length; ++i) {
            if(nodes[i].className.indexOf("sideblock") != -1 ) {
                nodes[i].style.width = width + 'px';
            }
        }
    }
}

/*
	Insert myValue at current cursor position
*/
function insertAtCursor(myField, myValue) {
	// IE support
	if (document.selection) {
		myField.focus();
		sel = document.selection.createRange();
		sel.text = myValue;
	}
	// Mozilla/Netscape support
	else if (myField.selectionStart || myField.selectionStart == '0') {
		var startPos = myField.selectionStart;
		var endPos = myField.selectionEnd;
		myField.value = myField.value.substring(0, startPos)
			+ myValue + myField.value.substring(endPos, myField.value.length);
	} else {
		myField.value += myValue;
	}
}
<commit_msg>see MDL-8886.<commit_after>// Miscellaneous core Javascript functions for Moodle

function popupchecker(msg) {
    var testwindow = window.open('itestwin.html', '', 'width=1,height=1,left=0,top=0,scrollbars=no');
    if (testwindow == null)
        {alert(msg);}
    else {
        testwindow.close();
    }
}

/*
function popUpProperties(inobj) {
/// Legacy function
  var op = window.open();
  op.document.open('text/plain');
  for (objprop in inobj) {
    op.document.write(objprop + ' => ' + inobj[objprop] + '\n');
  }
  op.document.close();
}

function fillmessagebox(text) {
/// Legacy function
  document.form.message.value = text;
}

function copyrichtext(textname) {
/// Legacy stub for old editor - to be removed soon
  return true;
}
*/

function checkall() {
  var el = document.getElementsByTagName('input');
  for(var i=0; i<el.length; i++) {
    if(el[i].type == 'checkbox') {
      el[i].checked = true;
    }
  }
}

function checknone() {
  var el = document.getElementsByTagName('input');
  for(var i=0; i<el.length; i++) {
    if(el[i].type == 'checkbox') {
      el[i].checked = false;
    }
  }
}

function lockoptions(formid, master, subitems) {
  // Subitems is an array of names of sub items.
  // Optionally, each item in subitems may have a
  // companion hidden item in the form with the
  // same name but prefixed by "h".
  var form = document.forms[formid];

  if (eval("form."+master+".checked")) {
    for (i=0; i<subitems.length; i++) {
      unlockoption(form, subitems[i]);
    }
  } else {
    for (i=0; i<subitems.length; i++) {
      lockoption(form, subitems[i]);
    }
  }
  return(true);
}

function lockoption(form,item) {
  eval("form."+item+".disabled=true");/* IE thing */
  if(form.elements['h'+item]) {
    eval("form.h"+item+".value=1");
  }
}

function unlockoption(form,item) {
  eval("form."+item+".disabled=false");/* IE thing */
  if(form.elements['h'+item]) {
    eval("form.h"+item+".value=0");
  }
}

function lockoptionsall(formid) {
    var form = document.forms[formid];
    var dependons = eval(formid+'items');
    var tolock = Array();
    for (var dependon in dependons) {
        var master = form[dependon];
        for (var condition in dependons[dependon]) {
            for (var value in dependons[dependon][condition]) {
                var lock;
                switch (condition) {
                  case 'notchecked':
                      lock = !master.checked; break;
                  case 'checked':
                      lock = master.checked; break;
                  case 'noitemselected':
                      lock = master.selectedIndex==-1; break;
                  case 'eq':
                      lock = master.value==value; break;
                  default:
                      lock = master.value!=value; break;
                }
                for (var ei in dependons[dependon][condition][value]) {
                    var eltolock = dependons[dependon][condition][value][ei];
                    if (tolock[eltolock] != null){
                        tolock[eltolock] =
                                lock || tolock[eltolock];
                    } else {
                        tolock[eltolock] = lock;
                    }
                }
            }
        }
    }
    for (var el in tolock){
        var formelement = form[el];
        formelement.disabled = tolock[el];
    }
    return true;
}

function lockoptionsallsetup(formid) {
    var form = document.forms[formid];
    var dependons = eval(formid+'items');
    for (var dependon in dependons) {
        var master = form[dependon];
        master.onclick  = function() {return lockoptionsall(this.form.getAttribute('id'));};
        master.onblur   = function() {return lockoptionsall(this.form.getAttribute('id'));};
        master.onchange = function() {return lockoptionsall(this.form.getAttribute('id'));};
    }
    for (var i = 0; i < form.elements.length; i++){
        var formelement = form.elements[i];
        if (formelement.type=='reset') {
            formelement.onclick  = function() {this.form.reset();return lockoptionsall(this.form.getAttribute('id'));};
            formelement.onblur   = function() {this.form.reset();return lockoptionsall(this.form.getAttribute('id'));};
            formelement.onchange = function() {this.form.reset();return lockoptionsall(this.form.getAttribute('id'));};
        }
    }
    return lockoptionsall(formid);
}

function submitFormById(id) {
    var theform = document.getElementById(id);
    if(!theform) {
        return false;
    }
    if(theform.tagName.toLowerCase() != 'form') {
        return false;
    }
    if(!theform.onsubmit || theform.onsubmit()) {
        return theform.submit();
    }
}

function select_all_in(elTagName, elClass, elId) {
    var inputs = document.getElementsByTagName('input');
    inputs = filterByParent(inputs, function(el) {return findParentNode(el, elTagName, elClass, elId);});
    for(var i = 0; i < inputs.length; ++i) {
        if(inputs[i].type == 'checkbox' || inputs[i].type == 'radio') {
            inputs[i].checked = 'checked';
        }
    }
}

function deselect_all_in(elTagName, elClass, elId) {
    var inputs = document.getElementsByTagName('INPUT');
    inputs = filterByParent(inputs, function(el) {return findParentNode(el, elTagName, elClass, elId);});
    for(var i = 0; i < inputs.length; ++i) {
        if(inputs[i].type == 'checkbox' || inputs[i].type == 'radio') {
            inputs[i].checked = '';
        }
    }
}

function confirm_if(expr, message) {
    if(!expr) {
        return true;
    }
    return confirm(message);
}

/*
    findParentNode (start, elementName, elementClass, elementID)

    Travels up the DOM hierarchy to find a parent element with the
    specified tag name, class, and id. All conditions must be met,
    but any can be ommitted. Returns the BODY element if no match
    found.
*/
function findParentNode(el, elName, elClass, elId) {
    while(el.nodeName.toUpperCase() != 'BODY') {
        if(
            (!elName || el.nodeName.toUpperCase() == elName) &&
            (!elClass || el.className.indexOf(elClass) != -1) &&
            (!elId || el.id == elId))
        {
            break;
        }
        el = el.parentNode;
    }
    return el;
}
/*
    findChildNode (start, elementName, elementClass, elementID)

    Travels down the DOM hierarchy to find all child elements with the
    specified tag name, class, and id. All conditions must be met,
    but any can be ommitted.
    Doesn't examine children of matches.
*/
function findChildNodes(start, tagName, elementClass, elementID, elementName) {
    var children = new Array();
    for (var i = 0; i < start.childNodes.length; i++) {
        var classfound = false;
        var child = start.childNodes[i];
        if((child.nodeType == 1) &&//element node type
                  (elementClass && (typeof(child.className)=='string'))){
            var childClasses = child.className.split(/\s+/);
            for (var childClassIndex in childClasses){
                if (childClasses[childClassIndex]==elementClass){
                    classfound = true;
                    break;
                }
            }
        }
        if(child.nodeType == 1) { //element node type
            if  ( (!tagName || child.nodeName == tagName) &&
                (!elementClass || classfound)&&
                (!elementID || child.id == elementID) &&
                (!elementName || child.name == elementName))
            {
                children = children.concat(child);
            } else {
                children = children.concat(findChildNodes(child, tagName, elementClass, elementID, elementName));
            }
        }
    }
    return children;
}
/*
    elementSetHide (elements, hide)

    Adds or removes the "hide" class for the specified elements depending on boolean hide.
*/
function elementShowAdvanced(elements, show) {
    for (var elementIndex in elements){
        element = elements[elementIndex];
        element.className = element.className.replace(new RegExp(' ?hide'), '')
        if(!show) {
            element.className += ' hide';
        }
    }
}

function showAdvancedOnClick(button, hidetext, showtext){
    var toSet=findChildNodes(button.form, null, 'advanced');
    var buttontext = '';
    if (button.form.elements['mform_showadvanced_last'].value == '0' ||  button.form.elements['mform_showadvanced_last'].value == '' ) {
        elementShowAdvanced(toSet, true);
        buttontext = hidetext;
        button.form.elements['mform_showadvanced_last'].value = '1';
    } else {
        elementShowAdvanced(toSet, false);
        buttontext = showtext;
        button.form.elements['mform_showadvanced_last'].value = '0';
    }
    var formelements = button.form.elements;
    for (var i in formelements){
        if (formelements[i] && formelements[i].name && (formelements[i].name=='mform_showadvanced')){
            formelements[i].value = buttontext;
        }
    }
    //never submit the form if js is enabled.
    return false;
}

/*
    elementToggleHide (element, elementFinder)

    If elementFinder is not provided, toggles the "hidden" class for the specified element.
    If elementFinder is provided, then the "hidden" class will be toggled for the object
    returned by the function call elementFinder(element).

    If persistent == true, also sets a cookie for this.
*/
function elementToggleHide(el, persistent, elementFinder) {
    if(!elementFinder) {
        var obj = el;
    }
    else {
        var obj = elementFinder(el);
    }
    if(obj.className.indexOf('hidden') == -1) {
        obj.className += ' hidden';
        var shown = 0;
    }
    else {
        obj.className = obj.className.replace(new RegExp(' ?hidden'), '')
        var shown = 1;
    }

    if(persistent == true) {
        new cookie('hide:' + obj.id, 1, (shown ? -1 : 356), '/').set();
    }
}

function elementCookieHide(id) {
    var obj  = document.getElementById(id);
    var cook = new cookie('hide:' + id).read();
    if(cook != null) {
        elementToggleHide(obj, false);
    }
}

function filterByParent(elCollection, parentFinder) {
    var filteredCollection = [];
    for(var i = 0; i < elCollection.length; ++i) {
        var findParent = parentFinder(elCollection[i]);
        if(findParent.nodeName != 'BODY') {
            filteredCollection.push(elCollection[i]);
        }
    }
    return filteredCollection;
}

/*
    All this is here just so that IE gets to handle oversized blocks
    in a visually pleasing manner. It does a browser detect. So sue me.
*/

function fix_column_widths() {
    var agt = navigator.userAgent.toLowerCase();
    if ((agt.indexOf("msie") != -1) && (agt.indexOf("opera") == -1)) {
        fix_column_width('left-column');
        fix_column_width('right-column');
    }
}

function fix_column_width(colName) {
    if(column = document.getElementById(colName)) {
        if(!column.offsetWidth) {
            setTimeout("fix_column_width('" + colName + "')", 20);
            return;
        }

        var width = 0;
        var nodes = column.childNodes;

        for(i = 0; i < nodes.length; ++i) {
            if(nodes[i].className.indexOf("sideblock") != -1 ) {
                if(width < nodes[i].offsetWidth) {
                    width = nodes[i].offsetWidth;
                }
            }
        }

        for(i = 0; i < nodes.length; ++i) {
            if(nodes[i].className.indexOf("sideblock") != -1 ) {
                nodes[i].style.width = width + 'px';
            }
        }
    }
}

/*
	Insert myValue at current cursor position
*/
function insertAtCursor(myField, myValue) {
	// IE support
	if (document.selection) {
		myField.focus();
		sel = document.selection.createRange();
		sel.text = myValue;
	}
	// Mozilla/Netscape support
	else if (myField.selectionStart || myField.selectionStart == '0') {
		var startPos = myField.selectionStart;
		var endPos = myField.selectionEnd;
		myField.value = myField.value.substring(0, startPos)
			+ myValue + myField.value.substring(endPos, myField.value.length);
	} else {
		myField.value += myValue;
	}
}
<|endoftext|>


<commit_before>#!/usr/bin/env node

var program = require('commander'),
    Hash = require('./../index'),
    pkg = require('./../package.json');

program
    .version(pkg.version)
    .usage("<hash-function> <data>")
    .parse(process.argv);

console.log(new Hash(program.args[0], program.args[1]).digest());
<commit_msg>Add print list of all hashes<commit_after>#!/usr/bin/env node

var program = require('commander'),
    Hash = require('./../index'),
    pkg = require('./../package.json');

program
    .version(pkg.version)
    .usage("<hash-function> <data>")
    .option('-l, --list', 'Print all available hashing functions')
    .parse(process.argv);

if (!!program.list) {
    Hash.SUPPORTED_HASHES.forEach(function (hash) {
        console.log(hash);
    });
} else {
    console.log(new Hash(program.args[0], program.args[1]).digest());
}
<|endoftext|>


<commit_before>const Helper = require('../../shared');

class CECcontroller extends Helper {
	// -------------------------------------------------------------------------- //
	// Example: basic.js
	// For more cec-events: http://www.cec-o-matic.com/
	// -------------------------------------------------------------------------- //
	constructor(){
		super('cec')
		const nodecec = require( 'node-cec' );
		const NodeCec = nodecec.NodeCec;

		this.cectypes = nodecec.CEC;
		this.cec = new NodeCec( 'node-cec-monitor' );
		this.client;

		// -------------------------------------------------------------------------- //
		//- CEC EVENT HANDLING
		this.cec.once( 'ready', (client)=>this.onReady(client));
		this.powerStatus();
		this.routeChange();

		// -------------------------------------------------------------------------- //
		// -m  = start in monitor-mode
		// -d8 = set log level to 8 (=TRAFFIC) (-d 8)
		// -br = logical address set to `recording device`
		this.cec.start('cec-client', '-m', '-d', '8', '-b', 'r')
	}

	// -------------------------------------------------------------------------- //
	//- KILL CEC-CLIENT PROCESS ON EXIT
	killOnExit(){
		process.on('SIGINT', () =>this.killCec());
	}

	killCec(){
		if ( this.cec != null ) {
		  this.cec.stop();
		}
		process.exit();
	}

	onReady(client){
		this.log( ' -- READY -- ' );
		this.client = client;
		this.client.sendCommand( 0xf0, this.cectypes.Opcode.GIVE_DEVICE_POWER_STATUS );
	}

	powerStatus(){
		cec.on( 'REPORT_POWER_STATUS', (packet, status) => this.powerStatusCallback(packet, status));
	}

	powerStatusCallback(){
		var keys = Object.keys( CEC.PowerStatus );

		for (var i = keys.length - 1; i >= 0; i--) {
			if (CEC.PowerStatus[keys[i]] == status) {
				this.emit('powerStatus', keys[i])
				break;
			}
		}
	}

	routeChange(){
		cec.on( 'ROUTING_CHANGE', (packet, fromSource, toSource)=>this.emit('routeChange', fromSource, toSource) );
	}
}
module.exports = new CECcontroller();<commit_msg>update reference<commit_after>const Helper = require('../../shared');

class CECcontroller extends Helper {
	// -------------------------------------------------------------------------- //
	// Example: basic.js
	// For more cec-events: http://www.cec-o-matic.com/
	// -------------------------------------------------------------------------- //
	constructor(){
		super('cec')
		const nodecec = require( 'node-cec' );
		const NodeCec = nodecec.NodeCec;

		this.cectypes = nodecec.CEC;
		this.cec = new NodeCec( 'node-cec-monitor' );
		this.client;

		// -------------------------------------------------------------------------- //
		//- CEC EVENT HANDLING
		this.cec.once( 'ready', (client)=>this.onReady(client));
		this.powerStatus();
		this.routeChange();

		// -------------------------------------------------------------------------- //
		// -m  = start in monitor-mode
		// -d8 = set log level to 8 (=TRAFFIC) (-d 8)
		// -br = logical address set to `recording device`
		this.cec.start('cec-client', '-m', '-d', '8', '-b', 'r')
	}

	// -------------------------------------------------------------------------- //
	//- KILL CEC-CLIENT PROCESS ON EXIT
	killOnExit(){
		process.on('SIGINT', () =>this.killCec());
	}

	killCec(){
		if ( this.cec != null ) {
		  this.cec.stop();
		}
		process.exit();
	}

	onReady(client){
		this.log( ' -- READY -- ' );
		this.client = client;
		this.client.sendCommand( 0xf0, this.cectypes.Opcode.GIVE_DEVICE_POWER_STATUS );
	}

	powerStatus(){
		this.cec.on( 'REPORT_POWER_STATUS', (packet, status) => this.powerStatusCallback(packet, status));
	}

	powerStatusCallback(){
		var keys = Object.keys( this.cectypes.PowerStatus );

		for (var i = keys.length - 1; i >= 0; i--) {
			if (this.cectypes.PowerStatus[keys[i]] == status) {
				this.emit('powerStatus', keys[i])
				break;
			}
		}
	}

	routeChange(){
		this.cec.on( 'ROUTING_CHANGE', (packet, fromSource, toSource)=>this.emit('routeChange', fromSource, toSource) );
	}
}
module.exports = new CECcontroller();<|endoftext|>


<commit_before>function afterProxy () {
    return function * (next) {
        console.log(this._proxyResponse);
        if (void 0 === this._proxyResponse) {
            return yield next;
        }

        if (!this.dispatcher) {
            return this.body = this._proxyResponse;
        }

        this.dispatcher.handler = () => this._proxyResponse;
        yield next;
    };
}

module.exports = afterProxy;<commit_msg>chore: rm log<commit_after>function afterProxy () {
    return function * (next) {
        if (void 0 === this._proxyResponse) {
            return yield next;
        }

        if (!this.dispatcher) {
            return this.body = this._proxyResponse;
        }

        this.dispatcher.handler = () => this._proxyResponse;
        yield next;
    };
}

module.exports = afterProxy;<|endoftext|>


<commit_before><commit_msg>Don't try to use openidId when it's not set<commit_after><|endoftext|>


<commit_before>// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License in the project root for license information.

'use strict';

const utils = require('../test/util/utils'),
    request = require('request-promise-native'),
    zlib = require('zlib');

const repoUrl = utils.getSourceRepoUrl(),
    validationService = "https://app.azure-devex-tools.com/api/validations",
    branch = utils.getSourceBranch(),
    processingDelay = 20,
    isRunningInTravisCI = process.env.MODE === 'liveValidation' && process.env.PR_ONLY === 'true',
    specsPaths = utils.getFilesChangedInPR(),
    regex = /resource-manager[\\|\/](.*?)[\\|\/].*?[\\|\/](.*?)[\\|\/]/,
    successThreshold = 90,
    validationModels = new Map();

let durationInSeconds = parseInt(process.env.LIVE_VALIDATION_DURATION_IN_MINUTES) * 60;
if (isNaN(durationInSeconds)) {
    durationInSeconds = 180;
}

async function runScript() {
    // See whether script is in Travis CI context
    console.log(`isRunningInTraviSCI: ${isRunningInTravisCI}`);
    for (const specPath of specsPaths) {
        let matchResult = specPath.match(regex);

        if (matchResult === null) {
            continue;
        }

        let resourceProvider = matchResult[1];
        let apiVersion = matchResult[2];

        if (!validationModels.has(resourceProvider)) {
            validationModels.set(resourceProvider, new Set());
        }

        validationModels.get(resourceProvider).add(apiVersion);
    }

    if (validationModels.size === 0) {
        console.log("Change didn't affect any swagger specs. No validation to be done.");
        return;
    } else if (validationModels.size > 1) {
        console.log("WARNING: Multiple resource provider have changes, only the first one will be validated.");
    }

    let resourceProvider = validationModels.keys().next().value;

    if (validationModels.get(resourceProvider).size > 1) {
        console.log("WARNING: Multiple api versions have changes, only the first one will be validated.");
    }

    let apiVersion = validationModels.get(resourceProvider).values().next().value;

    console.log(`Changes detected in a swagger spec.`);
    console.log(`RP is: ${resourceProvider}`);
    console.log(`ApiVersion is: ${apiVersion}`);
    console.log(`Source repo is: ${repoUrl}`);
    console.log(`Branch is: ${branch}`);

    console.log(`Making the request to the validation service...`);

    let response = await request.post(validationService).form({
        repoUrl: repoUrl,
        branch: branch,
        resourceProvider: resourceProvider,
        apiVersion: apiVersion,
        duration: durationInSeconds
    });
    let validationId = JSON.parse(response).validationId;

    let validationResultUrl = `${validationService}/${validationId}`;
    console.log(`Request done, results will be available in ${durationInSeconds} seconds...`);

    await timeout((durationInSeconds + processingDelay) * 1000);
    let validationResult = JSON.parse(await request(validationResultUrl));

    console.log(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>");
    console.log(`Results of validation ${validationId}:`);

    let analyticsUrl = await createAnalyticsLink(validationId);

    let failingOperations = [];
    let noTrafficOperations = [];
    for (const [operationId, operationResult] of Object.entries(validationResult.operationResults)) {

        if (operationResult.operationCount === 0) {
            noTrafficOperations.push(operationResult.operationId)
        } else if (operationResult.successRate < successThreshold) {
            failingOperations.push(operationResult.operationId);
        }

        console.log(JSON.stringify(operationResult));
    }

    console.log(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>");
    if (failingOperations.length > 0 || noTrafficOperations.length > 0) {
        console.log(`The changes in the specs introduced by this PR potentially do not reflect the Service API.`);

        console.log(`Active traffic and success rate > ${successThreshold}% FOR EACH OPERATION is required. Please review the following operations before moving forward.`);
        console.log(`SUCCESS RATE < ${successThreshold}%:
        ${JSON.stringify(failingOperations)}`);

        if (noTrafficOperations.length > 0) {
            console.log(`NO TRAFFIC:
         ${JSON.stringify(noTrafficOperations)}
         `);
        }
        console.log(`To inspect the individual failures go to the url (add '| where customDimensions.operationId == "<OPERATION_ID>"' to filter for individual operations.):
        ${analyticsUrl}
        `);
        process.exitCode = 1;
    } else {
        console.log(`SUCCESS RATE: ${validationResult.SuccessRate} > ${successThreshold}. You can move forward:`);
    }
}

function timeout(ms) {
    return new Promise(resolve => setTimeout(resolve, ms));
}

function createAnalyticsLink(validationId) {
    return new Promise(resolve => {
        const query = `
traces
| where customDimensions.validationId == "${validationId}"
| where customDimensions.logType == "data"
| where customDimensions.isSuccess == "false"
| project timestamp, message, customDimensions
`;

        zlib.deflate(query, (err, buffer) => {
            if (!err) {
                let queryParams = buffer.toString('base64');
                let analyticsLink = `https://analytics.applicationinsights.io/subscriptions/6b085460-5f21-477e-ba44-1035046e9101/resourcegroups/openapi-platform-logs/components/openapiAI?q=${queryParams}&apptype=Node.JS&timespan=P1D`;
                resolve(analyticsLink);
            }
        });
    });
}

runScript().then(success => {
    console.log(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>");
    console.log(`Thanks for using live validation.`);
    console.log(`If you encounter any issue(s), please open issue(s) at https://github.com/Azure/openapi-platform/issues .`);
}).catch(err => {
    console.log(err);
    process.exitCode = 1;
});
<commit_msg>Check for no total traffic for live validation (#2251)<commit_after>// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License in the project root for license information.

'use strict';

const utils = require('../test/util/utils'),
    request = require('request-promise-native'),
    zlib = require('zlib');

const repoUrl = utils.getSourceRepoUrl(),
    validationService = "https://app.azure-devex-tools.com/api/validations",
    branch = utils.getSourceBranch(),
    processingDelay = 20,
    isRunningInTravisCI = process.env.MODE === 'liveValidation' && process.env.PR_ONLY === 'true',
    specsPaths = utils.getFilesChangedInPR(),
    regex = /resource-manager[\\|\/](.*?)[\\|\/].*?[\\|\/](.*?)[\\|\/]/,
    successThreshold = 90,
    validationModels = new Map();

let durationInSeconds = parseInt(process.env.LIVE_VALIDATION_DURATION_IN_MINUTES) * 60;
if (isNaN(durationInSeconds)) {
    durationInSeconds = 180;
}

async function runScript() {
    // See whether script is in Travis CI context
    console.log(`isRunningInTraviSCI: ${isRunningInTravisCI}`);
    for (const specPath of specsPaths) {
        let matchResult = specPath.match(regex);

        if (matchResult === null) {
            continue;
        }

        let resourceProvider = matchResult[1];
        let apiVersion = matchResult[2];

        if (!validationModels.has(resourceProvider)) {
            validationModels.set(resourceProvider, new Set());
        }

        validationModels.get(resourceProvider).add(apiVersion);
    }

    if (validationModels.size === 0) {
        console.log("Change didn't affect any swagger specs. No validation to be done.");
        return;
    } else if (validationModels.size > 1) {
        console.log("WARNING: Multiple resource provider have changes, only the first one will be validated.");
    }

    let resourceProvider = validationModels.keys().next().value;

    if (validationModels.get(resourceProvider).size > 1) {
        console.log("WARNING: Multiple api versions have changes, only the first one will be validated.");
    }

    let apiVersion = validationModels.get(resourceProvider).values().next().value;

    console.log(`Changes detected in a swagger spec.`);
    console.log(`RP is: ${resourceProvider}`);
    console.log(`ApiVersion is: ${apiVersion}`);
    console.log(`Source repo is: ${repoUrl}`);
    console.log(`Branch is: ${branch}`);

    console.log(`Making the request to the validation service...`);

    let response = await request.post(validationService).form({
        repoUrl: repoUrl,
        branch: branch,
        resourceProvider: resourceProvider,
        apiVersion: apiVersion,
        duration: durationInSeconds
    });
    let validationId = JSON.parse(response).validationId;

    let validationResultUrl = `${validationService}/${validationId}`;
    console.log(`Request done, results will be available in ${durationInSeconds} seconds...`);

    await timeout((durationInSeconds + processingDelay) * 1000);
    let validationResult = JSON.parse(await request(validationResultUrl));

    console.log(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>");
    console.log(`Results of validation ${validationId}:`);

    let analyticsUrl = await createAnalyticsLink(validationId);

    let failingOperations = [];
    let noTrafficOperations = [];
    for (const [operationId, operationResult] of Object.entries(validationResult.operationResults)) {

        if (operationResult.operationCount === 0) {
            noTrafficOperations.push(operationResult.operationId)
        } else if (operationResult.successRate < successThreshold) {
            failingOperations.push(operationResult.operationId);
        }

        console.log(JSON.stringify(operationResult));
    }

    console.log(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>");
    if (validationResult.totalOperationCount === 0) {
        console.log(`There was no traffic detected for the provided RP and API version:${resourceProvider}-${apiVersion}. Please make sure there is traffic so the changes can be validated.`);
    } else if (failingOperations.length > 0 || noTrafficOperations.length > 0) {
        console.log(`The changes in the specs introduced by this PR potentially do not reflect the Service API.`);

        console.log(`Active traffic and success rate > ${successThreshold}% FOR EACH OPERATION is required. Please review the following operations before moving forward.`);
        console.log(`SUCCESS RATE < ${successThreshold}%:
        ${JSON.stringify(failingOperations)}`);

        if (noTrafficOperations.length > 0) {
            console.log(`NO TRAFFIC:
         ${JSON.stringify(noTrafficOperations)}
         `);
        }
        console.log(`To inspect the individual failures go to the url (add '| where customDimensions.operationId == "<OPERATION_ID>"' to filter for individual operations.):
        ${analyticsUrl}
        `);
        process.exitCode = 1;
    } else {
        console.log(`SUCCESS RATE: ${validationResult.successRate} > ${successThreshold}. You can move forward.`);
    }
}

function timeout(ms) {
    return new Promise(resolve => setTimeout(resolve, ms));
}

function createAnalyticsLink(validationId) {
    return new Promise(resolve => {
        const query = `
traces
| where customDimensions.validationId == "${validationId}"
| where customDimensions.logType == "data"
| where customDimensions.isSuccess == "false"
| project timestamp, message, customDimensions
`;

        zlib.deflate(query, (err, buffer) => {
            if (!err) {
                let queryParams = buffer.toString('base64');
                let analyticsLink = `https://analytics.applicationinsights.io/subscriptions/6b085460-5f21-477e-ba44-1035046e9101/resourcegroups/openapi-platform-logs/components/openapiAI?q=${queryParams}&apptype=Node.JS&timespan=P1D`;
                resolve(analyticsLink);
            }
        });
    });
}

runScript().then(success => {
    console.log(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>");
    console.log(`Thanks for using live validation.`);
    console.log(`If you encounter any issue(s), please open issue(s) at https://github.com/Azure/openapi-platform/issues .`);
}).catch(err => {
    console.log(err);
    process.exitCode = 1;
});
<|endoftext|>


<commit_before>// This grants us source map support, which is handy as our webpack bundling
// for the server will include source maps. Therefore we will have nice stack
// traces again for our server.
import "source-map-support/register"

import express from "express"
import compression from "compression"
import hpp from "hpp"
import helmet from "helmet"

import React from "react"
import { renderToString } from "react-dom/server"

import Button from "../components/Button"

// Create our express based server.
const app = express()

// Don't expose any software information to hackers.
app.disable("x-powered-by")

// Prevent HTTP Parameter pollution.
app.use(hpp())

// Content Security Policy
app.use(helmet.contentSecurityPolicy({
  defaultSrc: [ "'self'" ],
  scriptSrc: [ "'self'" ],
  styleSrc: [ "'self'" ],
  imgSrc: [ "'self'" ],
  connectSrc: [ "'self'", "ws:" ],
  fontSrc: [ "'self'" ],
  objectSrc: [ "'none'" ],
  mediaSrc: [ "'none'" ],
  frameSrc: [ "'none'" ],
}))

app.use(helmet.xssFilter())
app.use(helmet.frameguard("deny"))
app.use(helmet.ieNoOpen())
app.use(helmet.noSniff())

// Response compression.
app.use(compression())

// Configure static serving of our webpack bundled client files.
app.use(process.env.PUBLIC_PATH, express.static(process.env.OUTPUT_PATH))

// Test
app.get('/', function (request, response)
{
  if (true)
  {
    const html = renderToString( <Button>TestButton</Button> )
    response.status(200).send(html)
  }
  else
  {
    response.status(404).send("Not found")
  }
});

// Create an http listener for our express app.
const listener = app.listen(parseInt(process.env.API_PORT, 10))

// User feedback
console.log(`Running server at port ${process.env.API_PORT}.`)

// We export the listener as it will be handy for our development hot reloader.
export default listener
<commit_msg>Worked on producing some HTML (temporary) to demonstrate that the combination is executable of API rendering + script + css.<commit_after>// This grants us source map support, which is handy as our webpack bundling
// for the server will include source maps. Therefore we will have nice stack
// traces again for our server.
import "source-map-support/register"

import express from "express"
import compression from "compression"
import hpp from "hpp"
import helmet from "helmet"

import React from "react"
import { renderToString } from "react-dom/server"

import ClientBundleAssets from "../../build/client/assets.json"
import Button from "../components/Button"

// Create our express based server.
const app = express()

// Don't expose any software information to hackers.
app.disable("x-powered-by")

// Prevent HTTP Parameter pollution.
app.use(hpp())

// Content Security Policy
app.use(helmet.contentSecurityPolicy({
  defaultSrc: [ "'self'" ],
  scriptSrc: [ "'self'" ],
  styleSrc: [ "'self'" ],
  imgSrc: [ "'self'" ],
  connectSrc: [ "'self'", "ws:" ],
  fontSrc: [ "'self'" ],
  objectSrc: [ "'none'" ],
  mediaSrc: [ "'none'" ],
  frameSrc: [ "'none'" ],
}))

app.use(helmet.xssFilter())
app.use(helmet.frameguard("deny"))
app.use(helmet.ieNoOpen())
app.use(helmet.noSniff())

// Response compression.
app.use(compression())

// Configure static serving of our webpack bundled client files.
app.use(process.env.PUBLIC_PATH, express.static(process.env.OUTPUT_PATH))

// Test
app.get('/', function (request, response)
{
  console.log("ASSETS: ", ClientBundleAssets)
  if (true)
  {

    const html = renderToString( <Button>TestButton</Button> )
    const js = ClientBundleAssets.main.js;
    const css = ClientBundleAssets.main.css;

    var page = `
      <link rel="stylesheet" href="${css}"/>
      ${html}
      <script src="${js}"></script>
    `

    response.status(200).send(page)
  }
  else
  {
    response.status(404).send("Not found")
  }
});

// Create an http listener for our express app.
const listener = app.listen(parseInt(process.env.API_PORT, 10))

// User feedback
console.log(`Running server at port ${process.env.API_PORT}.`)

// We export the listener as it will be handy for our development hot reloader.
export default listener
<|endoftext|>


<commit_before>import Ember from 'ember';
var observer = Ember.observer;
var  computed = Ember.computed;

export default Ember.Component.extend({
    classNames: ['predicate-section'],
    analyticsValueService: Ember.inject.service(),
    queueService: Ember.inject.service(),
    userService: Ember.inject.service(),
    //TODO: support queue ids, user Ids,
    init: function(){
        this.get("queueService"); //make sure it is ready

        this._super(...arguments);

        this.set("dimensions", this.get("analyticsValueService").get("dimensions"));
        this.set("propertyTypes", this.get("analyticsValueService").get("propertyTypes"));
        this.set("metrics", this.get("analyticsValueService").get("metrics"));
        this.set("operators", this.get("analyticsValueService").get("operators"));
        this.set("mediaTypes", this.get("analyticsValueService").get("mediaTypes"));

        let predicate = this.get("predicate");
        if(predicate){
            this.set("selectedType", predicate.type);
            this.set("lhsValue", predicate.dimension || predicate.metric || predicate.property);
            this.set("selectedOperator", predicate.operator);
            this.set("value", predicate.value);
        }
    },
    didReceiveAttrs() {
        this._super(...arguments);
        if(this.get("filterValueOverride") !== null){
            this.set("dimensions", this.get("filterValueOverride"));
            this.set("types", ["dimension"]);

        }
    },
    queues: computed('queueService.queues', function() {
       return this.get('queueService').get('queues');
    }),
    users: computed('userService.users', function() {
       return this.get('userService').get('users');
    }),
    types:["dimension", "metric"], //TODO: Support property type

    selectedOperator: "matches",
    selectedType: "dimension",
    isOnChanged: observer('selectedType', 'selectedOperator', 'lhsValue', 'value', function() {
        console.log("predicate changed");
        this.set("predicate", this._computeValue());
        console.log(this.get("predicate"));
        this.get("updatePredicate")(this.get("index"), this._computeValue());
    }),

    _computeValue:function(){

        let predicate= {
            type : this.get('selectedType'),
        };

        if(predicate.type === "dimension"){
            predicate.dimension = this.get("lhsValue");
        }
        else if(predicate.type === "metric"){
            predicate.metric = this.get("lhsValue");
        }
        else if(predicate.type === "property"){
            predicate.property = this.get("lhsValue");
        }

        predicate.operator = this.get('selectedOperator');

        predicate.value = this.get('value');

        return predicate;
    },
    actions:{
        delete: function(){
            this.get("deletePredicate")(this.get("index"));
        }
    }

});
<commit_msg>fix filter value override so dimensions will show for call detail query<commit_after>import Ember from 'ember';
var observer = Ember.observer;
var  computed = Ember.computed;

export default Ember.Component.extend({
    classNames: ['predicate-section'],
    analyticsValueService: Ember.inject.service(),
    queueService: Ember.inject.service(),
    userService: Ember.inject.service(),
    //TODO: support queue ids, user Ids,
    init: function(){
        this.get("queueService"); //make sure it is ready

        this._super(...arguments);

        this.set("dimensions", this.get("analyticsValueService").get("dimensions"));
        this.set("propertyTypes", this.get("analyticsValueService").get("propertyTypes"));
        this.set("metrics", this.get("analyticsValueService").get("metrics"));
        this.set("operators", this.get("analyticsValueService").get("operators"));
        this.set("mediaTypes", this.get("analyticsValueService").get("mediaTypes"));

        let predicate = this.get("predicate");
        if(predicate){
            this.set("selectedType", predicate.type);
            this.set("lhsValue", predicate.dimension || predicate.metric || predicate.property);
            this.set("selectedOperator", predicate.operator);
            this.set("value", predicate.value);
        }
    },
    didReceiveAttrs() {
        this._super(...arguments);

        if(typeof this.get("filterValueOverride") !== "undefined" && this.get("filterValueOverride") !== null){
            this.set("dimensions", this.get("filterValueOverride"));
            this.set("types", ["dimension"]);

        }
    },
    queues: computed('queueService.queues', function() {
       return this.get('queueService').get('queues');
    }),
    users: computed('userService.users', function() {
       return this.get('userService').get('users');
    }),
    types:["dimension", "metric"], //TODO: Support property type

    selectedOperator: "matches",
    selectedType: "dimension",
    isOnChanged: observer('selectedType', 'selectedOperator', 'lhsValue', 'value', function() {
        console.log("predicate changed");
        this.set("predicate", this._computeValue());
        console.log(this.get("predicate"));
        this.get("updatePredicate")(this.get("index"), this._computeValue());
    }),

    _computeValue:function(){

        let predicate= {
            type : this.get('selectedType'),
        };

        if(predicate.type === "dimension"){
            predicate.dimension = this.get("lhsValue");
        }
        else if(predicate.type === "metric"){
            predicate.metric = this.get("lhsValue");
        }
        else if(predicate.type === "property"){
            predicate.property = this.get("lhsValue");
        }

        predicate.operator = this.get('selectedOperator');

        predicate.value = this.get('value');

        return predicate;
    },
    actions:{
        delete: function(){
            this.get("deletePredicate")(this.get("index"));
        }
    }

});
<|endoftext|>


<commit_before>// @flow
import React, { Component, type ElementRef, type Node } from 'react';

import {
  animatedScrollTo,
  className,
  getScrollParent,
  getScrollTop,
  normalizedHeight,
  scrollTo,
} from '../utils';
import { Div } from '../primitives';
import { borderRadius, colors, spacing } from '../theme';
import type { InnerRef, MenuPlacement, PropsWithStyles } from '../types';

// ==============================
// Menu
// ==============================

// Get Menu Placement
// ------------------------------

type MenuState = { placement: 'bottom' | 'top' | null, maxHeight: number };
type PlacementArgs = {
  maxHeight: number,
  menuEl: HTMLElement,
  minHeight: number,
  placement: 'bottom' | 'top' | 'auto',
  shouldScroll: boolean,
};

export function getMenuPlacement({
  maxHeight,
  menuEl,
  minHeight,
  placement,
  shouldScroll,
}: PlacementArgs): MenuState {
  const scrollParent = getScrollParent(menuEl);
  const optimisticState = { placement: 'bottom', maxHeight };

  // something went wrong, return optimistic state
  if (!menuEl || !menuEl.offsetParent) return optimisticState;

  // can't trust `scrollParent.scrollHeight` --> it increases when the menu is rendered
  const { height: scrollHeight } = scrollParent.getBoundingClientRect();
  const {
    bottom: menuBottom,
    height: menuHeight,
    top: menuTop,
  } = menuEl.getBoundingClientRect();

  // $FlowFixMe function returns above if there's no offsetParent
  const { top: containerTop } = menuEl.offsetParent.getBoundingClientRect();
  const viewHeight = normalizedHeight(scrollParent);
  const scrollTop = getScrollTop(scrollParent);

  const viewSpaceAbove = containerTop - spacing.menuGutter;
  const viewSpaceBelow = viewHeight - menuTop;
  const scrollSpaceAbove = viewSpaceAbove + scrollTop;
  const scrollSpaceBelow = scrollHeight - scrollTop - menuTop;

  const scrollDown = menuBottom - viewHeight + scrollTop + spacing.menuGutter;
  const scrollUp = scrollTop + menuTop - spacing.menuGutter;
  const scrollDuration = 160;

  switch (placement) {
    case 'auto':
    case 'bottom':
      // 1: the menu will fit, do nothing
      if (viewSpaceBelow >= menuHeight) {
        return { placement: 'bottom', maxHeight };
      }

      // 2: the menu will fit, if scrolled
      if (scrollSpaceBelow >= menuHeight) {
        if (shouldScroll) {
          animatedScrollTo(scrollParent, scrollDown, scrollDuration);
        }

        return { placement: 'bottom', maxHeight };
      }

      // 3: the menu will fit, if constrained
      if (scrollSpaceBelow >= minHeight) {
        if (shouldScroll) {
          animatedScrollTo(scrollParent, scrollDown, scrollDuration);
        }

        // we want to provide as much of the menu as possible to the user,
        // so give them whatever is available below rather than the minHeight.
        const constrainedHeight = scrollSpaceBelow - spacing.menuGutter;

        return {
          placement: 'bottom',
          maxHeight: constrainedHeight,
        };
      }

      // 4. Forked beviour when there isn't enough space below

      // AUTO: flip the menu, render above
      if (placement === 'auto') {
        return { placement: 'top', maxHeight };
      }

      // BOTTOM: allow browser to increase scrollable area and immediately set scroll
      if (placement === 'bottom') {
        scrollTo(scrollParent, scrollDown);
        return { placement: 'bottom', maxHeight };
      }
      break;
    case 'top':
      // 1: the menu will fit, do nothing
      if (viewSpaceAbove >= menuHeight) {
        return { placement: 'top', maxHeight };
      }

      // 2: the menu will fit, if scrolled
      if (scrollSpaceAbove >= menuHeight) {
        if (shouldScroll) {
          animatedScrollTo(scrollParent, scrollUp, scrollDuration);
        }

        return { placement: 'top', maxHeight };
      }

      // 3: the menu will fit, if constrained
      if (scrollSpaceAbove >= minHeight) {
        if (shouldScroll) {
          animatedScrollTo(scrollParent, scrollUp, scrollDuration);
        }

        // we want to provide as much of the menu as possible to the user,
        // so give them whatever is available below rather than the minHeight.
        const constrainedHeight = scrollSpaceAbove - spacing.menuGutter;

        return {
          placement: 'top',
          maxHeight: constrainedHeight,
        };
      }

      // 4. not enough space, the browser WILL NOT increase scrollable area when
      // absolutely positioned element rendered above the viewport (only below).
      // Flip the menu, render below
      return { placement: 'bottom', maxHeight };
    default:
      throw new Error(`Invalid placement provided "${placement}".`);
  }

  // fulfil contract with flow: implicit return value of undefined
  return optimisticState;
}

// Menu Component
// ------------------------------

export type MenuProps = PropsWithStyles & {
  /** The children to be rendered. */
  children: Node,
  /** Props to be passed to the menu wrapper. */
  innerProps: Object,
  /** Set the maximum height of the menu. */
  maxMenuHeight: number,
  /** Set whether the menu should be at the top, at the bottom. The auto options sets it to bottom. */
  menuPlacement: MenuPlacement,
  /** Set the minimum height of the menu. */
  minMenuHeight: number,
  /** Set whether the page should scroll to show the menu. */
  scrollMenuIntoView: boolean,
};

function alignToControl(placement) {
  const placementToCSSProp = { bottom: 'top', top: 'bottom' };
  return placement ? placementToCSSProp[placement] : 'bottom';
}
const coercePlacement = p => (p === 'auto' ? 'bottom' : p);

export const menuCSS = ({ maxHeight, placement }: MenuState) => ({
  [alignToControl(placement)]: '100%',
  backgroundColor: colors.neutral0,
  boxShadow: `0 0 0 1px ${colors.neutral10a}, 0 4px 11px ${colors.neutral10a}`,
  borderRadius: borderRadius,
  marginBottom: spacing.menuGutter,
  marginTop: spacing.menuGutter,
  maxHeight: maxHeight,
  position: 'absolute',
  width: '100%',
  zIndex: 1,
});

export class Menu extends Component<MenuProps, MenuState> {
  state = {
    maxHeight: this.props.maxMenuHeight,
    placement: null,
  };
  getPlacement = (ref: ElementRef<*>) => {
    const {
      minMenuHeight,
      maxMenuHeight,
      menuPlacement,
      scrollMenuIntoView,
    } = this.props;

    if (!ref) return;

    const state = getMenuPlacement({
      maxHeight: maxMenuHeight,
      menuEl: ref,
      minHeight: minMenuHeight,
      placement: menuPlacement,
      shouldScroll: scrollMenuIntoView,
    });

    this.setState(state);
  };
  getState = () => {
    const { menuPlacement } = this.props;
    const placement = this.state.placement || coercePlacement(menuPlacement);

    return { ...this.props, placement, maxHeight: this.state.maxHeight };
  };
  render() {
    const { children, getStyles, innerProps } = this.props;

    return (
      <Div
        className={className('menu')}
        css={getStyles('menu', this.getState())}
        innerRef={this.getPlacement}
        {...innerProps}
      >
        {children}
      </Div>
    );
  }
}

export default Menu;

// ==============================
// Menu List
// ==============================

type MenuListState = {
  /** Set classname for isMulti */
  isMulti: boolean,
  /* I do not know what this is - Ben */
  maxHeight: number,
};

export type MenuListProps = {
  /** The children to be rendered. */
  children: Node,
  /** Props to be passed to the wrapper component. */
  innerProps: {
    'aria-multiselectable': boolean,
    id: string,
    innerRef: InnerRef,
    role: 'listbox',
  },
};
export type MenuListComponentProps = PropsWithStyles & MenuListProps & MenuListState;
export const menuListCSS = () => ({
  maxHeight: 'inherit', // pixel max-height applied to wrapping Menu component
  overflowY: 'auto',
  paddingBottom: spacing.baseUnit,
  paddingTop: spacing.baseUnit,
  position: 'relative', // required for offset[Height, Top] > keyboard scroll
  WebkitOverflowScrolling: 'touch',
});
export const MenuList = (props: MenuListComponentProps) => {
  const { children, getStyles, isMulti, innerProps } = props;
  return (
    <Div
      className={className('menu-list', { isMulti })}
      css={getStyles('menuList', props)}
      {...innerProps}
    >
      {children}
    </Div>
  );
};

// ==============================
// Menu Notices
// ==============================

const noticeCSS = () => ({
  color: colors.neutral40,
  padding: `${spacing.baseUnit * 2}px ${spacing.baseUnit * 3}px`,
  textAlign: 'center',
});
export const noOptionsMessageCSS = noticeCSS;
export const loadingMessageCSS = noticeCSS;

export type NoticeProps = PropsWithStyles & {
  /** The children to be rendered. */
  children: Node,
  /** Props to be passed on to the wrapper. */
  innerProps: { [string]: any },
};

export const NoOptionsMessage = (props: NoticeProps) => {
  const { children, getStyles, innerProps } = props;
  return (
    <Div
      className={className(['menu-notice', 'menu-notice--no-options'])}
      css={getStyles('noOptionsMessage', props)}
      {...innerProps}
    >
      {children}
    </Div>
  );
};
NoOptionsMessage.defaultProps = {
  children: 'No options',
};

export const LoadingMessage = (props: NoticeProps) => {
  const { children, getStyles, innerProps } = props;
  return (
    <Div
      className={className(['menu-notice', 'menu-notice--loading'])}
      css={getStyles('loadingMessage', props)}
      {...innerProps}
    >
      {children}
    </Div>
  );
};
LoadingMessage.defaultProps = {
  children: 'Loading...',
};
<commit_msg>resolve menu-list scroll issue when siblings present<commit_after>// @flow
import React, { Component, type ElementRef, type Node } from 'react';

import {
  animatedScrollTo,
  className,
  getScrollParent,
  getScrollTop,
  normalizedHeight,
  scrollTo,
} from '../utils';
import { Div } from '../primitives';
import { borderRadius, colors, spacing } from '../theme';
import type { InnerRef, MenuPlacement, PropsWithStyles } from '../types';

// ==============================
// Menu
// ==============================

// Get Menu Placement
// ------------------------------

type MenuState = { placement: 'bottom' | 'top' | null, maxHeight: number };
type PlacementArgs = {
  maxHeight: number,
  menuEl: HTMLElement,
  minHeight: number,
  placement: 'bottom' | 'top' | 'auto',
  shouldScroll: boolean,
};

export function getMenuPlacement({
  maxHeight,
  menuEl,
  minHeight,
  placement,
  shouldScroll,
}: PlacementArgs): MenuState {
  const scrollParent = getScrollParent(menuEl);
  const optimisticState = { placement: 'bottom', maxHeight };

  // something went wrong, return optimistic state
  if (!menuEl || !menuEl.offsetParent) return optimisticState;

  // can't trust `scrollParent.scrollHeight` --> it increases when the menu is rendered
  const { height: scrollHeight } = scrollParent.getBoundingClientRect();
  const {
    bottom: menuBottom,
    height: menuHeight,
    top: menuTop,
  } = menuEl.getBoundingClientRect();

  // $FlowFixMe function returns above if there's no offsetParent
  const { top: containerTop } = menuEl.offsetParent.getBoundingClientRect();
  const viewHeight = normalizedHeight(scrollParent);
  const scrollTop = getScrollTop(scrollParent);

  const viewSpaceAbove = containerTop - spacing.menuGutter;
  const viewSpaceBelow = viewHeight - menuTop;
  const scrollSpaceAbove = viewSpaceAbove + scrollTop;
  const scrollSpaceBelow = scrollHeight - scrollTop - menuTop;

  const scrollDown = menuBottom - viewHeight + scrollTop + spacing.menuGutter;
  const scrollUp = scrollTop + menuTop - spacing.menuGutter;
  const scrollDuration = 160;

  switch (placement) {
    case 'auto':
    case 'bottom':
      // 1: the menu will fit, do nothing
      if (viewSpaceBelow >= menuHeight) {
        return { placement: 'bottom', maxHeight };
      }

      // 2: the menu will fit, if scrolled
      if (scrollSpaceBelow >= menuHeight) {
        if (shouldScroll) {
          animatedScrollTo(scrollParent, scrollDown, scrollDuration);
        }

        return { placement: 'bottom', maxHeight };
      }

      // 3: the menu will fit, if constrained
      if (scrollSpaceBelow >= minHeight) {
        if (shouldScroll) {
          animatedScrollTo(scrollParent, scrollDown, scrollDuration);
        }

        // we want to provide as much of the menu as possible to the user,
        // so give them whatever is available below rather than the minHeight.
        const constrainedHeight = scrollSpaceBelow - spacing.menuGutter;

        return {
          placement: 'bottom',
          maxHeight: constrainedHeight,
        };
      }

      // 4. Forked beviour when there isn't enough space below

      // AUTO: flip the menu, render above
      if (placement === 'auto') {
        return { placement: 'top', maxHeight };
      }

      // BOTTOM: allow browser to increase scrollable area and immediately set scroll
      if (placement === 'bottom') {
        scrollTo(scrollParent, scrollDown);
        return { placement: 'bottom', maxHeight };
      }
      break;
    case 'top':
      // 1: the menu will fit, do nothing
      if (viewSpaceAbove >= menuHeight) {
        return { placement: 'top', maxHeight };
      }

      // 2: the menu will fit, if scrolled
      if (scrollSpaceAbove >= menuHeight) {
        if (shouldScroll) {
          animatedScrollTo(scrollParent, scrollUp, scrollDuration);
        }

        return { placement: 'top', maxHeight };
      }

      // 3: the menu will fit, if constrained
      if (scrollSpaceAbove >= minHeight) {
        if (shouldScroll) {
          animatedScrollTo(scrollParent, scrollUp, scrollDuration);
        }

        // we want to provide as much of the menu as possible to the user,
        // so give them whatever is available below rather than the minHeight.
        const constrainedHeight = scrollSpaceAbove - spacing.menuGutter;

        return {
          placement: 'top',
          maxHeight: constrainedHeight,
        };
      }

      // 4. not enough space, the browser WILL NOT increase scrollable area when
      // absolutely positioned element rendered above the viewport (only below).
      // Flip the menu, render below
      return { placement: 'bottom', maxHeight };
    default:
      throw new Error(`Invalid placement provided "${placement}".`);
  }

  // fulfil contract with flow: implicit return value of undefined
  return optimisticState;
}

// Menu Component
// ------------------------------

export type MenuProps = PropsWithStyles & {
  /** The children to be rendered. */
  children: Node,
  /** Props to be passed to the menu wrapper. */
  innerProps: Object,
  /** Set the maximum height of the menu. */
  maxMenuHeight: number,
  /** Set whether the menu should be at the top, at the bottom. The auto options sets it to bottom. */
  menuPlacement: MenuPlacement,
  /** Set the minimum height of the menu. */
  minMenuHeight: number,
  /** Set whether the page should scroll to show the menu. */
  scrollMenuIntoView: boolean,
};

function alignToControl(placement) {
  const placementToCSSProp = { bottom: 'top', top: 'bottom' };
  return placement ? placementToCSSProp[placement] : 'bottom';
}
const coercePlacement = p => (p === 'auto' ? 'bottom' : p);

export const menuCSS = ({ maxHeight, placement }: MenuState) => ({
  [alignToControl(placement)]: '100%',
  backgroundColor: colors.neutral0,
  borderRadius: borderRadius,
  boxShadow: `0 0 0 1px ${colors.neutral10a}, 0 4px 11px ${colors.neutral10a}`,
  display: 'flex ',
  flexDirection: 'column',
  marginBottom: spacing.menuGutter,
  marginTop: spacing.menuGutter,
  maxHeight: maxHeight,
  position: 'absolute',
  width: '100%',
  zIndex: 1,
});

export class Menu extends Component<MenuProps, MenuState> {
  state = {
    maxHeight: this.props.maxMenuHeight,
    placement: null,
  };
  getPlacement = (ref: ElementRef<*>) => {
    const {
      minMenuHeight,
      maxMenuHeight,
      menuPlacement,
      scrollMenuIntoView,
    } = this.props;

    if (!ref) return;

    const state = getMenuPlacement({
      maxHeight: maxMenuHeight,
      menuEl: ref,
      minHeight: minMenuHeight,
      placement: menuPlacement,
      shouldScroll: scrollMenuIntoView,
    });

    this.setState(state);
  };
  getState = () => {
    const { menuPlacement } = this.props;
    const placement = this.state.placement || coercePlacement(menuPlacement);

    return { ...this.props, placement, maxHeight: this.state.maxHeight };
  };
  render() {
    const { children, getStyles, innerProps } = this.props;

    return (
      <Div
        className={className('menu')}
        css={getStyles('menu', this.getState())}
        innerRef={this.getPlacement}
        {...innerProps}
      >
        {children}
      </Div>
    );
  }
}

export default Menu;

// ==============================
// Menu List
// ==============================

type MenuListState = {
  /** Set classname for isMulti */
  isMulti: boolean,
  /* I do not know what this is - Ben */
  maxHeight: number,
};

export type MenuListProps = {
  /** The children to be rendered. */
  children: Node,
  /** Props to be passed to the wrapper component. */
  innerProps: {
    'aria-multiselectable': boolean,
    id: string,
    innerRef: InnerRef,
    role: 'listbox',
  },
};
export type MenuListComponentProps = PropsWithStyles &
  MenuListProps &
  MenuListState;
export const menuListCSS = () => ({
  flexGrow: 1,
  overflowY: 'auto',
  paddingBottom: spacing.baseUnit,
  paddingTop: spacing.baseUnit,
  position: 'relative', // required for offset[Height, Top] > keyboard scroll
  WebkitOverflowScrolling: 'touch',
});
export const MenuList = (props: MenuListComponentProps) => {
  const { children, getStyles, isMulti, innerProps } = props;
  return (
    <Div
      className={className('menu-list', { isMulti })}
      css={getStyles('menuList', props)}
      {...innerProps}
    >
      {children}
    </Div>
  );
};

// ==============================
// Menu Notices
// ==============================

const noticeCSS = () => ({
  color: colors.neutral40,
  padding: `${spacing.baseUnit * 2}px ${spacing.baseUnit * 3}px`,
  textAlign: 'center',
});
export const noOptionsMessageCSS = noticeCSS;
export const loadingMessageCSS = noticeCSS;

export type NoticeProps = PropsWithStyles & {
  /** The children to be rendered. */
  children: Node,
  /** Props to be passed on to the wrapper. */
  innerProps: { [string]: any },
};

export const NoOptionsMessage = (props: NoticeProps) => {
  const { children, getStyles, innerProps } = props;
  return (
    <Div
      className={className(['menu-notice', 'menu-notice--no-options'])}
      css={getStyles('noOptionsMessage', props)}
      {...innerProps}
    >
      {children}
    </Div>
  );
};
NoOptionsMessage.defaultProps = {
  children: 'No options',
};

export const LoadingMessage = (props: NoticeProps) => {
  const { children, getStyles, innerProps } = props;
  return (
    <Div
      className={className(['menu-notice', 'menu-notice--loading'])}
      css={getStyles('loadingMessage', props)}
      {...innerProps}
    >
      {children}
    </Div>
  );
};
LoadingMessage.defaultProps = {
  children: 'Loading...',
};
<|endoftext|>


<commit_before>var hotkeys = (function () {

var exports = {};

function do_narrow_action(action) {
    action(current_msg_list.selected_id(), {trigger: 'hotkey'});
    return true;
}

var actions_dropdown_hotkeys = [
    'down_arrow',
    'up_arrow',
    'vim_up',
    'vim_down',
    'enter'
];

function get_event_name(e) {
    // Note that multiple keys can map to the same event_name, which
    // we'll do in cases where they have the exact same semantics.
    // DON'T FORGET: update keyboard_shortcuts.html

    if ((e.which === 9) && e.shiftKey) {
        return 'shift_tab';
    }

    // We're in the middle of a combo; stop processing because
    // we want the browser to handle it (to avoid breaking
    // things like Ctrl-C or Command-C for copy).
    if (e.metaKey || e.ctrlKey) {
        return 'ignore';
    }

    if (!e.shiftKey) {
        switch (e.keyCode) {
        case 33: // Page Up
            return 'page_up';
        case 34: // Page Down
            return 'page_down';
        case 35:
            return 'end';
        case 36:
            return 'home';
        case 38:
            return 'up_arrow';
        case 40:
            return 'down_arrow';
        }
    }

    switch (e.which) {
    case 8:
        return 'backspace';
    case 9:
        return 'tab';
    case 13:
        return 'enter';
    case 27:
        return 'escape';
    case 32: // Spacebar
        if (e.shiftKey) {
            return 'page_up';
        } else {
            return 'page_down';
        }
    case 47: // '/': initiate search
        return 'search';
    case 63: // '?': Show keyboard shortcuts page
        return 'show_shortcuts';
    case 67: // 'C'
        return 'compose_private_message';
    case 74: // 'J'
        return 'page_down';
    case 75: // 'K'
        return 'page_up';
    case 82: // 'R': respond to author
        return 'respond_to_author';
    case 83: //'S'
        return 'narrow_by_subject';
    case 99: // 'c'
        return 'compose';
    case 105: // 'i'
        return 'message_actions';
    case 106: // 'j'
        return 'vim_down';
    case 107: // 'k'
        return 'vim_up';
    case 114: // 'r': respond to message
        return 'reply_message';
    case 115: // 's'
        return 'narrow_by_recipient';
    case 118: // 'v'
        return 'narrow_private';
    }
    return 'ignore';
}

// Process a keydown or keypress event.
//
// Returns true if we handled it, false if the browser should.
function process_hotkey(e) {
    var row, focused_message_edit_content, focused_message_edit_save, message_edit_form;

    var event_name = get_event_name(e);
    activity.new_user_input = true;

    if (event_name === "tab") {
        // The alert word configuration is on the settings page,
        // so handle this before we abort early
        var alert_words_content = $(".edit-alert-word").filter(":focus");
        if (alert_words_content.length > 0) {
            var add_word_li = alert_words_content.closest(".alert-word-item");
            add_word_li.find(".add-alert-word").focus();
            return true;
        }
    }

    // Disable hotkeys on settings page etc., and when a modal pop-up
    // is visible.
    if (ui.home_tab_obscured()) {
        return false;
    }

    if (event_name === 'ignore') {
        return false;
    }

    if (popovers.actions_popped() && actions_dropdown_hotkeys.indexOf(event_name) !== -1) {
        popovers.actions_menu_handle_keyboard(event_name);
        return true;
    }

    // Handle a few keys specially when the send button is focused.
    if ($('#compose-send-button').is(':focus')) {
        if (event_name === 'backspace') {
            // Ignore backspace; don't navigate back a page.
            return true;
        } else if (event_name === 'shift_tab') {
            // Shift-Tab: go back to content textarea and restore
            // cursor position.
            ui.restore_compose_cursor();
            return true;
        }
    }

    // In Safari and the desktop app, we can't tab to buttons. Intercept the
    // tab from the message edit content box to Save and then Cancel.
    if (event_name === "tab") {
        focused_message_edit_content = $(".message_edit_content").filter(":focus");
        if (focused_message_edit_content.length > 0) {
            message_edit_form = focused_message_edit_content.closest(".message_edit_form");
            message_edit_form.find(".message_edit_save").focus();
            return true;
        }

        focused_message_edit_save = $(".message_edit_save").filter(":focus");
        if (focused_message_edit_save.length > 0) {
            message_edit_form = focused_message_edit_save.closest(".message_edit_form");
            message_edit_form.find(".message_edit_cancel").focus();
            return true;
        }
    }

    // Process hotkeys specially when in an input, textarea, or send button
    if ($('input:focus,textarea:focus,#compose-send-button:focus').length > 0) {
        if (event_name === 'escape') {
            // If one of our typeaheads is open, do nothing so that the Esc
            // will go to close it
            if ($("#subject").data().typeahead.shown ||
                $("#stream").data().typeahead.shown ||
                $("#private_message_recipient").data().typeahead.shown ||
                $("#new_message_content").data().typeahead.shown ||
                $("#search_query").data().typeahead.shown) {
                // For some reason this code is only needed in Firefox;
                // in Chrome our typeahead is able to intercept the Esc
                // event before we even get it.
                // Regardless, we do nothing in this case.
                return true;
            } else if ($(".message_edit_content").filter(":focus").length > 0) {
                row = $(".message_edit_content").filter(":focus").closest(".message_row");
                message_edit.end(row);
            } else if (compose.composing()) {
                // If the user hit the escape key, cancel the current compose
                compose.cancel();
                return true;
            } else {
                // We pressed Esc and something was focused, and the composebox
                // wasn't open. In that case, we should blur the input.
                // (this is almost certainly the searchbar)
                $("input:focus,textarea:focus").blur();
                return true;
            }
        }

        if ((event_name === 'up_arrow' || event_name === 'down_arrow')
            && compose.composing()
            && compose.message_content() === ""
            && $('#new_message_content').is(':focus')) {
                compose.cancel();
                // don't return, as we still want it to be picked up by the code below
        } else {
            // Let the browser handle the key normally.
            return false;
        }
    }

    // If we're on a button or a link and have pressed enter, let the
    // browser handle the keypress
    //
    // This is subtle and here's why: Suppose you have the focus on a
    // stream name in your left sidebar. j and k will still move your
    // cursor up and down, but Enter won't reply -- it'll just trigger
    // the link on the sidebar! So you keep pressing enter over and
    // over again. Until you click somewhere or press r.
    if ($('a:focus,button:focus').length > 0 && event_name === 'enter') {
        return false;
    }

    // Shortcuts that don't require a message
    switch (event_name) {
        case 'narrow_private':
            return do_narrow_action(function (target, opts) {
                narrow.by('is', 'private', opts);
            });
        case 'escape': // Esc: close actions popup, cancel compose, clear a find, or un-narrow
            if (popovers.any_active()) {
                popovers.hide_all();
            } else if (compose.composing()) {
                compose.cancel();
            } else {
                search.clear_search();
            }
            return true;
        case 'compose': // 'c': compose
            compose.start('stream');
            return true;
        case 'compose_private_message':
            compose.start('private');
            return true;
        case 'search':
            search.initiate_search();
            return true;
        case 'show_shortcuts': // Show keyboard shortcuts page
            $('#keyboard-shortcuts').modal('show');
            return true;
    }

    if (current_msg_list.empty()) {
        return false;
    }

    // Navigation shortcuts
    switch (event_name) {
        case 'down_arrow':
        case 'vim_down':
            navigate.down(true); // with_centering
            return true;
        case 'up_arrow':
        case 'vim_up':
            navigate.up();
            return true;
        case 'home':
            navigate.to_home();
            return true;
        case 'end':
            navigate.to_end();
            return true;
        case 'page_up':
            navigate.page_up();
            return true;
        case 'page_down':
            navigate.page_down();
            return true;
    }

    if (current_msg_list.on_expandable_row()) {
        switch (event_name) {
            case 'enter':
                ui.expand_summary_row(current_msg_list.selected_row().expectOne());
                return true;
        }
        return false;
    }

    // Shortcuts that operate on a message
    switch (event_name) {
        case 'message_actions':
            return popovers.open_message_menu();
        case 'narrow_by_recipient':
            return do_narrow_action(narrow.by_recipient);
        case 'narrow_by_subject':
            return do_narrow_action(narrow.by_subject);
        case  'enter': // Enter: respond to message (unless we need to do something else)
            respond_to_message({trigger: 'hotkey enter'});
            return true;
        case 'reply_message': // 'r': respond to message
            respond_to_message({trigger: 'hotkey'});
            return true;
        case 'respond_to_author': // 'R': respond to author
            respond_to_message({reply_type: "personal", trigger: 'hotkey pm'});
            return true;
    }

    return false;
}

/* We register both a keydown and a keypress function because
   we want to intercept pgup/pgdn, escape, etc, and process them
   as they happen on the keyboard. However, if we processed
   letters/numbers in keydown, we wouldn't know what the case of
   the letters were.

   We want case-sensitive hotkeys (such as in the case of r vs R)
   so we bail in .keydown if the event is a letter or number and
   instead just let keypress go for it. */

$(document).keydown(function (e) {
    // Restrict to non-alphanumeric keys
    if (48 > e.which || 90 < e.which) {
        if (process_hotkey(e)) {
            e.preventDefault();
        }
    }
});

$(document).keypress(function (e) {
    // What exactly triggers .keypress may vary by browser.
    // Welcome to compatability hell.
    //
    // In particular, when you press tab in Firefox, it fires a
    // keypress event with keycode 0 after processing the original
    // event.
    if (e.which !== 0 && e.charCode !== 0) {
        if (process_hotkey(e)) {
            e.preventDefault();
        }
    }
});

return exports;

}());
<commit_msg>Allow search hotkey to work from outside the home tab.<commit_after>var hotkeys = (function () {

var exports = {};

function do_narrow_action(action) {
    action(current_msg_list.selected_id(), {trigger: 'hotkey'});
    return true;
}

var actions_dropdown_hotkeys = [
    'down_arrow',
    'up_arrow',
    'vim_up',
    'vim_down',
    'enter'
];

function get_event_name(e) {
    // Note that multiple keys can map to the same event_name, which
    // we'll do in cases where they have the exact same semantics.
    // DON'T FORGET: update keyboard_shortcuts.html

    if ((e.which === 9) && e.shiftKey) {
        return 'shift_tab';
    }

    // We're in the middle of a combo; stop processing because
    // we want the browser to handle it (to avoid breaking
    // things like Ctrl-C or Command-C for copy).
    if (e.metaKey || e.ctrlKey) {
        return 'ignore';
    }

    if (!e.shiftKey) {
        switch (e.keyCode) {
        case 33: // Page Up
            return 'page_up';
        case 34: // Page Down
            return 'page_down';
        case 35:
            return 'end';
        case 36:
            return 'home';
        case 38:
            return 'up_arrow';
        case 40:
            return 'down_arrow';
        }
    }

    switch (e.which) {
    case 8:
        return 'backspace';
    case 9:
        return 'tab';
    case 13:
        return 'enter';
    case 27:
        return 'escape';
    case 32: // Spacebar
        if (e.shiftKey) {
            return 'page_up';
        } else {
            return 'page_down';
        }
    case 47: // '/': initiate search
        return 'search';
    case 63: // '?': Show keyboard shortcuts page
        return 'show_shortcuts';
    case 67: // 'C'
        return 'compose_private_message';
    case 74: // 'J'
        return 'page_down';
    case 75: // 'K'
        return 'page_up';
    case 82: // 'R': respond to author
        return 'respond_to_author';
    case 83: //'S'
        return 'narrow_by_subject';
    case 99: // 'c'
        return 'compose';
    case 105: // 'i'
        return 'message_actions';
    case 106: // 'j'
        return 'vim_down';
    case 107: // 'k'
        return 'vim_up';
    case 114: // 'r': respond to message
        return 'reply_message';
    case 115: // 's'
        return 'narrow_by_recipient';
    case 118: // 'v'
        return 'narrow_private';
    }
    return 'ignore';
}

// Process a keydown or keypress event.
//
// Returns true if we handled it, false if the browser should.
function process_hotkey(e) {
    var row, focused_message_edit_content, focused_message_edit_save, message_edit_form;

    var event_name = get_event_name(e);
    activity.new_user_input = true;

    if (event_name === "tab") {
        // The alert word configuration is on the settings page,
        // so handle this before we abort early
        var alert_words_content = $(".edit-alert-word").filter(":focus");
        if (alert_words_content.length > 0) {
            var add_word_li = alert_words_content.closest(".alert-word-item");
            add_word_li.find(".add-alert-word").focus();
            return true;
        }
    }

    if (ui.home_tab_obscured() && event_name !== 'search') {
        return false;
    }

    if (event_name === 'ignore') {
        return false;
    }

    if (popovers.actions_popped() && actions_dropdown_hotkeys.indexOf(event_name) !== -1) {
        popovers.actions_menu_handle_keyboard(event_name);
        return true;
    }

    // Handle a few keys specially when the send button is focused.
    if ($('#compose-send-button').is(':focus')) {
        if (event_name === 'backspace') {
            // Ignore backspace; don't navigate back a page.
            return true;
        } else if (event_name === 'shift_tab') {
            // Shift-Tab: go back to content textarea and restore
            // cursor position.
            ui.restore_compose_cursor();
            return true;
        }
    }

    // In Safari and the desktop app, we can't tab to buttons. Intercept the
    // tab from the message edit content box to Save and then Cancel.
    if (event_name === "tab") {
        focused_message_edit_content = $(".message_edit_content").filter(":focus");
        if (focused_message_edit_content.length > 0) {
            message_edit_form = focused_message_edit_content.closest(".message_edit_form");
            message_edit_form.find(".message_edit_save").focus();
            return true;
        }

        focused_message_edit_save = $(".message_edit_save").filter(":focus");
        if (focused_message_edit_save.length > 0) {
            message_edit_form = focused_message_edit_save.closest(".message_edit_form");
            message_edit_form.find(".message_edit_cancel").focus();
            return true;
        }
    }

    // Process hotkeys specially when in an input, textarea, or send button
    if ($('input:focus,textarea:focus,#compose-send-button:focus').length > 0) {
        if (event_name === 'escape') {
            // If one of our typeaheads is open, do nothing so that the Esc
            // will go to close it
            if ($("#subject").data().typeahead.shown ||
                $("#stream").data().typeahead.shown ||
                $("#private_message_recipient").data().typeahead.shown ||
                $("#new_message_content").data().typeahead.shown ||
                $("#search_query").data().typeahead.shown) {
                // For some reason this code is only needed in Firefox;
                // in Chrome our typeahead is able to intercept the Esc
                // event before we even get it.
                // Regardless, we do nothing in this case.
                return true;
            } else if ($(".message_edit_content").filter(":focus").length > 0) {
                row = $(".message_edit_content").filter(":focus").closest(".message_row");
                message_edit.end(row);
            } else if (compose.composing()) {
                // If the user hit the escape key, cancel the current compose
                compose.cancel();
                return true;
            } else {
                // We pressed Esc and something was focused, and the composebox
                // wasn't open. In that case, we should blur the input.
                // (this is almost certainly the searchbar)
                $("input:focus,textarea:focus").blur();
                return true;
            }
        }

        if ((event_name === 'up_arrow' || event_name === 'down_arrow')
            && compose.composing()
            && compose.message_content() === ""
            && $('#new_message_content').is(':focus')) {
                compose.cancel();
                // don't return, as we still want it to be picked up by the code below
        } else {
            // Let the browser handle the key normally.
            return false;
        }
    }

    // If we're on a button or a link and have pressed enter, let the
    // browser handle the keypress
    //
    // This is subtle and here's why: Suppose you have the focus on a
    // stream name in your left sidebar. j and k will still move your
    // cursor up and down, but Enter won't reply -- it'll just trigger
    // the link on the sidebar! So you keep pressing enter over and
    // over again. Until you click somewhere or press r.
    if ($('a:focus,button:focus').length > 0 && event_name === 'enter') {
        return false;
    }

    // Shortcuts that don't require a message
    switch (event_name) {
        case 'narrow_private':
            return do_narrow_action(function (target, opts) {
                narrow.by('is', 'private', opts);
            });
        case 'escape': // Esc: close actions popup, cancel compose, clear a find, or un-narrow
            if (popovers.any_active()) {
                popovers.hide_all();
            } else if (compose.composing()) {
                compose.cancel();
            } else {
                search.clear_search();
            }
            return true;
        case 'compose': // 'c': compose
            compose.start('stream');
            return true;
        case 'compose_private_message':
            compose.start('private');
            return true;
        case 'search':
            search.initiate_search();
            return true;
        case 'show_shortcuts': // Show keyboard shortcuts page
            $('#keyboard-shortcuts').modal('show');
            return true;
    }

    if (current_msg_list.empty()) {
        return false;
    }

    // Navigation shortcuts
    switch (event_name) {
        case 'down_arrow':
        case 'vim_down':
            navigate.down(true); // with_centering
            return true;
        case 'up_arrow':
        case 'vim_up':
            navigate.up();
            return true;
        case 'home':
            navigate.to_home();
            return true;
        case 'end':
            navigate.to_end();
            return true;
        case 'page_up':
            navigate.page_up();
            return true;
        case 'page_down':
            navigate.page_down();
            return true;
    }

    if (current_msg_list.on_expandable_row()) {
        switch (event_name) {
            case 'enter':
                ui.expand_summary_row(current_msg_list.selected_row().expectOne());
                return true;
        }
        return false;
    }

    // Shortcuts that operate on a message
    switch (event_name) {
        case 'message_actions':
            return popovers.open_message_menu();
        case 'narrow_by_recipient':
            return do_narrow_action(narrow.by_recipient);
        case 'narrow_by_subject':
            return do_narrow_action(narrow.by_subject);
        case  'enter': // Enter: respond to message (unless we need to do something else)
            respond_to_message({trigger: 'hotkey enter'});
            return true;
        case 'reply_message': // 'r': respond to message
            respond_to_message({trigger: 'hotkey'});
            return true;
        case 'respond_to_author': // 'R': respond to author
            respond_to_message({reply_type: "personal", trigger: 'hotkey pm'});
            return true;
    }

    return false;
}

/* We register both a keydown and a keypress function because
   we want to intercept pgup/pgdn, escape, etc, and process them
   as they happen on the keyboard. However, if we processed
   letters/numbers in keydown, we wouldn't know what the case of
   the letters were.

   We want case-sensitive hotkeys (such as in the case of r vs R)
   so we bail in .keydown if the event is a letter or number and
   instead just let keypress go for it. */

$(document).keydown(function (e) {
    // Restrict to non-alphanumeric keys
    if (48 > e.which || 90 < e.which) {
        if (process_hotkey(e)) {
            e.preventDefault();
        }
    }
});

$(document).keypress(function (e) {
    // What exactly triggers .keypress may vary by browser.
    // Welcome to compatability hell.
    //
    // In particular, when you press tab in Firefox, it fires a
    // keypress event with keycode 0 after processing the original
    // event.
    if (e.which !== 0 && e.charCode !== 0) {
        if (process_hotkey(e)) {
            e.preventDefault();
        }
    }
});

return exports;

}());
<|endoftext|>