<commit_before>let objBrowser = chrome ? chrome : browser;

class EtherAddressLookup {

    constructor()
    {
        console.log("Init EAL");
        this.setDefaultExtensionSettings();
        this.init();
    }

    setDefaultExtensionSettings()
    {
        this.blHighlight = false;
        this.strBlockchainExplorer = "https://etherscan.io/address";

        this.intSettingsCount = 0;
        this.intSettingsTotalCount = 2;
    }

    /**
     * @name init
     * @desc Gets extension settings and applies DOM manipulation
     */
    init()
    {
        let objBrowser = chrome ? chrome : browser;
        //Get the highlight option for the user
        objBrowser.runtime.sendMessage({func: "highlight_option"}, function(objResponse) {
            if(objResponse && objResponse.hasOwnProperty("resp")) {
                this.blHighlight = (objResponse.resp == 1);
            }
            ++this.intSettingsCount;
        }.bind(this));

        //Get the blockchain explorer for the user
        objBrowser.runtime.sendMessage({func: "blockchain_explorer"}, function(objResponse) {
            this.strBlockchainExplorer = objResponse.resp;
            ++this.intSettingsCount;
        }.bind(this));

        //Update the DOM once all settings have been received...
        setTimeout(function() {
            // Needs to happen after user settings have been collected
            // and in the context of init();
            this.setSearchAndReplaceSettings();
            this.setWarningSettings();
            this.manipulateDOM();
        }.bind(this), 10);
    }

    /**
     * @name Set Search And Replace Settings
     * @desc
     */
    setSearchAndReplaceSettings()
    {
        // Check user is on their favourite block explorer, on fail target blank.
        this.target = (this.isBlockchainExplorerSite() ? '_self' : '_blank');

        // Register RegEx Patterns
        this.regExPatterns = [
            // Ethereum Address Regex
            /(^|\s|:|-)((?:0x)[0-9a-fA-F]{40})(\s|$)/gi,

            // ENS Address Regex
            /(^|\s|:|-)([a-z0-9][a-z0-9-.]+[a-z0-9](?:\.eth))(\s|$)/gi,

            // ENS With ZWCs
            /(^|\s|:|-)([a-z0-9][a-z0-9-.]*(\u200B|\u200C|\u200D|\uFEFF|\u2028|\u2029||)[a-z0-9]*(?:\.eth))(\s|$)/gi
        ];

        // Register RegEx Matching Patterns
        this.matchPatterns = [
            // Ethereum Match Pattern
            /((?:0x)[0-9a-fA-F]{40})/gi,

            // ENS Match Pattern
            this.regExPatterns[1],

            // ENS With ZWCs
            this.regExPatterns[2]
        ];

        // Register Replace Patterns
        this.replacePatterns = [
            // Ethereum Address Replace
            '$1' +
            '

 $2' +
            '$3',

            // ENS Address Replace
            '$1$2$3',

            // ENS With ZWCs Replace
            '$1<slot title="WARNING! This ENS address has ZWCs. Someone may be trying to scam you." ' +
            'class="ext-etheraddresslookup-warning">$2</slot>$3'
        ];
    }

    /**
     * @name Set Warning Settings
     * @desc
     */
    setWarningSettings()
    {
        // The block explorers that can handle ENS addresses
        this.ENSCompatiableExplorers = [
            "https://etherscan.io/address",
            "https://etherchain.org/account"
        ];

        // Does the user's favorite explorer support an ENS address
        for(var i=0; i<this.ENSCompatiableExplorers.length; i++){
            if(this.strBlockchainExplorer == this.ENSCompatiableExplorers[i]){
                this.ENSCompatiable = true;
                break;
            }
            this.ENSCompatiable = false;
        }

        // On failure give the user a warning.
        if(!this.ENSCompatiable){
            this.replacePatterns[1] = '$1$2$3';
        }
    }

    manipulateDOM()
    {
        if(true || this.intSettingsCount === this.intSettingsTotalCount) {
            if(this.blBlacklistDomains) {
                this.blacklistedDomainCheck();
            }
            this.convertAddressToLink();
        }
    }

    /**
     * @name Convert Address To Link
     * @desc Finds Ethereum addresses and converts to a link to a block explorer
     */
    convertAddressToLink()
    {
        var arrWhitelistedTags = ["code", "span", "p", "td", "li", "em", "i", "b", "strong", "small"];

        //Get the whitelisted nodes
        for(var i=0; i<arrWhitelistedTags.length; i++) {
            var objNodes = document.getElementsByTagName(arrWhitelistedTags[i]);
            //Loop through the whitelisted content
            for(var x=0; x<objNodes.length; x++) {

                if(this.hasIgnoreAttributes(objNodes[x])){
                    continue;
                }

                this.convertAddresses(objNodes[x]);
            }
        }

        this.tidyUpSlots();
        this.addBlockies();

        if(this.blHighlight) {
            this.addHighlightStyle();
        }
    }

    /**
     * @name Convert Addresses
     * @desc Takes a Node and checks if any of its children are textNodes. On success replace textNode with slot node
     * @desc slot node contains regex replaced content; see generateReplacementContent()
     * @param {Node} objNode
     */
    convertAddresses(objNode)
    {
        // Some nodes have non-textNode children
        // we need to ensure regex is applied only to text otherwise we will mess the html up
        for(var i=0; i < objNode.childNodes.length; i++){
            // Only check textNodes to prevent applying RegEx against element attributes
            if(objNode.childNodes[i].nodeType == 3){ // nodeType 3 = a text node

                var child = objNode.childNodes[i];
                var childContent = child.textContent;

                // Only start replacing stuff if the we get a RegEx match.
                if(this.isPatternMatched(childContent)) {
                    // https://developer.mozilla.org/en-US/docs/Web/HTML/Element/slot
                    var replacement = document.createElement('slot');
                    replacement.setAttribute('class', 'ext-etheraddresslookup-temporary');
                    replacement.innerHTML = this.generateReplacementContent(childContent);
                    objNode.replaceChild(replacement, child);
                }
            }
        }
    }

    /**
     * @name Generate Replacement Content
     * @desc Takes string and replaces any regex pattern matches with the associated replace patterns
     * @param {string} content
     * @returns {string}
     */
    generateReplacementContent(content)
    {
        for(var i=0; i < this.regExPatterns.length; i++){
            content = content.replace(this.regExPatterns[i], this.replacePatterns[i]);
        }
        return content;
    }

    /**
     * @name Has Ignore Attributes
     * @desc Checks if a node contains any attribute that we want to avoid manipulating
     * @param {Element} node
     * @returns {boolean}
     */
    hasIgnoreAttributes(node)
    {
        var ignoreAttributes = {
            "class": ["ng-binding"]
        };

        // Loop through all attributes we want to test for ignoring
        for(var attributeName in ignoreAttributes){
            // Filter out the object's default properties
            if (ignoreAttributes.hasOwnProperty(attributeName)) {

                // Check this node has the attribute we are currently checking for
                if(node.hasAttribute(attributeName)){

                    // This node's value for the attribute we are checking
                    var nodeAttributeValue = node.getAttribute(attributeName);
                    // The values we want to ignore for this attribute
                    var badAttributeValueList = ignoreAttributes[attributeName];

                    // Loop through the attribute values we want to ignore
                    for(var i=0; i < badAttributeValueList.length; i++){
                        // If we find an indexOf, this value is present in the attribute
                        if(nodeAttributeValue.indexOf(badAttributeValueList[i]) !== -1){
                            return true;
                        }
                    }

                }

            }
        }

        return false;
    }

    /**
     * @name Is Pattern Matched
     * @desc Checks content matches any of the object's matchPatterns
     * @param {string} content
     * @returns {boolean}
     */
    isPatternMatched(content)
    {
        for(var i=0; i < this.matchPatterns.length; i++){
            if(this.matchPatterns[i].exec(content) !== null){
                return true;
            }
        }
        return false;
    }

    /**
     * @name Is Blockchain Explorer Site
     * @desc Check if the current website is the user's selected block explorer
     * @returns {boolean}
     */
    isBlockchainExplorerSite()
    {
        var objBlockchainExplorer = document.createElement("a");
        objBlockchainExplorer.href = this.strBlockchainExplorer;
        return (objBlockchainExplorer.hostname === window.location.hostname);
    }

    /**
     * @name Tidy Slots
     * @desc Searches document for slots adds the slot's child nodes to its parent then removes the slot
     */
    tidyUpSlots()
    {
        var slots = document.querySelectorAll("slot.ext-etheraddresslookup-temporary");
        for(var i=0; i < slots.length; i++){
            while(slots[i].childNodes.length > 0){
                slots[i].parentNode.insertBefore(slots[i].firstChild, slots[i]);
            }
            slots[i].parentNode.removeChild(slots[i]);
        }
    }

    addBlockies()
    {
        var blockieDivs = document.querySelectorAll("div.ext-etheraddresslookup-blockie");
        for(var i = 0; i < blockieDivs.length; i++){

            blockieDivs[i].style.backgroundImage = 'url(' + blockies.create({
                // toLowerCase is used because standard blockies are based on none-checksum Ethereum addresses
                seed:blockieDivs[i].getAttribute('data-ether-address').toLowerCase(),
                size: 8,
                scale: 16
            }).toDataURL() +')';
        }
    }

    //Removes the highlight style from Ethereum addresses
    removeHighlightStyle()
    {
        var objEtherAddresses = document.getElementsByClassName("ext-etheraddresslookup-link");
        for (var i = 0; i < objEtherAddresses.length; i++) {
            objEtherAddresses[i].classList.add("ext-etheraddresslookup-link-no_highlight");
            objEtherAddresses[i].classList.add("ext-etheraddresslookup-link-highlight");
        }
        return false;
    }

    //Adds the highlight style
    addHighlightStyle()
    {
        var objEtherAddresses = document.getElementsByClassName("ext-etheraddresslookup-link");
        for (var i = 0; i < objEtherAddresses.length; i++) {
            objEtherAddresses[i].classList.add("ext-etheraddresslookup-link-highlight");
            objEtherAddresses[i].classList.remove("ext-etheraddresslookup-link-no_highlight");
        }
        return false;
    }
}

window.addEventListener("load", function() {
    let objEtherAddressLookup = new EtherAddressLookup();
});

//Send message from the extension to here.
objBrowser.runtime.onMessage.addListener(
    function(request, sender, sendResponse) {
        let objEtherAddressLookup = new EtherAddressLookup();
        if(typeof request.func !== "undefined") {
            if(typeof objEtherAddressLookup[request.func] == "function") {
                objEtherAddressLookup[request.func]();
                sendResponse({status: "ok"});
                return true;
            }
        }
        sendResponse({status: "fail"});
    }
);
<commit_msg>Fix to RegEx replace string<commit_after>let objBrowser = chrome ? chrome : browser;

class EtherAddressLookup {

    constructor()
    {
        console.log("Init EAL");
        this.setDefaultExtensionSettings();
        this.init();
    }

    setDefaultExtensionSettings()
    {
        this.blHighlight = false;
        this.strBlockchainExplorer = "https://etherscan.io/address";

        this.intSettingsCount = 0;
        this.intSettingsTotalCount = 2;
    }

    /**
     * @name init
     * @desc Gets extension settings and applies DOM manipulation
     */
    init()
    {
        let objBrowser = chrome ? chrome : browser;
        //Get the highlight option for the user
        objBrowser.runtime.sendMessage({func: "highlight_option"}, function(objResponse) {
            if(objResponse && objResponse.hasOwnProperty("resp")) {
                this.blHighlight = (objResponse.resp == 1);
            }
            ++this.intSettingsCount;
        }.bind(this));

        //Get the blockchain explorer for the user
        objBrowser.runtime.sendMessage({func: "blockchain_explorer"}, function(objResponse) {
            this.strBlockchainExplorer = objResponse.resp;
            ++this.intSettingsCount;
        }.bind(this));

        //Update the DOM once all settings have been received...
        setTimeout(function() {
            // Needs to happen after user settings have been collected
            // and in the context of init();
            this.setSearchAndReplaceSettings();
            this.setWarningSettings();
            this.manipulateDOM();
        }.bind(this), 10);
    }

    /**
     * @name Set Search And Replace Settings
     * @desc
     */
    setSearchAndReplaceSettings()
    {
        // Check user is on their favourite block explorer, on fail target blank.
        this.target = (this.isBlockchainExplorerSite() ? '_self' : '_blank');

        // Register RegEx Patterns
        this.regExPatterns = [
            // Ethereum Address Regex
            /(^|\s|:|-)((?:0x)[0-9a-fA-F]{40})(\s|$)/gi,

            // ENS Address Regex
            /(^|\s|:|-)([a-z0-9][a-z0-9-.]+[a-z0-9](?:\.eth))(\s|$)/gi,

            // ENS With ZWCs
            /(^|\s|:|-)([a-z0-9][a-z0-9-.]*(\u200B|\u200C|\u200D|\uFEFF|\u2028|\u2029||)[a-z0-9]*(?:\.eth))(\s|$)/gi
        ];

        // Register RegEx Matching Patterns
        this.matchPatterns = [
            // Ethereum Match Pattern
            /((?:0x)[0-9a-fA-F]{40})/gi,

            // ENS Match Pattern
            this.regExPatterns[1],

            // ENS With ZWCs
            this.regExPatterns[2]
        ];

        // Register Replace Patterns
        this.replacePatterns = [
            // Ethereum Address Replace
            '$1' +
            '

 $2' +
            '$3',

            // ENS Address Replace
            '$1$2$3',

            // ENS With ZWCs Replace
            '$1<slot title="WARNING! This ENS address has ZWCs. Someone may be trying to scam you." ' +
            'class="ext-etheraddresslookup-warning">$2</slot>$3'
        ];
    }

    /**
     * @name Set Warning Settings
     * @desc
     */
    setWarningSettings()
    {
        // The block explorers that can handle ENS addresses
        this.ENSCompatiableExplorers = [
            "https://etherscan.io/address",
            "https://etherchain.org/account"
        ];

        // Does the user's favorite explorer support an ENS address
        for(var i=0; i<this.ENSCompatiableExplorers.length; i++){
            if(this.strBlockchainExplorer == this.ENSCompatiableExplorers[i]){
                this.ENSCompatiable = true;
                break;
            }
            this.ENSCompatiable = false;
        }

        // On failure give the user a warning.
        if(!this.ENSCompatiable){
            this.replacePatterns[1] = '$1$2$3';
        }
    }

    manipulateDOM()
    {
        if(true || this.intSettingsCount === this.intSettingsTotalCount) {
            if(this.blBlacklistDomains) {
                this.blacklistedDomainCheck();
            }
            this.convertAddressToLink();
        }
    }

    /**
     * @name Convert Address To Link
     * @desc Finds Ethereum addresses and converts to a link to a block explorer
     */
    convertAddressToLink()
    {
        var arrWhitelistedTags = ["code", "span", "p", "td", "li", "em", "i", "b", "strong", "small"];

        //Get the whitelisted nodes
        for(var i=0; i<arrWhitelistedTags.length; i++) {
            var objNodes = document.getElementsByTagName(arrWhitelistedTags[i]);
            //Loop through the whitelisted content
            for(var x=0; x<objNodes.length; x++) {

                if(this.hasIgnoreAttributes(objNodes[x])){
                    continue;
                }

                this.convertAddresses(objNodes[x]);
            }
        }

        this.tidyUpSlots();
        this.addBlockies();

        if(this.blHighlight) {
            this.addHighlightStyle();
        }
    }

    /**
     * @name Convert Addresses
     * @desc Takes a Node and checks if any of its children are textNodes. On success replace textNode with slot node
     * @desc slot node contains regex replaced content; see generateReplacementContent()
     * @param {Node} objNode
     */
    convertAddresses(objNode)
    {
        // Some nodes have non-textNode children
        // we need to ensure regex is applied only to text otherwise we will mess the html up
        for(var i=0; i < objNode.childNodes.length; i++){
            // Only check textNodes to prevent applying RegEx against element attributes
            if(objNode.childNodes[i].nodeType == 3){ // nodeType 3 = a text node

                var child = objNode.childNodes[i];
                var childContent = child.textContent;

                // Only start replacing stuff if the we get a RegEx match.
                if(this.isPatternMatched(childContent)) {
                    // https://developer.mozilla.org/en-US/docs/Web/HTML/Element/slot
                    var replacement = document.createElement('slot');
                    replacement.setAttribute('class', 'ext-etheraddresslookup-temporary');
                    replacement.innerHTML = this.generateReplacementContent(childContent);
                    objNode.replaceChild(replacement, child);
                }
            }
        }
    }

    /**
     * @name Generate Replacement Content
     * @desc Takes string and replaces any regex pattern matches with the associated replace patterns
     * @param {string} content
     * @returns {string}
     */
    generateReplacementContent(content)
    {
        for(var i=0; i < this.regExPatterns.length; i++){
            content = content.replace(this.regExPatterns[i], this.replacePatterns[i]);
        }
        return content;
    }

    /**
     * @name Has Ignore Attributes
     * @desc Checks if a node contains any attribute that we want to avoid manipulating
     * @param {Element} node
     * @returns {boolean}
     */
    hasIgnoreAttributes(node)
    {
        var ignoreAttributes = {
            "class": ["ng-binding"]
        };

        // Loop through all attributes we want to test for ignoring
        for(var attributeName in ignoreAttributes){
            // Filter out the object's default properties
            if (ignoreAttributes.hasOwnProperty(attributeName)) {

                // Check this node has the attribute we are currently checking for
                if(node.hasAttribute(attributeName)){

                    // This node's value for the attribute we are checking
                    var nodeAttributeValue = node.getAttribute(attributeName);
                    // The values we want to ignore for this attribute
                    var badAttributeValueList = ignoreAttributes[attributeName];

                    // Loop through the attribute values we want to ignore
                    for(var i=0; i < badAttributeValueList.length; i++){
                        // If we find an indexOf, this value is present in the attribute
                        if(nodeAttributeValue.indexOf(badAttributeValueList[i]) !== -1){
                            return true;
                        }
                    }

                }

            }
        }

        return false;
    }

    /**
     * @name Is Pattern Matched
     * @desc Checks content matches any of the object's matchPatterns
     * @param {string} content
     * @returns {boolean}
     */
    isPatternMatched(content)
    {
        for(var i=0; i < this.matchPatterns.length; i++){
            if(this.matchPatterns[i].exec(content) !== null){
                return true;
            }
        }
        return false;
    }

    /**
     * @name Is Blockchain Explorer Site
     * @desc Check if the current website is the user's selected block explorer
     * @returns {boolean}
     */
    isBlockchainExplorerSite()
    {
        var objBlockchainExplorer = document.createElement("a");
        objBlockchainExplorer.href = this.strBlockchainExplorer;
        return (objBlockchainExplorer.hostname === window.location.hostname);
    }

    /**
     * @name Tidy Slots
     * @desc Searches document for slots adds the slot's child nodes to its parent then removes the slot
     */
    tidyUpSlots()
    {
        var slots = document.querySelectorAll("slot.ext-etheraddresslookup-temporary");
        for(var i=0; i < slots.length; i++){
            while(slots[i].childNodes.length > 0){
                slots[i].parentNode.insertBefore(slots[i].firstChild, slots[i]);
            }
            slots[i].parentNode.removeChild(slots[i]);
        }
    }

    addBlockies()
    {
        var blockieDivs = document.querySelectorAll("div.ext-etheraddresslookup-blockie");
        for(var i = 0; i < blockieDivs.length; i++){

            blockieDivs[i].style.backgroundImage = 'url(' + blockies.create({
                // toLowerCase is used because standard blockies are based on none-checksum Ethereum addresses
                seed:blockieDivs[i].getAttribute('data-ether-address').toLowerCase(),
                size: 8,
                scale: 16
            }).toDataURL() +')';
        }
    }

    //Removes the highlight style from Ethereum addresses
    removeHighlightStyle()
    {
        var objEtherAddresses = document.getElementsByClassName("ext-etheraddresslookup-link");
        for (var i = 0; i < objEtherAddresses.length; i++) {
            objEtherAddresses[i].classList.add("ext-etheraddresslookup-link-no_highlight");
            objEtherAddresses[i].classList.add("ext-etheraddresslookup-link-highlight");
        }
        return false;
    }

    //Adds the highlight style
    addHighlightStyle()
    {
        var objEtherAddresses = document.getElementsByClassName("ext-etheraddresslookup-link");
        for (var i = 0; i < objEtherAddresses.length; i++) {
            objEtherAddresses[i].classList.add("ext-etheraddresslookup-link-highlight");
            objEtherAddresses[i].classList.remove("ext-etheraddresslookup-link-no_highlight");
        }
        return false;
    }
}

window.addEventListener("load", function() {
    let objEtherAddressLookup = new EtherAddressLookup();
});

//Send message from the extension to here.
objBrowser.runtime.onMessage.addListener(
    function(request, sender, sendResponse) {
        let objEtherAddressLookup = new EtherAddressLookup();
        if(typeof request.func !== "undefined") {
            if(typeof objEtherAddressLookup[request.func] == "function") {
                objEtherAddressLookup[request.func]();
                sendResponse({status: "ok"});
                return true;
            }
        }
        sendResponse({status: "fail"});
    }
);
<|endoftext|>


<commit_before>// Description:
//   Buy soda or list balance
//
// Configuration:
//   SODA_TOKEN,
//   SODA_URL,
//   MEMBERS_URL
//
// Commands
//   hubot kjøp <produkt_navn> - Purchase a product from Brus
//   hubot brus produkter - Get a list of products from brus.abakus.no
//   hubot saldo brus - Get your balance brus.abakus.no

const _ = require('lodash');
const fetch = require('node-fetch');
const members = require('../lib/members');
const logger = require('../lib/log');

const { SODA_TOKEN, SODA_URL } = process.env;

const sodaMappings = {
  ':dahls:': 'beer_dahls_bottle',
  ':dahls-jul:': 'beer_dahls_jul_bottle'
};

function brus(path, options = {}) {
  return fetch(
    `${SODA_URL}/api/liste${path}`,
    Object.assign(
      {
        method: 'GET',
        headers: {
          Authorization: `Token ${SODA_TOKEN}`,
          'Content-Type': 'application/json'
        }
      },
      options
    )
  );
}

function getSodaName(slackName) {
  return members(`?slack=${slackName}`).then(body => {
    const user = body[0];

    if (!user || !user.brus) {
      throw new Error(`Fant ikke ${slackName} på brus.`);
    }

    return user.brus;
  });
}
function getProducts() {
  return brus(`/products/`, {
    method: 'GET'
  })
    .then(response => {
      if (!response.ok) {
        throw new Error(`Jeg klarte ikke hente produktene fra brus`);
      }
      return response.json();
    })
    .then(body => {
      return body
        .map(product => `- \`${product.key}\`: ${product.current_price} kr`)
        .join('\n');
    });
}

function purchaseSoda(slackName, sodaType, count = 1) {
  return getSodaName(slackName).then(name => {
    return brus(`/purchase/`, {
      method: 'POST',
      body: JSON.stringify({
        name,
        shopping_cart: [
          {
            product_name: sodaMappings[sodaType] || sodaType,
            count
          }
        ]
      })
    }).then(response => {
      if (!response.ok) {
        if (response.status === 404) {
          throw new Error(
            `Jeg klarte ikke å kjøpe brusen: fant ikke noe produkt med navnet \`${sodaType}\`\nSkriv \`hubot brus produkter\` for å se listen`
          );
        } else {
          throw new Error(`Jeg klarte ikke å kjøpe brusen: ${response.status}`);
        }
      }

      return response.json();
    });
  });
}

module.exports = robot => {
  Object.keys(sodaMappings).map(sodaKey => {
    console.log('Mapping thing:', sodaKey, new RegExp(`(-|)(${sodaKey})`));

    robot.hear(new RegExp(`(-|)(${sodaKey})`), msg => {
      console.log('Found match', msg);
      if (msg.message.room === '#brus' || msg.message.room === 'brus') {
        logger.log(msg);
        const send = msg.send.bind(msg);
        const isMinus = msg.match[1].trim() === '-';
        purchaseSoda(msg.message.user.name, sodaKey, isMinus ? -1 : 1).catch(
          error => send(error.message)
        );
      }
    });
  });
  robot.hear(/./i, msg => {
    console.log('matching', msg);
  });
  robot.hear(/:dahls:/i, msg => {
    console.log('Found match', msg);
    if (msg.message.room === '#brus' || msg.message.room === 'brus') {
      logger.log(msg);
      const send = msg.send.bind(msg);
      const isMinus = false;
      purchaseSoda(msg.message.user.name, sodaKey, isMinus ? -1 : 1).catch(
        error => send(error.message)
      );
    }
  });

  robot.respond(/kjøp (.*)?/i, msg => {
    logger.log(msg);
    const send = msg.send.bind(msg);
    const productName = msg.match[1].trim();
    purchaseSoda(msg.message.user.name, productName).catch(error =>
      send(error.message)
    );
  });

  robot.respond(/brus produkter/i, msg => {
    logger.log(msg);
    const send = msg.send.bind(msg);
    getProducts()
      .then(send)
      .catch(error => send(error.message));
  });

  robot.respond(/saldo brus( .*)?/i, msg => {
    logger.log(msg);
    const send = msg.send.bind(msg);
    const slackName = msg.match[1]
      ? msg.match[1].trim()
      : msg.message.user.name;

    getSodaName(slackName)
      .then(name => {
        return brus(`/${name}/`)
          .then(response => {
            if (response.status !== 200) {
              throw new Error(
                `Jeg klarte ikke hente info fra brus (${response.status}).`
              );
            }

            return response.json();
          })
          .then(
            // TODO: list products
            body =>
              `${body.name} sin saldo er ${
                body.balance
              } spenn. (TODO: produktliste, @noen fix)`
          );
      })
      .then(send)
      .catch(error => send(error.message));
  });
};
<commit_msg>Even more<commit_after>// Description:
//   Buy soda or list balance
//
// Configuration:
//   SODA_TOKEN,
//   SODA_URL,
//   MEMBERS_URL
//
// Commands
//   hubot kjøp <produkt_navn> - Purchase a product from Brus
//   hubot brus produkter - Get a list of products from brus.abakus.no
//   hubot saldo brus - Get your balance brus.abakus.no

const _ = require('lodash');
const fetch = require('node-fetch');
const members = require('../lib/members');
const logger = require('../lib/log');

const { SODA_TOKEN, SODA_URL } = process.env;

const sodaMappings = {
  ':dahls:': 'beer_dahls_bottle',
  ':dahls-jul:': 'beer_dahls_jul_bottle'
};

function brus(path, options = {}) {
  return fetch(
    `${SODA_URL}/api/liste${path}`,
    Object.assign(
      {
        method: 'GET',
        headers: {
          Authorization: `Token ${SODA_TOKEN}`,
          'Content-Type': 'application/json'
        }
      },
      options
    )
  );
}

function getSodaName(slackName) {
  return members(`?slack=${slackName}`).then(body => {
    const user = body[0];

    if (!user || !user.brus) {
      throw new Error(`Fant ikke ${slackName} på brus.`);
    }

    return user.brus;
  });
}
function getProducts() {
  return brus(`/products/`, {
    method: 'GET'
  })
    .then(response => {
      if (!response.ok) {
        throw new Error(`Jeg klarte ikke hente produktene fra brus`);
      }
      return response.json();
    })
    .then(body => {
      return body
        .map(product => `- \`${product.key}\`: ${product.current_price} kr`)
        .join('\n');
    });
}

function purchaseSoda(slackName, sodaType, count = 1) {
  return getSodaName(slackName).then(name => {
    return brus(`/purchase/`, {
      method: 'POST',
      body: JSON.stringify({
        name,
        shopping_cart: [
          {
            product_name: sodaMappings[sodaType] || sodaType,
            count
          }
        ]
      })
    }).then(response => {
      if (!response.ok) {
        if (response.status === 404) {
          throw new Error(
            `Jeg klarte ikke å kjøpe brusen: fant ikke noe produkt med navnet \`${sodaType}\`\nSkriv \`hubot brus produkter\` for å se listen`
          );
        } else {
          throw new Error(`Jeg klarte ikke å kjøpe brusen: ${response.status}`);
        }
      }

      return response.json();
    });
  });
}

module.exports = robot => {
  Object.keys(sodaMappings).map(sodaKey => {
    console.log('Mapping thing:', sodaKey, new RegExp(`(-|)(${sodaKey})`));

    robot.hear(new RegExp(`(-|)(${sodaKey})`), msg => {
      console.log('Found match', msg);
      if (msg.message.room === '#brus' || msg.message.room === 'brus') {
        logger.log(msg);
        const send = msg.send.bind(msg);
        const isMinus = msg.match[1].trim() === '-';
        purchaseSoda(msg.message.user.name, sodaKey, isMinus ? -1 : 1).catch(
          error => send(error.message)
        );
      }
    });
  });
  robot.hear(/./i, msg => {
    console.log('matching', msg);
  });

  robot.hear(/:dahls:/i, msg => {
    console.log('Found match', msg);
    if (msg.message.room === '#brus' || msg.message.room === 'brus') {
      logger.log(msg);
      const send = msg.send.bind(msg);
      const isMinus = false;
      purchaseSoda(msg.message.user.name, sodaKey, isMinus ? -1 : 1).catch(
        error => send(error.message)
      );
    }
  });

  robot.respond(/kjøp (.*)?/i, msg => {
    logger.log(msg);
    const send = msg.send.bind(msg);
    const productName = msg.match[1].trim();
    purchaseSoda(msg.message.user.name, productName).catch(error =>
      send(error.message)
    );
  });

  robot.respond(/brus produkter/i, msg => {
    logger.log(msg);
    const send = msg.send.bind(msg);
    getProducts()
      .then(send)
      .catch(error => send(error.message));
  });

  robot.respond(/saldo brus( .*)?/i, msg => {
    logger.log(msg);
    const send = msg.send.bind(msg);
    const slackName = msg.match[1]
      ? msg.match[1].trim()
      : msg.message.user.name;

    getSodaName(slackName)
      .then(name => {
        return brus(`/${name}/`)
          .then(response => {
            if (response.status !== 200) {
              throw new Error(
                `Jeg klarte ikke hente info fra brus (${response.status}).`
              );
            }

            return response.json();
          })
          .then(
            // TODO: list products
            body =>
              `${body.name} sin saldo er ${
                body.balance
              } spenn. (TODO: produktliste, @noen fix)`
          );
      })
      .then(send)
      .catch(error => send(error.message));
  });
};
<|endoftext|>


<commit_before>// Copyright 2002-2013, University of Colorado Boulder

/**
 * Shows the about dialog.
 *
 * @author Sam Reid
 */
define( function( require ) {
  'use strict';

  var VBox = require( 'SCENERY/nodes/VBox' );
  var Rectangle = require( 'SCENERY/nodes/Rectangle' );
  var Text = require( 'SCENERY/nodes/Text' );
  var inherit = require( 'PHET_CORE/inherit' );
  var ScreenView = require( 'JOIST/ScreenView' );
  var Panel = require( 'SUN/Panel' );
  var CheckBox = require( 'SUN/CheckBox' );
  var PhetFont = require( 'SCENERY_PHET/PhetFont' );
  var Property = require( 'AXON/Property' );
  var TextButton = require( 'SUN/TextButton' );

  // strings
  var showPointersString = require( 'string!JOIST/showPointers' );
  var doneString = require( 'string!JOIST/done' );
  var settingsString = require( 'string!JOIST/title.settings' );

  /**
   * @param {Sim} sim
   * @constructor
   */
  function SettingsDialog( sim ) {
    var settingsDialog = this;

    //Use view, to help center and scale content
    ScreenView.call( this, {renderer: 'svg'} );

    var content = new VBox( { align: 'center', spacing: 50, children: [
      new Text( settingsString, { font: new PhetFont( 16 ) } ),
      new CheckBox( new Text( showPointersString ), sim.showPointersProperty ),
      new TextButton( doneString, {
          callback: function() {
            settingsDialog.doneListeners.forEach( function( listener ) {
              listener();
            } );
          }
        } )
    ]} );

    //Show a gray overlay that will help focus on the about dialog, and prevent clicks on the sim while the dialog is up
    this.addChild( new Panel( content, {centerX: this.layoutBounds.centerX, centerY: this.layoutBounds.centerY, xMargin: 20, yMargin: 20 } ) );

    function resize() {
      settingsDialog.layout( $( window ).width(), $( window ).height() );
    }

    //Fit to the window and render the initial scene
    $( window ).resize( resize );
    resize();
    this.doneListeners = [];
  }

  return inherit( ScreenView, SettingsDialog, {
    addDoneListener: function( listener ) {
      this.doneListeners.push( listener );
    }
  } );
} );
<commit_msg>sun#21, rename 'callback' option to 'listener' for push buttons, for symmetry with addListener, update all sims accordingly.<commit_after>// Copyright 2002-2013, University of Colorado Boulder

/**
 * Shows the about dialog.
 *
 * @author Sam Reid
 */
define( function( require ) {
  'use strict';

  var VBox = require( 'SCENERY/nodes/VBox' );
  var Rectangle = require( 'SCENERY/nodes/Rectangle' );
  var Text = require( 'SCENERY/nodes/Text' );
  var inherit = require( 'PHET_CORE/inherit' );
  var ScreenView = require( 'JOIST/ScreenView' );
  var Panel = require( 'SUN/Panel' );
  var CheckBox = require( 'SUN/CheckBox' );
  var PhetFont = require( 'SCENERY_PHET/PhetFont' );
  var Property = require( 'AXON/Property' );
  var TextButton = require( 'SUN/TextButton' );

  // strings
  var showPointersString = require( 'string!JOIST/showPointers' );
  var doneString = require( 'string!JOIST/done' );
  var settingsString = require( 'string!JOIST/title.settings' );

  /**
   * @param {Sim} sim
   * @constructor
   */
  function SettingsDialog( sim ) {
    var settingsDialog = this;

    //Use view, to help center and scale content
    ScreenView.call( this, {renderer: 'svg'} );

    var content = new VBox( { align: 'center', spacing: 50, children: [
      new Text( settingsString, { font: new PhetFont( 16 ) } ),
      new CheckBox( new Text( showPointersString ), sim.showPointersProperty ),
      new TextButton( doneString, {
        listener: function() {
            settingsDialog.doneListeners.forEach( function( listener ) {
              listener();
            } );
          }
        } )
    ]} );

    //Show a gray overlay that will help focus on the about dialog, and prevent clicks on the sim while the dialog is up
    this.addChild( new Panel( content, {centerX: this.layoutBounds.centerX, centerY: this.layoutBounds.centerY, xMargin: 20, yMargin: 20 } ) );

    function resize() {
      settingsDialog.layout( $( window ).width(), $( window ).height() );
    }

    //Fit to the window and render the initial scene
    $( window ).resize( resize );
    resize();
    this.doneListeners = [];
  }

  return inherit( ScreenView, SettingsDialog, {
    addDoneListener: function( listener ) {
      this.doneListeners.push( listener );
    }
  } );
} );
<|endoftext|>


<commit_before>function flacso_adv_search_click()
{
	var checkValues = jQuery("input[name*='adv-search-box-']:checked").map(function()
    {
        return {name: jQuery(this).attr("name").replace("adv-search-box-","").replace("[]", ""), value:jQuery(this).val()};
    }).get();

	var texts = jQuery("input[name*='adv-search-box-input-']").map(function()
    {
		return { name:jQuery(this).attr("name").replace("adv-search-box-input-",""), value:jQuery(this).val()};
    }).get();

	jQuery.ajax({
		type   : 'post',
		url    : 'http://' + window.location.host + '/wp-admin/admin-ajax.php',
		data   : {
			action: 'flacso_adv_search',
			checked: checkValues,
			fields: texts,
			paged: jQuery('input[name="adv-search-paged"]').val()
		},
		success: function(response) {
			jQuery("html, body").animate({ scrollTop: 0 }, "slow");
			jQuery('.general-list').replaceWith(response);
		}
	});

}

jQuery(document).ready(function () {
	var w = jQuery('.clickable').width();
	jQuery('.mutliSelect ul').width(w);

	jQuery('.adv-search-box-button').click(function(){
		flacso_adv_search_click();
	});
});

function flacso_tax_click(name, id)
{
	var checkValues = [{name:name, value:id}];

	jQuery("input[name='adv-search-box-"+name+"[]'][value="+id+"]").attr("checked", "checked");

	if(adv_search_box.gea != '')
	{
		checkValues[1] = {name:'gea', value:adv_search_box.gea}
	}

	if(adv_search_box.library != 1)
	{
		window.location.href = adv_search_box.library_url + "&"+name+"="+id;
		return false;
	}

	var texts = [];

	jQuery.ajax({
		type   : 'post',
		url    : 'http://' + window.location.host + '/wp-admin/admin-ajax.php',
		data   : {
			action: 'flacso_adv_search',
			checked: checkValues,
			fields: texts
		},
		success: function(response) {
			jQuery('.general-list').replaceWith(response);
		}
	});
}

function flacso_adv_searchget_pagenum(num)
{
	jQuery('input[name="adv-search-paged"]').val(num);
	flacso_adv_search_click();
}<commit_msg>label search results<commit_after>function flacso_adv_search_click()
{
	var checkValues = jQuery("input[name*='adv-search-box-']:checked").map(function()
    {
        return {name: jQuery(this).attr("name").replace("adv-search-box-","").replace("[]", ""), value:jQuery(this).val()};
    }).get();

	var texts = jQuery("input[name*='adv-search-box-input-']").map(function()
    {
		return { name:jQuery(this).attr("name").replace("adv-search-box-input-",""), value:jQuery(this).val()};
    }).get();

	jQuery.ajax({
		type   : 'post',
		url    : 'http://' + window.location.host + '/wp-admin/admin-ajax.php',
		data   : {
			action: 'flacso_adv_search',
			checked: checkValues,
			fields: texts,
			paged: jQuery('input[name="adv-search-paged"]').val()
		},
		success: function(response) {
			jQuery("html, body").animate({ scrollTop: 0 }, "slow");
			jQuery('.general-list').replaceWith(response);
			flacso_adv_search_result();
		}
	});

}

function flacso_adv_search_result()
{
	var checkValues = jQuery("input[name*='adv-search-box-']:checked").map(function()
    {
        return {
        	name:  jQuery(this).attr("name").replace("adv-search-box-","").replace("[]", ""),
        	value: jQuery(this).val(),
        	title: jQuery(this).parent().text().trim(),
        	tax_label: jQuery(this).parent().parent().parent().parent().parent().parent().find('.dropdown-checkbox-header-label').text()
        };
    }).get();

	var texts = jQuery("input[name*='adv-search-box-input-']").map(function()
	{
		if(jQuery(this).val().length > 0)
		{
			return {
				name:jQuery(this).attr("name").replace("adv-search-box-input-",""),
				value:jQuery(this).val(),
				label: jQuery(this).parent().find('label').text()
			};
		}
	}).get();

	/*

Biblioteca
					*/

	var html = '
Busca por:
';

	for (i = 0; i < checkValues.length; i++)
	{
		html+="
" + checkValues[i].tax_label + ": "+ checkValues[i].title + "
";
	}
	for (i = 0; i < texts.length; i++)
	{
		html+="
" + texts[i].label + ": "+ texts[i].value + "
";
	}
	html+="";

	jQuery('main article header').first().replaceWith(html);
	jQuery('main article .entry-content').hide();

}

jQuery(document).ready(function () {
	var w = jQuery('.clickable').width();
	jQuery('.mutliSelect ul').width(w);

	jQuery('.adv-search-box-button').click(function(){
		flacso_adv_search_click();
	});
});

function flacso_tax_click(name, id)
{
	var checkValues = [{name:name, value:id}];

	jQuery("input[name='adv-search-box-"+name+"[]'][value="+id+"]").attr("checked", "checked");

	if(adv_search_box.gea != '')
	{
		checkValues[1] = {name:'gea', value:adv_search_box.gea}
	}

	if(adv_search_box.library != 1)
	{
		window.location.href = adv_search_box.library_url + "&"+name+"="+id;
		return false;
	}

	var texts = [];

	jQuery.ajax({
		type   : 'post',
		url    : 'http://' + window.location.host + '/wp-admin/admin-ajax.php',
		data   : {
			action: 'flacso_adv_search',
			checked: checkValues,
			fields: texts
		},
		success: function(response) {
			jQuery("html, body").animate({ scrollTop: 0 }, "slow");
			jQuery('.general-list').replaceWith(response);
			flacso_adv_search_result();
		}
	});
}

function flacso_adv_searchget_pagenum(num)
{
	jQuery('input[name="adv-search-paged"]').val(num);
	flacso_adv_search_click();
}<|endoftext|>


<commit_before>$(document).ready(
  function() {
    console.log('ready');

    var model = new AppModel();
    var handModel = model.handModel;
    var $tbody = $("table tbody")
    var $shapeRow = $tbody.find('tr.shape');
    var $totalRow = $tbody.find('tr.total');
    var $suitRow = {}
    handModel.suits().forEach(
        function(suit) {
            $suitRow[suit.short]=$tbody.find('tr.suit'+suit.short);
        }
    );

    var focusSpades = function() {
	$('input.holding#spades').focus();
    }

    var submitButton =  $('form#handentry input[type="submit"]');

    var disableSubmit= function(label) {
	submitButton.attr('disabled','disabled');
	submitButton.val(label);
    };

    var clearModel = function(e) {
          handModel.clear();
          $('input.holding').val('');
          $('#evaluationscontainer').hide();
	  focusSpades();
    };

    $('input.holding').bind("propertychange change click keyup input paste",
       function(event) {
         var $input = $(this);
         var $form = $(this.form)
         suitLabel = $input.data('suit');
         hString = $input.val().toUpperCase();
         if (hString != $input.val()) {
             $input.val(hString);
         }

         // if the string ends with 1, treat it as ending with 10, except when
         // exiting field, for purposes of validating entry inline
         if (hString.slice(-1)=='1' && event.type !='change' && event.type!='propertychange') {
             hString = hString+'0';
             appended = true;
         } else {
             appended = false;
         }

         var valid = handModel.setHolding(suitLabel,hString);
         if (!valid) {
           $input.addClass('invalid');
         } else {
           $input.removeClass('invalid');
         }

         if (handModel.isComplete() && !appended) {
	     submitButton.val('Evaluate');
	     submitButton.removeAttr('disabled');

         } else {
	     if (handModel.length<0) {
		 disableSubmit('Invalid');
	     } else if (appended) {
                 disableSubmit('Incomplete 10');
	     } else if (handModel.length==0) {
		 disableSubmit('Evaluate');
             } else {
		 disableSubmit(handModel.length.toString() + " cards");
	     }
         }
       }
    );
     window.onpopstate = function(event) {
         stateHash = handModel.urlHash()
         if (stateHash==null) {
             if (window.location.hash != '') {
                 initializeHash();
             }
         } else if (window.location.hash != '#' + stateHash) {
                 initializeHash();
         }
     }

     var handleSubmit = function() {
         var container = $('#evaluationscontainer');
         container.hide(250);

         window.location.hash = handModel.urlHash()
         var shape = handModel.shape();
         $shapeRow.find('.key').text(shape.short);
         handModel.suits().forEach(
             function (suit) {
                 $suitRow[suit.short].find('.holding').text(handModel.text(suit));
             }
         );

         var evaluations = model.evaluate();
         var toText = function(val) {
             if (val == null) {
                 return "n/a";
             } else {
                 return val;
             }
         }

         evaluations.forEach(
            function (evaluation) {
                var evalClass = '.' + evaluation['name'];
                var update = function(row,value) {
                    row.find(evalClass).html(toText(value));
                }

                update($shapeRow,evaluation.shape);
                update($totalRow,evaluation.total);
                handModel.suits().forEach(
                    function(suit) {
                        update($suitRow[suit.short],evaluation[suit.short])
                    }
                );
            }
         );

         container.finish();
         container.show('fast');
     }

     var initializeHash=function() {
         hash = window.location.hash
         clearModel();
         if (hash != '' && hash!='#' || hash!=null) {
            hStrings = hash.substring(1).split(',',4);
            map = {'S':'','H':'','D':'','C':'' }
            for (i=0; i<hStrings.length; i++) {
                suit = handModel.suits().entry(i).short;
                map[suit] = hStrings[i];
            }
            $('input.holding').each(
               function(index,entry) {
                   var $entry = $(entry);
                   $entry.val(map[$entry.data('suit')]);
                   $entry.triggerHandler('change');
               }
            );
            if (handModel.isComplete()) {
                handleSubmit();
            } else {
               $('#evaluationscontainer').hide(500);
            }

         }
     }

     initializeHash();

    $('#handentry').submit(
      function(e) {
        e.preventDefault();
        handleSubmit();
      }
    );

    $('input#clear').click(
       function (e) {
          clearModel();
          window.location.hash = '';
       }
    );

    var currentOpenNote = null;
    $('.notes h4').each(
       function() {
           var $this = $(this);
           $this.data('section',$this.next('.section'));
       }
    );

    var myStorage;
    if (typeof(Storage) !== "undefined") {
        myStorage = window.localStorage;
    } else {
	myStorage = null;
    }

    var storeNotesTab = function(name) {
        if (myStorage != null) {
            myStorage.setItem('NoteSelected',name);
        }
    }

    var getNotesTab = function() {
        if (myStorage === null) {
            return 'about';
        }
        value = myStorage.getItem('NoteSelected');
        if (value=='undefined') {
            return 'about';
        } else {
	    return value;
	}
    }

    $('.notes h4').click(function(e) {
         var $this = $(this);
	 storeNotesTab($this[0].id);
	 if (currentOpenNote != null) {
	     currentOpenNote.data('section').hide(0);
             currentOpenNote.removeClass('selected');
	     if (currentOpenNote.html() == $this.html()) {
		 currentOpenNote = null;
		 return;
	     }
         }
	 currentOpenNote=$this;
	 $this.addClass('selected');
	 currentOpenNote.data('section').show(0);
      }
    );
    var tab = getNotesTab();
    $('.notes h4#' +tab).click();
    focusSpades();
  }
);

<commit_msg>Possible fix #1<commit_after>$(document).ready(
  function() {
    console.log('ready');

    var model = new AppModel();
    var handModel = model.handModel;
    var $tbody = $("table tbody")
    var $shapeRow = $tbody.find('tr.shape');
    var $totalRow = $tbody.find('tr.total');
    var $suitRow = {}
    handModel.suits().forEach(
        function(suit) {
            $suitRow[suit.short]=$tbody.find('tr.suit'+suit.short);
        }
    );

    var focusSpades = function() {
	$('input.holding#spades').focus();
    }

    var submitButton =  $('form#handentry input[type="submit"]');

    var disableSubmit= function(label) {
	submitButton.attr('disabled','disabled');
	submitButton.val(label);
    };

    var clearModel = function(e) {
          handModel.clear();
          $('input.holding').val('');
          $('input.holding').removeClass('invalid');
          $('#evaluationscontainer').hide();
	  disableSubmit('Evaluate');
	  focusSpades();
    };

    $('input.holding').bind("propertychange change click keyup input paste",
       function(event) {
         var $input = $(this);
         var $form = $(this.form)
         suitLabel = $input.data('suit');
         hString = $input.val().toUpperCase();
         if (hString != $input.val()) {
             $input.val(hString);
         }

         // if the string ends with 1, treat it as ending with 10, except when
         // exiting field, for purposes of validating entry inline
         if (hString.slice(-1)=='1' && event.type !='change' && event.type!='propertychange') {
             hString = hString+'0';
             appended = true;
         } else {
             appended = false;
         }

         var valid = handModel.setHolding(suitLabel,hString);
         if (!valid) {
           $input.addClass('invalid');
         } else {
           $input.removeClass('invalid');
         }

         if (handModel.isComplete() && !appended) {
	     submitButton.val('Evaluate');
	     submitButton.removeAttr('disabled');

         } else {
	     if (handModel.length<0) {
		 disableSubmit('Invalid');
	     } else if (appended) {
                 disableSubmit('Incomplete 10');
	     } else if (handModel.length==0) {
		 disableSubmit('Evaluate');
             } else {
		 disableSubmit(handModel.length.toString() + " cards");
	     }
         }
       }
    );
     window.onpopstate = function(event) {
         stateHash = handModel.urlHash()
         if (stateHash==null) {
             if (window.location.hash != '') {
                 initializeHash();
             }
         } else if (window.location.hash != '#' + stateHash) {
                 initializeHash();
         }
     }

     var handleSubmit = function() {
         var container = $('#evaluationscontainer');
         container.hide(250);

         window.location.hash = handModel.urlHash()
         var shape = handModel.shape();
         $shapeRow.find('.key').text(shape.short);
         handModel.suits().forEach(
             function (suit) {
                 $suitRow[suit.short].find('.holding').text(handModel.text(suit));
             }
         );

         var evaluations = model.evaluate();
         var toText = function(val) {
             if (val == null) {
                 return "n/a";
             } else {
                 return val;
             }
         }

         evaluations.forEach(
            function (evaluation) {
                var evalClass = '.' + evaluation['name'];
                var update = function(row,value) {
                    row.find(evalClass).html(toText(value));
                }

                update($shapeRow,evaluation.shape);
                update($totalRow,evaluation.total);
                handModel.suits().forEach(
                    function(suit) {
                        update($suitRow[suit.short],evaluation[suit.short])
                    }
                );
            }
         );

         container.finish();
         container.show('fast');
     }

     var initializeHash=function() {
         hash = window.location.hash
         clearModel();
         if (hash != '' && hash!='#' || hash!=null) {
            hStrings = hash.substring(1).split(',',4);
            map = {'S':'','H':'','D':'','C':'' }
            for (i=0; i<hStrings.length; i++) {
                suit = handModel.suits().entry(i).short;
                map[suit] = hStrings[i];
            }
            $('input.holding').each(
               function(index,entry) {
                   var $entry = $(entry);
                   $entry.val(map[$entry.data('suit')]);
                   $entry.triggerHandler('change');
               }
            );
            if (handModel.isComplete()) {
                handleSubmit();
            } else {
               $('#evaluationscontainer').hide(500);
            }

         }
     }

     initializeHash();

    $('#handentry').submit(
      function(e) {
        e.preventDefault();
        handleSubmit();
      }
    );

    $('input#clear').click(
       function (e) {
          clearModel();
          window.location.hash = '';
       }
    );

    var currentOpenNote = null;
    $('.notes h4').each(
       function() {
           var $this = $(this);
           $this.data('section',$this.next('.section'));
       }
    );

    var myStorage;
    if (false) { // typeof(Storage) !== "undefined") {
        myStorage = window.localStorage;
    } else {
	myStorage = {
            setItem: function(label,value) { },
            getItem: function(label) { return null; }
        }
    }

    var storeNotesTab = function(name) {
	myStorage.setItem('NoteSelected',name);
    }

    var getNotesTab = function() {
        value = myStorage.getItem('NoteSelected');
        if (typeof(value)=='undefined' || value==null) {
            return 'about';
        } else {
	    return value;
	}
    };

    $('.notes h4').click(function(e) {
         var $this = $(this);
	 storeNotesTab($this[0].id);
	 if (currentOpenNote != null) {
	     currentOpenNote.data('section').hide(0);
             currentOpenNote.removeClass('selected');
	     if (currentOpenNote.html() == $this.html()) {
		 currentOpenNote = null;
		 return;
	     }
         }
	 currentOpenNote=$this;
	 $this.addClass('selected');
	 currentOpenNote.data('section').show(0);
      }
    );
    var tab = getNotesTab();
    $('.notes h4#' +tab).click();
    focusSpades();
  }
    );

<|endoftext|>


<commit_before>/*jslint browser: true, white: true, plusplus: true */
/*global angular, console, alert*/

(function () {
  'use strict';

  var app = angular.module('keira2');

  app.controller("GameobjectController", function ($rootScope, $scope, $http, $stateParams, $modal) {

    /* All Gameobject tabs, disabled by default.
    *  Only one tab can be active at a time */
    $scope.gameobjectTabs = {
      search     : false,
      questItems : false,
      template   : false,
      spawns     : false,
      loot       : false,
      script     : false
    };

    /* Init arrays */
    $scope.current_gameobject               = [];
    $scope.new_gameobject                   = [];
    $scope.current_gameobject_loot_template = [];
    $scope.new_gameobject_loot_template     = [];

    /* importing constants */
    $scope.constants = app.goConstants;

    /* Check if a gameobject is selected */
    if ($stateParams.id) {

      /* We have a gameobject selected and default active tab is template */
      $scope.isGameobjectSelected = true;
      $scope.gameobjectTabs.template = true;

      /*  Following lines retrieve all Gameobject data
       *  current_* mantains the database state
       *  new_*     mantains the editor state
       *  we will use those two objects to generate the SQL queries
       */

      /* Retrieve all gameobject_template data */
      $http.get( app.api + "gameobject/template/" + $stateParams.id )
        .success(function (data, status, header, config) {
        $scope.current_gameobject_template = $rootScope.fixNumericValues(data[0]);
        $scope.new_gameobject_template = angular.copy($scope.current_gameobject_template);
        $scope.selectionText = "Gameobject " + $scope.current_gameobject_template.entry;
      })
        .error(function (data, status, header, config) {
        console.log("[ERROR] gameobject/template/" + $stateParams.id + " $http.get request failed");
      });

      /* Retrieve all gameobject_loot_template data */
      $http.get( app.api + "loot/template/gameobject/" + $stateParams.id )
        .success(function (data, status, header, config) {
        $scope.current_gameobject_loot_template = $rootScope.fixNumericValues(data);
        $scope.new_gameobject_loot_template = angular.copy($scope.current_gameobject_loot_template);
      })
        .error(function (data, status, header, config) {
        console.log("[ERROR] loot/template/gameobject/" + $stateParams.id + " $http.get request failed");
      });

      /* Retrieve all gameobject data */
      $http.get( app.api + "gameobject/spawn/id/" + $stateParams.id )
        .success(function (data, status, header, config) {
        $scope.current_gameobject = $rootScope.fixNumericValues(data);
        $scope.new_gameobject = angular.copy($scope.current_gameobject);
      })
        .error(function (data, status, header, config) {
        console.log("[ERROR] gameobject/spawn/id/" + $stateParams.id + " $http.get request failed");
      });

      /* Retrieve all gameobject questitems */
      $http.get( app.api + "gameobject/questitem/" + $stateParams.id )
        .success(function (data, status, header, config) {
        $scope.current_gameobject_questitem = $rootScope.fixNumericValues(data);
        $scope.new_gameobject_questitem = angular.copy($scope.current_gameobject_questitem);
      })
        .error(function (data, status, header, config) {
        console.log("[ERROR] gameobject/questitem/" + $stateParams.id + " $http.get request failed");
      });

    } else {
      /* We have no gameobject selected and default active tab is search */
      $scope.isGameobjectSelected = false;
      $scope.gameobjectTabs.search = true;
      $scope.selectionText = "No GameObject selected. Please use Search to select one.";
    }

    /* [Function] Search */
    $scope.search = function (gameobjectEntry, gameobjectName) {

      if ( gameobjectEntry && !gameobjectName && (gameobjectEntry.length < 2) ) {
        alert("Please insert an ID of at least 2 characters");
        return;
      }
      if ( gameobjectName && !gameobjectEntry && (gameobjectName.length < 3) ) {
        alert("Please insert a Title of at least 3 characters");
        return;
      }

      $http.get( app.api + "search/gameobject/", {
        params: {
          id: gameobjectEntry,
          name: gameobjectName
        }
      }).success(function (data, status, header, config) {
        $scope.gameobjects = $rootScope.fixNumericValues(data);
      })
        .error(function (data, status, header, config) {
        console.log("[ERROR] GAMEOBJECT SEARCH $http.get request failed");
      });

    };

    /* [Function] Generate SQL Script for Gameobject */
    $scope.generateGameobjectScript = function() {

      if (!$scope.isGameobjectSelected) {
        $scope.gameobjectScript = "-- No Gameobject selected";
        return;
      }

      $scope.gameobjectScript = "";

      var whereCondition = "entry = " + $scope.current_gameobject_template.entry;

      $scope.gameobjectScript += app.getUpdateQuery("gameobject_template", whereCondition, $scope.current_gameobject_template, $scope.new_gameobject_template);
    };

    /* [Function] disactive all tabs */
    $scope.disactiveAllTabs = function() {
      angular.forEach($scope.gameobjectTabs, function(value, key) {
        value = false;
      });
    };

    /* [Function] open SQL Script tab */
    $scope.openScriptTab = function() {
      $scope.disactiveAllTabs();
      $scope.generateGameobjectScript();
      $scope.gameobjectTabs.script = true;
    };

  });

}());
<commit_msg>Gameobject: show GO name in the top of the editor<commit_after>/*jslint browser: true, white: true, plusplus: true */
/*global angular, console, alert*/

(function () {
  'use strict';

  var app = angular.module('keira2');

  app.controller("GameobjectController", function ($rootScope, $scope, $http, $stateParams, $modal) {

    /* All Gameobject tabs, disabled by default.
    *  Only one tab can be active at a time */
    $scope.gameobjectTabs = {
      search     : false,
      questItems : false,
      template   : false,
      spawns     : false,
      loot       : false,
      script     : false
    };

    /* Init arrays */
    $scope.current_gameobject               = [];
    $scope.new_gameobject                   = [];
    $scope.current_gameobject_loot_template = [];
    $scope.new_gameobject_loot_template     = [];

    /* importing constants */
    $scope.constants = app.goConstants;

    /* Check if a gameobject is selected */
    if ($stateParams.id) {

      /* We have a gameobject selected and default active tab is template */
      $scope.isGameobjectSelected = true;
      $scope.gameobjectTabs.template = true;

      /*  Following lines retrieve all Gameobject data
       *  current_* mantains the database state
       *  new_*     mantains the editor state
       *  we will use those two objects to generate the SQL queries
       */

      /* Retrieve all gameobject_template data */
      $http.get( app.api + "gameobject/template/" + $stateParams.id )
        .success(function (data, status, header, config) {
        $scope.current_gameobject_template = $rootScope.fixNumericValues(data[0]);
        $scope.new_gameobject_template = angular.copy($scope.current_gameobject_template);
        $scope.selectionText = $scope.current_gameobject_template.name + " (" + $scope.current_gameobject_template.entry + ")";
      })
        .error(function (data, status, header, config) {
        console.log("[ERROR] gameobject/template/" + $stateParams.id + " $http.get request failed");
      });

      /* Retrieve all gameobject_loot_template data */
      $http.get( app.api + "loot/template/gameobject/" + $stateParams.id )
        .success(function (data, status, header, config) {
        $scope.current_gameobject_loot_template = $rootScope.fixNumericValues(data);
        $scope.new_gameobject_loot_template = angular.copy($scope.current_gameobject_loot_template);
      })
        .error(function (data, status, header, config) {
        console.log("[ERROR] loot/template/gameobject/" + $stateParams.id + " $http.get request failed");
      });

      /* Retrieve all gameobject data */
      $http.get( app.api + "gameobject/spawn/id/" + $stateParams.id )
        .success(function (data, status, header, config) {
        $scope.current_gameobject = $rootScope.fixNumericValues(data);
        $scope.new_gameobject = angular.copy($scope.current_gameobject);
      })
        .error(function (data, status, header, config) {
        console.log("[ERROR] gameobject/spawn/id/" + $stateParams.id + " $http.get request failed");
      });

      /* Retrieve all gameobject questitems */
      $http.get( app.api + "gameobject/questitem/" + $stateParams.id )
        .success(function (data, status, header, config) {
        $scope.current_gameobject_questitem = $rootScope.fixNumericValues(data);
        $scope.new_gameobject_questitem = angular.copy($scope.current_gameobject_questitem);
      })
        .error(function (data, status, header, config) {
        console.log("[ERROR] gameobject/questitem/" + $stateParams.id + " $http.get request failed");
      });

    } else {
      /* We have no gameobject selected and default active tab is search */
      $scope.isGameobjectSelected = false;
      $scope.gameobjectTabs.search = true;
      $scope.selectionText = "No GameObject selected. Please use Search to select one.";
    }

    /* [Function] Search */
    $scope.search = function (gameobjectEntry, gameobjectName) {

      if ( gameobjectEntry && !gameobjectName && (gameobjectEntry.length < 2) ) {
        alert("Please insert an ID of at least 2 characters");
        return;
      }
      if ( gameobjectName && !gameobjectEntry && (gameobjectName.length < 3) ) {
        alert("Please insert a Title of at least 3 characters");
        return;
      }

      $http.get( app.api + "search/gameobject/", {
        params: {
          id: gameobjectEntry,
          name: gameobjectName
        }
      }).success(function (data, status, header, config) {
        $scope.gameobjects = $rootScope.fixNumericValues(data);
      })
        .error(function (data, status, header, config) {
        console.log("[ERROR] GAMEOBJECT SEARCH $http.get request failed");
      });

    };

    /* [Function] Generate SQL Script for Gameobject */
    $scope.generateGameobjectScript = function() {

      if (!$scope.isGameobjectSelected) {
        $scope.gameobjectScript = "-- No Gameobject selected";
        return;
      }

      $scope.gameobjectScript = "";

      var whereCondition = "entry = " + $scope.current_gameobject_template.entry;

      $scope.gameobjectScript += app.getUpdateQuery("gameobject_template", whereCondition, $scope.current_gameobject_template, $scope.new_gameobject_template);
    };

    /* [Function] disactive all tabs */
    $scope.disactiveAllTabs = function() {
      angular.forEach($scope.gameobjectTabs, function(value, key) {
        value = false;
      });
    };

    /* [Function] open SQL Script tab */
    $scope.openScriptTab = function() {
      $scope.disactiveAllTabs();
      $scope.generateGameobjectScript();
      $scope.gameobjectTabs.script = true;
    };

  });

}());
<|endoftext|>


<commit_before>"use strict";

//-----------------------------------------------------------------------------

const CryptoJS = require ('crypto-js')
    , qs       = require ('qs') // querystring

//-----------------------------------------------------------------------------

const { RequestTimeout } = require ('./errors')

//-----------------------------------------------------------------------------
// utility helpers

const setTimeout_original = setTimeout

// setTimeout can fire earlier than specified, so we need to ensure it does not happen...

const setTimeout_safe = (done, ms, setTimeout = setTimeout_original /* overrideable for mocking purposes */, targetTime = Date.now () + ms) => {

    let clearInnerTimeout = () => {}
    let active = true

    let id = setTimeout (() => {
        active = true
        const rest = targetTime - Date.now ()
        if (rest > 0) {
            clearInnerTimeout = setTimeout_safe (done, rest, setTimeout, targetTime) // try sleep more
        } else {
            done ()
        }
    }, ms)

    return function clear () {
        if (active) {
            active = false // dunno if IDs are unique on various platforms, so it's better to rely on this flag to exclude the possible cancellation of the wrong timer (if called after completion)
            clearTimeout (id)
        }
        clearInnerTimeout ()
    }
}

const sleep = ms => new Promise (resolve => setTimeout_safe (resolve, ms))

const decimal = float => parseFloat (float).toString ()

const timeout = async (ms, promise) => {

    let clear = () => {}
    const timeout = new Promise (resolve => (clear = setTimeout_safe (resolve, ms)))

    try {
        return await Promise.race ([promise, timeout.then (() => { throw new RequestTimeout ('request timed out') })])
    } finally {
        clear () // fixes https://github.com/ccxt/ccxt/issues/749
    }
}

const capitalize = string => string.length ? (string.charAt (0).toUpperCase () + string.slice (1)) : string

const keysort = object => {
    const result = {}
    Object.keys (object).sort ().forEach (key => result[key] = object[key])
    return result
}

const extend = (...args) => Object.assign ({}, ...args)

const deepExtend = function (...args) {

    // if (args.length < 1)
    //     return args
    // else if (args.length < 2)
    //     return args[0]

    let result = undefined

    for (const arg of args) {

        if (arg && (typeof arg == 'object') && (arg.constructor === Object || !('constructor' in arg))) {

            if (typeof result != 'object') {
                result = {}
            }

            for (const key in arg) {
                result[key] = deepExtend (result[key], arg[key])
            }

        } else {

            result = arg
        }
    }

    return result
}

const omit = (object, ...args) => {
    const result = extend (object)
    for (const x of args) {
        if (typeof x === 'string') {
            delete result[x]
        } else if (Array.isArray (x)) {
            for (const k of x)
                delete result[k]
        }
    }
    return result
}

const groupBy = (array, key) => {
    const result = {}
    Object
        .values (array)
        .filter (entry => entry[key] != 'undefined')
        .forEach (entry => {
            if (typeof result[entry[key]] == 'undefined')
                result[entry[key]] = []
            result[entry[key]].push (entry)
        })
    return result
}

const filterBy = (array, key, value = undefined) => {
    if (value) {
        let grouped = groupBy (array, key)
        if (value in grouped)
            return grouped[value]
        return []
    }
    return array
}

const indexBy = (array, key) => {
    const result = {}
    Object
        .values (array)
        .filter (entry => entry[key] != 'undefined')
        .forEach (entry => {
            result[entry[key]] = entry
        })
    return result
}

const sortBy = (array, key, descending = false) => {
    descending = descending ? -1 : 1
    return array.sort ((a, b) => ((a[key] < b[key]) ? -descending : ((a[key] > b[key]) ? descending : 0)))
}

const flatten = (array, result = []) => {
    for (let i = 0, length = array.length; i < length; i++) {
        const value = array[i]
        if (Array.isArray (value)) {
            flatten (value, result)
        } else {
            result.push (value)
        }
    }
    return result
}

const unique = array => array.filter ((value, index, self) => (self.indexOf (value) == index))

const pluck = (array, key) => array
                                .filter (element => (typeof element[key] != 'undefined'))
                                .map (element => element[key])

const urlencode = object => qs.stringify (object)
const rawencode = object => qs.stringify (object, { encode: false })

const sum = (...args) => {
    const result = args.filter (arg => typeof arg != 'undefined')
    return (result.length > 0) ?
        result.reduce ((sum, value) => sum + value, 0) : undefined
}

const safeFloat = (object, key, defaultValue = undefined) => {
    if (key in object) {
        if (typeof object[key] == 'number')
            return object[key]
        else if ((typeof object[key] == 'string') && object[key])
            return parseFloat (object[key])
    }
    return defaultValue
}

const safeString = (object, key, defaultValue = undefined) => {
    return (object && (key in object) && object[key]) ? object[key].toString () : defaultValue
}

const safeInteger = (object, key, defaultValue = undefined) => {
    return ((key in object) && object[key]) ? parseInt (object[key]) : defaultValue
}

const safeValue = (object, key, defaultValue = undefined) => {
    return ((key in object) && object[key]) ? object[key] : defaultValue
}

const uuid = a => a ?
    (a ^ Math.random () * 16 >> a / 4).toString (16) :
    ([1e7]+-1e3+-4e3+-8e3+-1e11).replace (/[018]/g, uuid)

// See https://stackoverflow.com/questions/1685680/how-to-avoid-scientific-notation-for-large-numbers-in-javascript for discussion

function toFixed (x) { // avoid scientific notation for too large and too small numbers

    if (Math.abs (x) < 1.0) {
        const e = parseInt (x.toString ().split ('e-')[1])
        if (e) {
            x *= Math.pow (10, e-1)
            x = '0.' + (new Array (e)).join ('0') + x.toString ().substring (2)
        }
    } else {
        let e = parseInt (x.toString ().split ('+')[1])
        if (e > 20) {
            e -= 20
            x /= Math.pow (10, e)
            x += (new Array (e+1)).join ('0')
        }
    }
    return x
}

// See https://stackoverflow.com/questions/4912788/truncate-not-round-off-decimal-numbers-in-javascript for discussion

// > So, after all it turned out, rounding bugs will always haunt you, no matter how hard you try to compensate them.
// > Hence the problem should be attacked by representing numbers exactly in decimal notation.

const truncate_regExpCache = []
    , truncate_to_string = (num, precision = 0) => {
        num = toFixed (num)
        if (precision > 0) {
            const re = truncate_regExpCache[precision] || (truncate_regExpCache[precision] = new RegExp("([-]*\\d+\\.\\d{" + precision + "})(\\d)"))
            const [,result] = num.toString ().match (re) || [null, num]
            return result.toString ()
        }
        return parseInt (num).toString ()
    }
    , truncate = (num, precision = 0) => parseFloat (truncate_to_string (num, precision))

const precisionFromString = (string) => {
    const split = string.replace (/0+$/g, '').split ('.')
    return (split.length > 1) ? (split[1].length) : 0
}

const ordered = x => x // a stub to keep assoc keys in order, in JS it does nothing, it's mostly for Python

const aggregate = function (bidasks) {

    let result = {}

    bidasks.forEach (([ price, volume ]) => {
        if (volume > 0)
            result[price] = (result[price] || 0) + volume
    })

    return Object.keys (result).map (price => [
        parseFloat (price),
        parseFloat (result[price]),
    ])
}

//-----------------------------------------------------------------------------
// string ←→ binary ←→ base64 conversion routines

const stringToBinary = str => {
    const arr = new Uint8Array (str.length)
    for (let i = 0; i < str.length; i++) { arr[i] = str.charCodeAt(i); }
    return CryptoJS.lib.WordArray.create (arr)
}

const stringToBase64 = string => CryptoJS.enc.Latin1.parse (string).toString (CryptoJS.enc.Base64)
    , utf16ToBase64  = string => CryptoJS.enc.Utf16 .parse (string).toString (CryptoJS.enc.Base64)
    , base64ToBinary = string => CryptoJS.enc.Base64.parse (string)
    , base64ToString = string => CryptoJS.enc.Base64.parse (string).toString (CryptoJS.enc.Utf8)
    , binaryToString = string => string

const binaryConcat = (...args) => args.reduce ((a, b) => a.concat (b))

// url-safe-base64 without equals signs, with + replaced by - and slashes replaced by underscores
const urlencodeBase64 = base64string => base64string.replace (/[=]+$/, '')
                                                    .replace (/\+/g, '-')
                                                    .replace (/\//g, '_')

//-----------------------------------------------------------------------------
// cryptography

const hash = (request, hash = 'md5', digest = 'hex') => {
    const result = CryptoJS[hash.toUpperCase ()] (request)
    return (digest == 'binary') ? result : result.toString (CryptoJS.enc[capitalize (digest)])
}

const hmac = (request, secret, hash = 'sha256', digest = 'hex') => {
    const encoding = (digest == 'binary') ? 'Latin1' : capitalize (digest)
    return CryptoJS['Hmac' + hash.toUpperCase ()] (request, secret).toString (CryptoJS.enc[capitalize (encoding)])
}

//-----------------------------------------------------------------------------
// a JSON Web Token authentication method

const jwt = (request, secret, alg = 'HS256', hash = 'sha256') => {
    const encodedHeader = urlencodeBase64 (stringToBase64 (JSON.stringify ({ 'alg': alg, 'typ': 'JWT' })))
        , encodedData = urlencodeBase64 (stringToBase64 (JSON.stringify (request)))
        , token = [ encodedHeader, encodedData ].join ('.')
        , signature = urlencodeBase64 (utf16ToBase64 (hmac (token, secret, hash, 'utf16')))
    return [ token, signature ].join ('.')
}

//-----------------------------------------------------------------------------

module.exports = {

    setTimeout_safe,

    // common utility functions

    sleep,
    timeout,
    capitalize,
    keysort,
    extend,
    deepExtend,
    omit,
    groupBy,
    indexBy,
    sortBy,
    filterBy,
    flatten,
    unique,
    pluck,
    urlencode,
    rawencode,
    sum,
    decimal,
    safeFloat,
    safeString,
    safeInteger,
    safeValue,
    ordered,
    aggregate,
    truncate,
    truncate_to_string,
    uuid,
    precisionFromString,

    // underscore aliases

    index_by: indexBy,
    sort_by: sortBy,
    group_by: groupBy,
    filter_by: filterBy,
    safe_float: safeFloat,
    safe_string: safeString,
    safe_integer: safeInteger,
    safe_value: safeValue,

    // crypto functions

    binaryConcat,
    stringToBinary,
    binaryToString,
    stringToBase64,
    utf16ToBase64,
    base64ToBinary,
    base64ToString,
    urlencodeBase64,
    hash,
    hmac,
    jwt,

    // json
    json:   JSON.stringify,
    unjson: JSON.parse
}
<commit_msg>added missing typeof to indexBy in js<commit_after>"use strict";

//-----------------------------------------------------------------------------

const CryptoJS = require ('crypto-js')
    , qs       = require ('qs') // querystring

//-----------------------------------------------------------------------------

const { RequestTimeout } = require ('./errors')

//-----------------------------------------------------------------------------
// utility helpers

const setTimeout_original = setTimeout

// setTimeout can fire earlier than specified, so we need to ensure it does not happen...

const setTimeout_safe = (done, ms, setTimeout = setTimeout_original /* overrideable for mocking purposes */, targetTime = Date.now () + ms) => {

    let clearInnerTimeout = () => {}
    let active = true

    let id = setTimeout (() => {
        active = true
        const rest = targetTime - Date.now ()
        if (rest > 0) {
            clearInnerTimeout = setTimeout_safe (done, rest, setTimeout, targetTime) // try sleep more
        } else {
            done ()
        }
    }, ms)

    return function clear () {
        if (active) {
            active = false // dunno if IDs are unique on various platforms, so it's better to rely on this flag to exclude the possible cancellation of the wrong timer (if called after completion)
            clearTimeout (id)
        }
        clearInnerTimeout ()
    }
}

const sleep = ms => new Promise (resolve => setTimeout_safe (resolve, ms))

const decimal = float => parseFloat (float).toString ()

const timeout = async (ms, promise) => {

    let clear = () => {}
    const timeout = new Promise (resolve => (clear = setTimeout_safe (resolve, ms)))

    try {
        return await Promise.race ([promise, timeout.then (() => { throw new RequestTimeout ('request timed out') })])
    } finally {
        clear () // fixes https://github.com/ccxt/ccxt/issues/749
    }
}

const capitalize = string => string.length ? (string.charAt (0).toUpperCase () + string.slice (1)) : string

const keysort = object => {
    const result = {}
    Object.keys (object).sort ().forEach (key => result[key] = object[key])
    return result
}

const extend = (...args) => Object.assign ({}, ...args)

const deepExtend = function (...args) {

    // if (args.length < 1)
    //     return args
    // else if (args.length < 2)
    //     return args[0]

    let result = undefined

    for (const arg of args) {

        if (arg && (typeof arg == 'object') && (arg.constructor === Object || !('constructor' in arg))) {

            if (typeof result != 'object') {
                result = {}
            }

            for (const key in arg) {
                result[key] = deepExtend (result[key], arg[key])
            }

        } else {

            result = arg
        }
    }

    return result
}

const omit = (object, ...args) => {
    const result = extend (object)
    for (const x of args) {
        if (typeof x === 'string') {
            delete result[x]
        } else if (Array.isArray (x)) {
            for (const k of x)
                delete result[k]
        }
    }
    return result
}

const groupBy = (array, key) => {
    const result = {}
    Object
        .values (array)
        .filter (entry => entry[key] != 'undefined')
        .forEach (entry => {
            if (typeof result[entry[key]] == 'undefined')
                result[entry[key]] = []
            result[entry[key]].push (entry)
        })
    return result
}

const filterBy = (array, key, value = undefined) => {
    if (value) {
        let grouped = groupBy (array, key)
        if (value in grouped)
            return grouped[value]
        return []
    }
    return array
}

const indexBy = (array, key) => {
    const result = {}
    Object
        .values (array)
        .filter (entry => typeof entry[key] != 'undefined')
        .forEach (entry => {
            result[entry[key]] = entry
        })
    return result
}

const sortBy = (array, key, descending = false) => {
    descending = descending ? -1 : 1
    return array.sort ((a, b) => ((a[key] < b[key]) ? -descending : ((a[key] > b[key]) ? descending : 0)))
}

const flatten = (array, result = []) => {
    for (let i = 0, length = array.length; i < length; i++) {
        const value = array[i]
        if (Array.isArray (value)) {
            flatten (value, result)
        } else {
            result.push (value)
        }
    }
    return result
}

const unique = array => array.filter ((value, index, self) => (self.indexOf (value) == index))

const pluck = (array, key) => array
                                .filter (element => (typeof element[key] != 'undefined'))
                                .map (element => element[key])

const urlencode = object => qs.stringify (object)
const rawencode = object => qs.stringify (object, { encode: false })

const sum = (...args) => {
    const result = args.filter (arg => typeof arg != 'undefined')
    return (result.length > 0) ?
        result.reduce ((sum, value) => sum + value, 0) : undefined
}

const safeFloat = (object, key, defaultValue = undefined) => {
    if (key in object) {
        if (typeof object[key] == 'number')
            return object[key]
        else if ((typeof object[key] == 'string') && object[key])
            return parseFloat (object[key])
    }
    return defaultValue
}

const safeString = (object, key, defaultValue = undefined) => {
    return (object && (key in object) && object[key]) ? object[key].toString () : defaultValue
}

const safeInteger = (object, key, defaultValue = undefined) => {
    return ((key in object) && object[key]) ? parseInt (object[key]) : defaultValue
}

const safeValue = (object, key, defaultValue = undefined) => {
    return ((key in object) && object[key]) ? object[key] : defaultValue
}

const uuid = a => a ?
    (a ^ Math.random () * 16 >> a / 4).toString (16) :
    ([1e7]+-1e3+-4e3+-8e3+-1e11).replace (/[018]/g, uuid)

// See https://stackoverflow.com/questions/1685680/how-to-avoid-scientific-notation-for-large-numbers-in-javascript for discussion

function toFixed (x) { // avoid scientific notation for too large and too small numbers

    if (Math.abs (x) < 1.0) {
        const e = parseInt (x.toString ().split ('e-')[1])
        if (e) {
            x *= Math.pow (10, e-1)
            x = '0.' + (new Array (e)).join ('0') + x.toString ().substring (2)
        }
    } else {
        let e = parseInt (x.toString ().split ('+')[1])
        if (e > 20) {
            e -= 20
            x /= Math.pow (10, e)
            x += (new Array (e+1)).join ('0')
        }
    }
    return x
}

// See https://stackoverflow.com/questions/4912788/truncate-not-round-off-decimal-numbers-in-javascript for discussion

// > So, after all it turned out, rounding bugs will always haunt you, no matter how hard you try to compensate them.
// > Hence the problem should be attacked by representing numbers exactly in decimal notation.

const truncate_regExpCache = []
    , truncate_to_string = (num, precision = 0) => {
        num = toFixed (num)
        if (precision > 0) {
            const re = truncate_regExpCache[precision] || (truncate_regExpCache[precision] = new RegExp("([-]*\\d+\\.\\d{" + precision + "})(\\d)"))
            const [,result] = num.toString ().match (re) || [null, num]
            return result.toString ()
        }
        return parseInt (num).toString ()
    }
    , truncate = (num, precision = 0) => parseFloat (truncate_to_string (num, precision))

const precisionFromString = (string) => {
    const split = string.replace (/0+$/g, '').split ('.')
    return (split.length > 1) ? (split[1].length) : 0
}

const ordered = x => x // a stub to keep assoc keys in order, in JS it does nothing, it's mostly for Python

const aggregate = function (bidasks) {

    let result = {}

    bidasks.forEach (([ price, volume ]) => {
        if (volume > 0)
            result[price] = (result[price] || 0) + volume
    })

    return Object.keys (result).map (price => [
        parseFloat (price),
        parseFloat (result[price]),
    ])
}

//-----------------------------------------------------------------------------
// string ←→ binary ←→ base64 conversion routines

const stringToBinary = str => {
    const arr = new Uint8Array (str.length)
    for (let i = 0; i < str.length; i++) { arr[i] = str.charCodeAt(i); }
    return CryptoJS.lib.WordArray.create (arr)
}

const stringToBase64 = string => CryptoJS.enc.Latin1.parse (string).toString (CryptoJS.enc.Base64)
    , utf16ToBase64  = string => CryptoJS.enc.Utf16 .parse (string).toString (CryptoJS.enc.Base64)
    , base64ToBinary = string => CryptoJS.enc.Base64.parse (string)
    , base64ToString = string => CryptoJS.enc.Base64.parse (string).toString (CryptoJS.enc.Utf8)
    , binaryToString = string => string

const binaryConcat = (...args) => args.reduce ((a, b) => a.concat (b))

// url-safe-base64 without equals signs, with + replaced by - and slashes replaced by underscores
const urlencodeBase64 = base64string => base64string.replace (/[=]+$/, '')
                                                    .replace (/\+/g, '-')
                                                    .replace (/\//g, '_')

//-----------------------------------------------------------------------------
// cryptography

const hash = (request, hash = 'md5', digest = 'hex') => {
    const result = CryptoJS[hash.toUpperCase ()] (request)
    return (digest == 'binary') ? result : result.toString (CryptoJS.enc[capitalize (digest)])
}

const hmac = (request, secret, hash = 'sha256', digest = 'hex') => {
    const encoding = (digest == 'binary') ? 'Latin1' : capitalize (digest)
    return CryptoJS['Hmac' + hash.toUpperCase ()] (request, secret).toString (CryptoJS.enc[capitalize (encoding)])
}

//-----------------------------------------------------------------------------
// a JSON Web Token authentication method

const jwt = (request, secret, alg = 'HS256', hash = 'sha256') => {
    const encodedHeader = urlencodeBase64 (stringToBase64 (JSON.stringify ({ 'alg': alg, 'typ': 'JWT' })))
        , encodedData = urlencodeBase64 (stringToBase64 (JSON.stringify (request)))
        , token = [ encodedHeader, encodedData ].join ('.')
        , signature = urlencodeBase64 (utf16ToBase64 (hmac (token, secret, hash, 'utf16')))
    return [ token, signature ].join ('.')
}

//-----------------------------------------------------------------------------

module.exports = {

    setTimeout_safe,

    // common utility functions

    sleep,
    timeout,
    capitalize,
    keysort,
    extend,
    deepExtend,
    omit,
    groupBy,
    indexBy,
    sortBy,
    filterBy,
    flatten,
    unique,
    pluck,
    urlencode,
    rawencode,
    sum,
    decimal,
    safeFloat,
    safeString,
    safeInteger,
    safeValue,
    ordered,
    aggregate,
    truncate,
    truncate_to_string,
    uuid,
    precisionFromString,

    // underscore aliases

    index_by: indexBy,
    sort_by: sortBy,
    group_by: groupBy,
    filter_by: filterBy,
    safe_float: safeFloat,
    safe_string: safeString,
    safe_integer: safeInteger,
    safe_value: safeValue,

    // crypto functions

    binaryConcat,
    stringToBinary,
    binaryToString,
    stringToBase64,
    utf16ToBase64,
    base64ToBinary,
    base64ToString,
    urlencodeBase64,
    hash,
    hmac,
    jwt,

    // json
    json:   JSON.stringify,
    unjson: JSON.parse
}
<|endoftext|>


<commit_before>/**
*
* Define models & collection for Vertebrate.js
*
*/

var BHM = {};

BHM.helpersurl = '/dev/bootstraphelpmanager/bhm.helpers.php';
BHM.pagesurl = '/dev/bootstraphelpmanager/bhm.pages.php';

BHM.helper = Vertebrate.Model.Extend({
    attributes: {
        id: -1,
        field_selecter: '',
        title: '',
        large: false,
        html: ''
    },
    url: BHM.helpersurl
})

BHM.page = Vertebrate.Model.Extend({
    attributes: {
        "id": -1,
        "url": ''
    },
    url: BHM.pagesurl
});

BHM.helpers = Vertebrate.Collection.Extend({
    model: BHM.helper,
    url: BHM.helpersurl
});

BHM.pages = Vertebrate.Collection.Extend({
    model: BHM.page,
    url: BHM.pagesurl
});

BHM.collPages = new BHM.pages();
BHM.collHelps = new BHM.helpers();
<commit_msg>fix folder names<commit_after>/**
*
* Define models & collection for Vertebrate.js
*
*/

var BHM = {};

BHM.helpersurl = '/dev/Bootstrap-Help-Manager/bhm.helpers.php';
BHM.pagesurl = '/dev/Bootstrap-Help-Manager/bhm.pages.php';

BHM.helper = Vertebrate.Model.Extend({
    attributes: {
        id: -1,
        field_selecter: '',
        title: '',
        large: false,
        html: ''
    },
    url: BHM.helpersurl
})

BHM.page = Vertebrate.Model.Extend({
    attributes: {
        "id": -1,
        "url": ''
    },
    url: BHM.pagesurl
});

BHM.helpers = Vertebrate.Collection.Extend({
    model: BHM.helper,
    url: BHM.helpersurl
});

BHM.pages = Vertebrate.Collection.Extend({
    model: BHM.page,
    url: BHM.pagesurl
});

BHM.collPages = new BHM.pages();
BHM.collHelps = new BHM.helpers();
<|endoftext|>


<commit_before>/* ========================================================
 * bootstrap-tabs.js v1.3.0
 * http://twitter.github.com/bootstrap/javascript.html#tabs
 * ========================================================
 * Copyright 2011 Twitter, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * ======================================================== */

!function( $ ){

  function activate ( element, container ) {
    container.find('.active').removeClass('active')
    element.addClass('active')
  }

  function tab( e ) {
    var $this = $(this)
      , href = $this.attr('href')
      , $ul = $(e.liveFired)
      , $controlled

    if (/^#\w+/.test(href)) {
      e.preventDefault()

      if ($this.hasClass('active')) {
        return
      }

      $href = $(href)

      activate($this.parent('li'), $ul)
      activate($href, $href.parent())
    }
  }

 /* TABS/PILLS PLUGIN DEFINITION
  * ============================ */

  $.fn.tabs = $.fn.pills = function ( selector ) {
    return this.each(function () {
      $(this).delegate(selector || '.tabs li > a, .pills > li > a', 'click', tab)
    })
  }

  $(document).ready(function () {
    $('body').tabs('ul[data-tabs] li > a, ul[data-pills] > li > a')
  })

}( window.jQuery || window.ender )<commit_msg>fixes #258, clicking a tab remove .active class from all elements on the page<commit_after>/* ========================================================
 * bootstrap-tabs.js v1.3.0
 * http://twitter.github.com/bootstrap/javascript.html#tabs
 * ========================================================
 * Copyright 2011 Twitter, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * ======================================================== */

!function( $ ){

  function activate ( element, container ) {
    container.find('.active').removeClass('active')
    element.addClass('active')
  }

  function tab( e ) {
    var $this = $(this)
      , href = $this.attr('href')
      , $ul = $this.closest('ul')
      , $controlled

    if (/^#\w+/.test(href)) {
      e.preventDefault()

      if ($this.hasClass('active')) {
        return
      }

      $href = $(href)

      activate($this.parent('li'), $ul)
      activate($href, $href.parent())
    }
  }

 /* TABS/PILLS PLUGIN DEFINITION
  * ============================ */

  $.fn.tabs = $.fn.pills = function ( selector ) {
    return this.each(function () {
      $(this).delegate(selector || '.tabs li > a, .pills > li > a', 'click', tab)
    })
  }

  $(document).ready(function () {
    $('body').tabs('ul[data-tabs] li > a, ul[data-pills] > li > a')
  })

}( window.jQuery || window.ender )<|endoftext|>