<commit_before>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

var CipherFox = (function() {
  'use strict';

  const Cc = Components.classes, Ci = Components.interfaces;

  var certDb  = Cc['@mozilla.org/security/x509certdb;1'].getService(Ci.nsIX509CertDB);
  var certDlg = Cc['@mozilla.org/nsCertificateDialogs;1'].getService(Ci.nsICertificateDialogs);
  var pipnss  = Cc['@mozilla.org/intl/stringbundle;1'].getService(Ci.nsIStringBundleService)
                .createBundle('chrome://pipnss/locale/pipnss.properties');
  var clipboardHelper = Cc["@mozilla.org/widget/clipboardhelper;1"].getService(Ci.nsIClipboardHelper);

  var prefService = Cc['@mozilla.org/preferences-service;1'].getService(Ci.nsIPrefBranch2);
  var prefs = {};
  var rc4Enabled;

  var unknown = '?'; // label to use for missing fields

  // ciphers from ciphersuites
  var ciphers = ['_AES_', '_RC4_', '_3DES_', '_DES_', '_CAMELLIA_', '_RC2_',
    '_DES40_', '_FORTEZZA_', '_IDEA_', '_SEED_', '_GOST', '_NULL_'];
  var ciphersRe = new RegExp(ciphers.join('|'));

  // default SSL3 RC4 preferences
  var rc4 = ['ecdh_ecdsa_rc4_128_sha', 'ecdh_rsa_rc4_128_sha',
    'ecdhe_ecdsa_rc4_128_sha', 'ecdhe_rsa_rc4_128_sha', 'rsa_1024_rc4_56_sha',
    'rsa_rc4_128_md5', 'rsa_rc4_128_sha', 'rsa_rc4_40_md5'];

  // XUL DOM elements
  var cfToggle, cfPanel, cfButton, cfCerts, cfBCerts, cfPSep;

  var hideIdentityPopup = function() {
    try {
      gIdentityHandler.hideIdentityPopup();
    } catch(e) {}
  };

  // show dialog for cert in database
  var viewCertByDBKey = function(e) {
    hideIdentityPopup();

    var dbkey = e.target.getAttribute('dbkey');
    var cert = certDb.findCertByDBKey(dbkey, null);
    certDlg.viewCert(window, cert);
  };

  // update RC4 preferences
  var setRC4 = function() {
    for (var i = 0, len = rc4.length; i < len; i++) {
      var pref = 'security.ssl3.' + rc4[i];

      if (rc4Enabled) {
        try { prefService.clearUserPref(pref); }
        catch(e) {}
      } else {
        var value;
        try { value = prefService.getBoolPref(pref); }
        catch(e) {}

        if (value !== undefined) {
          prefService.setBoolPref(pref, false);
        }
      }
    }
  };

  var toggleRC4 = function() {
    rc4Enabled = !rc4Enabled;
    cfToggle.setAttribute('checked', rc4Enabled);
    setRC4();
  };

  // get existing preferences
  var loadPrefs = function() {
    prefs.base_format  = prefService.getCharPref('extensions.cipherfox.base_format');
    prefs.cert_format  = prefService.getCharPref('extensions.cipherfox.cert_format');
    prefs.disable_rc4  = prefService.getBoolPref('extensions.cipherfox.disable_rc4');
    prefs.show_builtin = prefService.getBoolPref('extensions.cipherfox.show_builtin');
    prefs.show_partial = prefService.getBoolPref('extensions.cipherfox.show_partial');
    prefs.show_panel   = prefService.getBoolPref('extensions.cipherfox.show_panel');
    prefs.show_button  = prefService.getBoolPref('extensions.cipherfox.show_button');

    // set RC4 status and menuitem
    rc4Enabled = !prefs.disable_rc4;
    cfToggle.setAttribute('hidden', rc4Enabled);
    cfToggle.setAttribute('checked', rc4Enabled);
  };

  // get all certs and update
  var populateCertChain = function(status) {
    cfCerts.hidePopup();
    if (cfBCerts instanceof XULElement) {
      cfBCerts.hidePopup();
    }

    // remove old certs
    while(cfCerts.hasChildNodes()) {
      cfCerts.removeChild(cfCerts.firstChild);
    }

    if (cfBCerts instanceof XULElement) {
      while(cfBCerts.hasChildNodes() && cfBCerts.firstChild !== cfPSep) {
        cfBCerts.removeChild(cfBCerts.firstChild);
      }
    }

    var serverCert = status.serverCert;
    if (serverCert instanceof Ci.nsIX509Cert) {
      var certChain = serverCert.getChain().enumerate();

      while (certChain.hasMoreElements()) {
        var next = certChain.getNext();
        var cert = next.QueryInterface(Ci.nsIX509Cert || Ci.nsIX509Cert2);

        var certItem = document.createElement('menuitem');

        if (cert.tokenName === 'Builtin Object Token' &&
            cert.certType === Ci.nsIX509Cert.CA_CERT) {
          if (!prefs.show_builtin) { continue; }
          certItem.setAttribute('builtin', true);
        }

        var label = formatLabel(cert);
        var dbkey = cert.dbKey.replace(/[\n\r\t]/g, '');

        // selecting a cert brings up details
        certItem.setAttribute('label', label);
        certItem.setAttribute('dbkey', dbkey);

        // add attributes for styling
        certItem.setAttribute('cert', true);
        if (!cfCerts.hasChildNodes()) {
          certItem.setAttribute('first', true);
        }

        certItem.addEventListener('command', viewCertByDBKey, false);

        var certItemB = certItem.cloneNode(false);
        certItemB.addEventListener('command', viewCertByDBKey, false);

        cfCerts.insertBefore(certItem, cfCerts.firstChild);
        if (cfBCerts instanceof XULElement) {
          cfBCerts.insertBefore(certItemB, cfPSep);
        }
      }
    }
  };

  var protocolString = function(v) {
    if (typeof v !== 'number' || isNaN(v)) { return; }

    if (v === Ci.nsISSLStatus.SSL_VERSION_3) { return 'SSL 3.0'; }
    if (v === Ci.nsISSLStatus.TLS_VERSION_1) { return 'TLS 1.0'; }
    if (v === Ci.nsISSLStatus.TLS_VERSION_1_1) { return 'TLS 1.1'; }
    if (v === Ci.nsISSLStatus.TLS_VERSION_1_2) { return 'TLS 1.2'; }
  };

  var formatLabel = function(obj) {
    var cert, label;

    if (obj instanceof Ci.nsISSLStatus) {
      cert = obj.serverCert;
      label = prefs.base_format;

      var cipherName = obj.cipherName
      var suiteMatch = ciphersRe.exec(cipherName);
      var protocol = protocolString(obj.protocolVersion); // Fx 36+

      var cipherSuite = obj.cipherSuite;

      // in Fx 25+, cipherName contains a full cipher suite
      if (suiteMatch) {
        cipherSuite = cipherName; // full cipher suite
        cipherName = suiteMatch[0].replace(/_/g, ''); // short cipher name
      } else {
        cipherName = cipherName.split('-')[0];
      }

      label = label
        .replace(/\$CIPHERALG/g, cipherName || unknown)
        .replace(/\$CIPHERSIZE/g, obj.secretKeyLength || unknown)
        .replace(/\$CIPHERSUITE/g, cipherSuite || unknown)
        .replace(/\$PROTOCOL/g, protocol || unknown);

    } else if (obj instanceof Ci.nsIX509Cert) {
      cert = obj;
      label = prefs.cert_format;
    } else { return null; }

    var certDmp = Cc['@mozilla.org/security/nsASN1Tree;1'].createInstance(Ci.nsIASN1Tree);
    certDmp.loadASN1Structure(cert.ASN1Structure);

    var certOrg = cert.organization ? cert.organization : cert.commonName;
    var certCn  = cert.commonName   ? cert.commonName   : cert.organization;

    var certAlg;
    switch (certDmp.getDisplayData(11)) {
      case pipnss.GetStringFromName('CertDumpRSAEncr'):
        certAlg = 'RSA';
        break;
    }

    if (!certAlg) {
      switch (certDmp.getDisplayData(12)) {
        case pipnss.GetStringFromName('CertDumpECPublicKey'):
          certAlg = 'ECC';
          break;
        case pipnss.GetStringFromName('CertDumpAnsiX9DsaSignature'):
        case pipnss.GetStringFromName('CertDumpAnsiX9DsaSignatureWithSha1'):
          certAlg = 'DSA';
          break;
      }
    }

    var certSize, key, template;
    try {
      switch(certAlg) {
        case 'RSA':
          key = certDmp.getDisplayData(12).split('\n')[0];
          template = pipnss.GetStringFromName('CertDumpRSATemplate');
          break;

        case 'ECC':
          key = certDmp.getDisplayData(14).split('\n')[0];
          template = pipnss.GetStringFromName('CertDumpECTemplate');
          break;

        case 'DSA':
          key = certDmp.getDisplayData(14);
          key = key.replace(key.split('\n')[0], '').replace(/\n|(\s$)/g, '').split(/\s/);
          if (key[0] === '02' && key[1] === '81') { key.splice(0,3); }
          if (key[0] === '00') { key.splice(0,1); }
          certSize = (8 * key.length);
          break;
      }

      if (!certSize && template) {
        var discards = template.split('\n')[0].split('%S');
        discards.forEach(function(str) {
          key = key.replace(str, '');
        });

        certSize = key;
      }
    } catch(e) {}

    // look for hash type
    var certHash;
    var displayData = certDmp.getDisplayData(certDmp.rowCount-2);
    switch (displayData) {
      case pipnss.GetStringFromName('CertDumpMD2WithRSA'):    certHash = 'MD2';    break;
      case pipnss.GetStringFromName('CertDumpMD5WithRSA'):    certHash = 'MD5';    break;
      case pipnss.GetStringFromName('CertDumpSHA1WithRSA'):   certHash = 'SHA1';   break;
      case pipnss.GetStringFromName('CertDumpSHA256WithRSA'): certHash = 'SHA256'; break;
      case pipnss.GetStringFromName('CertDumpSHA384WithRSA'): certHash = 'SHA384'; break;
      case pipnss.GetStringFromName('CertDumpSHA512WithRSA'): certHash = 'SHA512';
    }

    // assume ECDSA OID
    if (!certHash) {
      // displayData: 'Object Identifier (1 2 840 10045 4 3 2)'
      var oidMatches = displayData.match(/\((.*)\)/);
      if (oidMatches && oidMatches.length > 1) {
        var oid = oidMatches[1];

        switch (oid) {
          case '1 2 840 10045 4 1':   certHash = 'SHA1';   break;
          case '1 2 840 10045 4 3 1': certHash = 'SHA224'; break;
          case '1 2 840 10045 4 3 2': certHash = 'SHA256'; break;
          case '1 2 840 10045 4 3 3': certHash = 'SHA384'; break;
          case '1 2 840 10045 4 3 4': certHash = 'SHA512'; break;
        }
      }
    }

    var certFrom = cert.validity.notBeforeLocalDay;
    var certExp = cert.validity.notAfterLocalDay;
    var certIss = cert.issuerOrganization;

    // replace variable names in format string with values
    label = label
      .replace(/\$CERTORG/g,    certOrg  || unknown)
      .replace(/\$CERTCN/g,     certCn   || unknown)
      .replace(/\$CERTALG/g,    certAlg  || unknown)
      .replace(/\$CERTSIZE/g,   certSize || unknown)
      .replace(/\$CERTHASH/g,   certHash || unknown)
      .replace(/\$CERTISSUED/g, certFrom || unknown)
      .replace(/\$CERTEXP/g,    certExp  || unknown)
      .replace(/\$CERTISSUER/g, certIss  || unknown);

    return label;
  };

  var updateCipher = function() {
    hideIdentityPopup();

    var currentBrowser = gBrowser.selectedBrowser;
    var panelLabel = null;
    var hidden = true;

    var ui = currentBrowser.securityUI;
    if (ui instanceof Ci.nsISecureBrowserUI) {
      var status = ui.QueryInterface(Ci.nsISSLStatusProvider).SSLStatus;
      var isPartial = (ui.state & Ci.nsIWebProgressListener.STATE_IS_BROKEN);

      if (status instanceof Ci.nsISSLStatus) {
        panelLabel = formatLabel(status);
        hidden = !(panelLabel && (!isPartial || prefs.show_partial));
        populateCertChain(status);
      }
    }

    cfPanel.label = panelLabel;
    cfPanel.hidden  = hidden || !prefs.show_panel;

    if (cfButton instanceof XULElement) {
      cfButton.label = panelLabel;
      cfButton.hidden = hidden || !prefs.show_button;
    }
  };

  // unused functions must be defined
  var updateListener = {
    onStateChange:    function(){},
    onProgressChange: function(){},
    onLocationChange: function(){},
    onStatusChange:   function(){},
    onSecurityChange: function(webProgress, request, state) { updateCipher(); }
  };

  // exposed methods
  return {
    onLoad: function() {
      cfToggle = document.getElementById('cipherfox-toggle-rc4');
      cfPanel  = document.getElementById('cipherfox-panel');
      cfButton = document.getElementById('cipherfox-button');
      cfCerts  = document.getElementById('cipherfox-certs');
      cfBCerts = document.getElementById('cipherfox-button-certs');
      cfPSep   = document.getElementById('cipherfox-prefs-seperator');

      // don't autohide the identity-popup
      var moreInfo = document.getElementById('identity-popup-more-info-button');
      if (moreInfo instanceof XULElement) {
        moreInfo.removeAttribute('onblur');
        moreInfo.addEventListener('command', hideIdentityPopup, false);
      }

      if (cfCerts instanceof XULElement) {
        cfCerts.addEventListener('popupshowing', function() {
          cfPanel.setAttribute('popupopen', true);
        }, false);

        cfCerts.addEventListener('popuphiding', function() {
          cfPanel.removeAttribute('popupopen');
        }, false);
      }

      // quick RC4 toggle
      cfToggle.addEventListener('command', toggleRC4, false);

      // keep the identity-box 'open'
      if (cfBCerts instanceof XULElement) {
        cfBCerts.addEventListener('popuphidden', function(e) {
          e.stopPropagation();
        }, false);
      }

      prefService.addObserver('extensions.cipherfox.', this, false);
      loadPrefs();

      // only modify RC4 prefs if the user has disabled RC4
      if (prefs.disable_rc4) { setRC4(); }

      gBrowser.addProgressListener(updateListener);
    },

    onUnload: function() {
      prefService.removeObserver('extensions.cipherfox.', this);
      gBrowser.removeProgressListener(updateListener);
    },

    // update state when prefs change
    observe: function(subject, topic, data) {
      if (topic === 'nsPref:changed') {
        loadPrefs();
        updateCipher();
        if (data === 'extensions.cipherfox.disable_rc4') { setRC4(); }
      }
    },

    copyCipherSuite: function() {
      var securityUI = gBrowser.selectedBrowser.securityUI;
      if (securityUI instanceof Ci.nsISecureBrowserUI) {
        var status = securityUI.QueryInterface(Ci.nsISSLStatusProvider).SSLStatus;
        if (status instanceof Ci.nsISSLStatus) {
          clipboardHelper.copyString(status.cipherSuite || status.cipherName);
        }
      }
    },

    // Qualys SSL Labs Server Test
    testDomain: function() {
      gBrowser.addTab('https://www.ssllabs.com/ssldb/analyze.html?d='
                      + gBrowser.contentDocument.domain);
    }
  };
})();
<commit_msg>Fix missing semicolon<commit_after>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

var CipherFox = (function() {
  'use strict';

  const Cc = Components.classes, Ci = Components.interfaces;

  var certDb  = Cc['@mozilla.org/security/x509certdb;1'].getService(Ci.nsIX509CertDB);
  var certDlg = Cc['@mozilla.org/nsCertificateDialogs;1'].getService(Ci.nsICertificateDialogs);
  var pipnss  = Cc['@mozilla.org/intl/stringbundle;1'].getService(Ci.nsIStringBundleService)
                .createBundle('chrome://pipnss/locale/pipnss.properties');
  var clipboardHelper = Cc["@mozilla.org/widget/clipboardhelper;1"].getService(Ci.nsIClipboardHelper);

  var prefService = Cc['@mozilla.org/preferences-service;1'].getService(Ci.nsIPrefBranch2);
  var prefs = {};
  var rc4Enabled;

  var unknown = '?'; // label to use for missing fields

  // ciphers from ciphersuites
  var ciphers = ['_AES_', '_RC4_', '_3DES_', '_DES_', '_CAMELLIA_', '_RC2_',
    '_DES40_', '_FORTEZZA_', '_IDEA_', '_SEED_', '_GOST', '_NULL_'];
  var ciphersRe = new RegExp(ciphers.join('|'));

  // default SSL3 RC4 preferences
  var rc4 = ['ecdh_ecdsa_rc4_128_sha', 'ecdh_rsa_rc4_128_sha',
    'ecdhe_ecdsa_rc4_128_sha', 'ecdhe_rsa_rc4_128_sha', 'rsa_1024_rc4_56_sha',
    'rsa_rc4_128_md5', 'rsa_rc4_128_sha', 'rsa_rc4_40_md5'];

  // XUL DOM elements
  var cfToggle, cfPanel, cfButton, cfCerts, cfBCerts, cfPSep;

  var hideIdentityPopup = function() {
    try {
      gIdentityHandler.hideIdentityPopup();
    } catch(e) {}
  };

  // show dialog for cert in database
  var viewCertByDBKey = function(e) {
    hideIdentityPopup();

    var dbkey = e.target.getAttribute('dbkey');
    var cert = certDb.findCertByDBKey(dbkey, null);
    certDlg.viewCert(window, cert);
  };

  // update RC4 preferences
  var setRC4 = function() {
    for (var i = 0, len = rc4.length; i < len; i++) {
      var pref = 'security.ssl3.' + rc4[i];

      if (rc4Enabled) {
        try { prefService.clearUserPref(pref); }
        catch(e) {}
      } else {
        var value;
        try { value = prefService.getBoolPref(pref); }
        catch(e) {}

        if (value !== undefined) {
          prefService.setBoolPref(pref, false);
        }
      }
    }
  };

  var toggleRC4 = function() {
    rc4Enabled = !rc4Enabled;
    cfToggle.setAttribute('checked', rc4Enabled);
    setRC4();
  };

  // get existing preferences
  var loadPrefs = function() {
    prefs.base_format  = prefService.getCharPref('extensions.cipherfox.base_format');
    prefs.cert_format  = prefService.getCharPref('extensions.cipherfox.cert_format');
    prefs.disable_rc4  = prefService.getBoolPref('extensions.cipherfox.disable_rc4');
    prefs.show_builtin = prefService.getBoolPref('extensions.cipherfox.show_builtin');
    prefs.show_partial = prefService.getBoolPref('extensions.cipherfox.show_partial');
    prefs.show_panel   = prefService.getBoolPref('extensions.cipherfox.show_panel');
    prefs.show_button  = prefService.getBoolPref('extensions.cipherfox.show_button');

    // set RC4 status and menuitem
    rc4Enabled = !prefs.disable_rc4;
    cfToggle.setAttribute('hidden', rc4Enabled);
    cfToggle.setAttribute('checked', rc4Enabled);
  };

  // get all certs and update
  var populateCertChain = function(status) {
    cfCerts.hidePopup();
    if (cfBCerts instanceof XULElement) {
      cfBCerts.hidePopup();
    }

    // remove old certs
    while(cfCerts.hasChildNodes()) {
      cfCerts.removeChild(cfCerts.firstChild);
    }

    if (cfBCerts instanceof XULElement) {
      while(cfBCerts.hasChildNodes() && cfBCerts.firstChild !== cfPSep) {
        cfBCerts.removeChild(cfBCerts.firstChild);
      }
    }

    var serverCert = status.serverCert;
    if (serverCert instanceof Ci.nsIX509Cert) {
      var certChain = serverCert.getChain().enumerate();

      while (certChain.hasMoreElements()) {
        var next = certChain.getNext();
        var cert = next.QueryInterface(Ci.nsIX509Cert || Ci.nsIX509Cert2);

        var certItem = document.createElement('menuitem');

        if (cert.tokenName === 'Builtin Object Token' &&
            cert.certType === Ci.nsIX509Cert.CA_CERT) {
          if (!prefs.show_builtin) { continue; }
          certItem.setAttribute('builtin', true);
        }

        var label = formatLabel(cert);
        var dbkey = cert.dbKey.replace(/[\n\r\t]/g, '');

        // selecting a cert brings up details
        certItem.setAttribute('label', label);
        certItem.setAttribute('dbkey', dbkey);

        // add attributes for styling
        certItem.setAttribute('cert', true);
        if (!cfCerts.hasChildNodes()) {
          certItem.setAttribute('first', true);
        }

        certItem.addEventListener('command', viewCertByDBKey, false);

        var certItemB = certItem.cloneNode(false);
        certItemB.addEventListener('command', viewCertByDBKey, false);

        cfCerts.insertBefore(certItem, cfCerts.firstChild);
        if (cfBCerts instanceof XULElement) {
          cfBCerts.insertBefore(certItemB, cfPSep);
        }
      }
    }
  };

  var protocolString = function(v) {
    if (typeof v !== 'number' || isNaN(v)) { return; }

    if (v === Ci.nsISSLStatus.SSL_VERSION_3) { return 'SSL 3.0'; }
    if (v === Ci.nsISSLStatus.TLS_VERSION_1) { return 'TLS 1.0'; }
    if (v === Ci.nsISSLStatus.TLS_VERSION_1_1) { return 'TLS 1.1'; }
    if (v === Ci.nsISSLStatus.TLS_VERSION_1_2) { return 'TLS 1.2'; }
  };

  var formatLabel = function(obj) {
    var cert, label;

    if (obj instanceof Ci.nsISSLStatus) {
      cert = obj.serverCert;
      label = prefs.base_format;

      var cipherName = obj.cipherName;
      var suiteMatch = ciphersRe.exec(cipherName);
      var protocol = protocolString(obj.protocolVersion); // Fx 36+

      var cipherSuite = obj.cipherSuite;

      // in Fx 25+, cipherName contains a full cipher suite
      if (suiteMatch) {
        cipherSuite = cipherName; // full cipher suite
        cipherName = suiteMatch[0].replace(/_/g, ''); // short cipher name
      } else {
        cipherName = cipherName.split('-')[0];
      }

      label = label
        .replace(/\$CIPHERALG/g, cipherName || unknown)
        .replace(/\$CIPHERSIZE/g, obj.secretKeyLength || unknown)
        .replace(/\$CIPHERSUITE/g, cipherSuite || unknown)
        .replace(/\$PROTOCOL/g, protocol || unknown);

    } else if (obj instanceof Ci.nsIX509Cert) {
      cert = obj;
      label = prefs.cert_format;
    } else { return null; }

    var certDmp = Cc['@mozilla.org/security/nsASN1Tree;1'].createInstance(Ci.nsIASN1Tree);
    certDmp.loadASN1Structure(cert.ASN1Structure);

    var certOrg = cert.organization ? cert.organization : cert.commonName;
    var certCn  = cert.commonName   ? cert.commonName   : cert.organization;

    var certAlg;
    switch (certDmp.getDisplayData(11)) {
      case pipnss.GetStringFromName('CertDumpRSAEncr'):
        certAlg = 'RSA';
        break;
    }

    if (!certAlg) {
      switch (certDmp.getDisplayData(12)) {
        case pipnss.GetStringFromName('CertDumpECPublicKey'):
          certAlg = 'ECC';
          break;
        case pipnss.GetStringFromName('CertDumpAnsiX9DsaSignature'):
        case pipnss.GetStringFromName('CertDumpAnsiX9DsaSignatureWithSha1'):
          certAlg = 'DSA';
          break;
      }
    }

    var certSize, key, template;
    try {
      switch(certAlg) {
        case 'RSA':
          key = certDmp.getDisplayData(12).split('\n')[0];
          template = pipnss.GetStringFromName('CertDumpRSATemplate');
          break;

        case 'ECC':
          key = certDmp.getDisplayData(14).split('\n')[0];
          template = pipnss.GetStringFromName('CertDumpECTemplate');
          break;

        case 'DSA':
          key = certDmp.getDisplayData(14);
          key = key.replace(key.split('\n')[0], '').replace(/\n|(\s$)/g, '').split(/\s/);
          if (key[0] === '02' && key[1] === '81') { key.splice(0,3); }
          if (key[0] === '00') { key.splice(0,1); }
          certSize = (8 * key.length);
          break;
      }

      if (!certSize && template) {
        var discards = template.split('\n')[0].split('%S');
        discards.forEach(function(str) {
          key = key.replace(str, '');
        });

        certSize = key;
      }
    } catch(e) {}

    // look for hash type
    var certHash;
    var displayData = certDmp.getDisplayData(certDmp.rowCount-2);
    switch (displayData) {
      case pipnss.GetStringFromName('CertDumpMD2WithRSA'):    certHash = 'MD2';    break;
      case pipnss.GetStringFromName('CertDumpMD5WithRSA'):    certHash = 'MD5';    break;
      case pipnss.GetStringFromName('CertDumpSHA1WithRSA'):   certHash = 'SHA1';   break;
      case pipnss.GetStringFromName('CertDumpSHA256WithRSA'): certHash = 'SHA256'; break;
      case pipnss.GetStringFromName('CertDumpSHA384WithRSA'): certHash = 'SHA384'; break;
      case pipnss.GetStringFromName('CertDumpSHA512WithRSA'): certHash = 'SHA512';
    }

    // assume ECDSA OID
    if (!certHash) {
      // displayData: 'Object Identifier (1 2 840 10045 4 3 2)'
      var oidMatches = displayData.match(/\((.*)\)/);
      if (oidMatches && oidMatches.length > 1) {
        var oid = oidMatches[1];

        switch (oid) {
          case '1 2 840 10045 4 1':   certHash = 'SHA1';   break;
          case '1 2 840 10045 4 3 1': certHash = 'SHA224'; break;
          case '1 2 840 10045 4 3 2': certHash = 'SHA256'; break;
          case '1 2 840 10045 4 3 3': certHash = 'SHA384'; break;
          case '1 2 840 10045 4 3 4': certHash = 'SHA512'; break;
        }
      }
    }

    var certFrom = cert.validity.notBeforeLocalDay;
    var certExp = cert.validity.notAfterLocalDay;
    var certIss = cert.issuerOrganization;

    // replace variable names in format string with values
    label = label
      .replace(/\$CERTORG/g,    certOrg  || unknown)
      .replace(/\$CERTCN/g,     certCn   || unknown)
      .replace(/\$CERTALG/g,    certAlg  || unknown)
      .replace(/\$CERTSIZE/g,   certSize || unknown)
      .replace(/\$CERTHASH/g,   certHash || unknown)
      .replace(/\$CERTISSUED/g, certFrom || unknown)
      .replace(/\$CERTEXP/g,    certExp  || unknown)
      .replace(/\$CERTISSUER/g, certIss  || unknown);

    return label;
  };

  var updateCipher = function() {
    hideIdentityPopup();

    var currentBrowser = gBrowser.selectedBrowser;
    var panelLabel = null;
    var hidden = true;

    var ui = currentBrowser.securityUI;
    if (ui instanceof Ci.nsISecureBrowserUI) {
      var status = ui.QueryInterface(Ci.nsISSLStatusProvider).SSLStatus;
      var isPartial = (ui.state & Ci.nsIWebProgressListener.STATE_IS_BROKEN);

      if (status instanceof Ci.nsISSLStatus) {
        panelLabel = formatLabel(status);
        hidden = !(panelLabel && (!isPartial || prefs.show_partial));
        populateCertChain(status);
      }
    }

    cfPanel.label = panelLabel;
    cfPanel.hidden  = hidden || !prefs.show_panel;

    if (cfButton instanceof XULElement) {
      cfButton.label = panelLabel;
      cfButton.hidden = hidden || !prefs.show_button;
    }
  };

  // unused functions must be defined
  var updateListener = {
    onStateChange:    function(){},
    onProgressChange: function(){},
    onLocationChange: function(){},
    onStatusChange:   function(){},
    onSecurityChange: function(webProgress, request, state) { updateCipher(); }
  };

  // exposed methods
  return {
    onLoad: function() {
      cfToggle = document.getElementById('cipherfox-toggle-rc4');
      cfPanel  = document.getElementById('cipherfox-panel');
      cfButton = document.getElementById('cipherfox-button');
      cfCerts  = document.getElementById('cipherfox-certs');
      cfBCerts = document.getElementById('cipherfox-button-certs');
      cfPSep   = document.getElementById('cipherfox-prefs-seperator');

      // don't autohide the identity-popup
      var moreInfo = document.getElementById('identity-popup-more-info-button');
      if (moreInfo instanceof XULElement) {
        moreInfo.removeAttribute('onblur');
        moreInfo.addEventListener('command', hideIdentityPopup, false);
      }

      if (cfCerts instanceof XULElement) {
        cfCerts.addEventListener('popupshowing', function() {
          cfPanel.setAttribute('popupopen', true);
        }, false);

        cfCerts.addEventListener('popuphiding', function() {
          cfPanel.removeAttribute('popupopen');
        }, false);
      }

      // quick RC4 toggle
      cfToggle.addEventListener('command', toggleRC4, false);

      // keep the identity-box 'open'
      if (cfBCerts instanceof XULElement) {
        cfBCerts.addEventListener('popuphidden', function(e) {
          e.stopPropagation();
        }, false);
      }

      prefService.addObserver('extensions.cipherfox.', this, false);
      loadPrefs();

      // only modify RC4 prefs if the user has disabled RC4
      if (prefs.disable_rc4) { setRC4(); }

      gBrowser.addProgressListener(updateListener);
    },

    onUnload: function() {
      prefService.removeObserver('extensions.cipherfox.', this);
      gBrowser.removeProgressListener(updateListener);
    },

    // update state when prefs change
    observe: function(subject, topic, data) {
      if (topic === 'nsPref:changed') {
        loadPrefs();
        updateCipher();
        if (data === 'extensions.cipherfox.disable_rc4') { setRC4(); }
      }
    },

    copyCipherSuite: function() {
      var securityUI = gBrowser.selectedBrowser.securityUI;
      if (securityUI instanceof Ci.nsISecureBrowserUI) {
        var status = securityUI.QueryInterface(Ci.nsISSLStatusProvider).SSLStatus;
        if (status instanceof Ci.nsISSLStatus) {
          clipboardHelper.copyString(status.cipherSuite || status.cipherName);
        }
      }
    },

    // Qualys SSL Labs Server Test
    testDomain: function() {
      gBrowser.addTab('https://www.ssllabs.com/ssldb/analyze.html?d='
                      + gBrowser.contentDocument.domain);
    }
  };
})();
<|endoftext|>


<commit_before>var _ = require('underscore');
var VisView = require('../vis/vis-view');
var VisModel = require('../vis/vis');
var Loader = require('../core/loader');
var VizJSON = require('./vizjson');

var DEFAULT_OPTIONS = {
  tiles_loader: true,
  loaderControl: true,
  infowindow: true,
  tooltip: true,
  time_slider: true,
  show_empty_infowindow_fields: false
};

var createVis = function (el, vizjson, options) {
  if (typeof el === 'string') {
    el = document.getElementById(el);
  }
  if (!el) {
    throw new TypeError('a valid DOM element or selector must be provided');
  }
  if (!vizjson) {
    throw new TypeError('a vizjson URL or object must be provided');
  }

  options = _.defaults(options || {}, DEFAULT_OPTIONS);

  var isProtocolHTTPs = window && window.location.protocol && window.location.protocol === 'https:';

  var visModel = new VisModel({
    title: options.title || vizjson.title,
    description: options.description || vizjson.description,
    apiKey: options.apiKey,
    authToken: vizjson.auth_tokens && vizjson.auth_tokens.length && vizjson.auth_tokens[0],
    showLegends: options.legends === true || vizjson.legends === true,
    showLogo: options.logo === undefined || options.logo === true || vizjson.logo === true,
    showEmptyInfowindowFields: options.show_empty_infowindow_fields === true,
    https: isProtocolHTTPs || options.https === true || vizjson.https === true
  });

  new VisView({ // eslint-disable-line
    el: el,
    model: visModel
  });

  if (typeof vizjson === 'string') {
    var url = vizjson;
    Loader.get(url, function (vizjson) {
      if (vizjson) {
        loadVizJSON(visModel, vizjson, options);
      } else {
        throw new Error('error fetching viz.json file');
      }
    });
  } else {
    loadVizJSON(visModel, vizjson, options);
  }

  return visModel;
};

var loadVizJSON = function (visModel, vizjsonData, options) {
  var vizjson = new VizJSON(vizjsonData);
  applyOptionsToVizJSON(vizjson, options);
  visModel.load(vizjson);
  if (!options.skipMapInstantiation) {
    visModel.instantiateMap();
  }
};

var applyOptionsToVizJSON = function (vizjson, options) {
  vizjson.scrollwheel = options.scrollwheel || vizjson.scrollwheel;

  if (!options.tiles_loader || !options.loaderControl) {
    vizjson.removeLoaderOverlay();
  }

  if (options.searchControl === true) {
    vizjson.addSearchOverlay();
  } else if (options.searchControl === false) {
    vizjson.removeSearchOverlay();
  }

  if ((options.title && vizjson.title) || (options.description && vizjson.description)) {
    vizjson.addHeaderOverlay(options.title, options.description, options.shareable);
  }

  if (options.layer_selector) {
    vizjson.addLayerSelectorOverlay();
  }

  if (options.zoomControl !== undefined && !options.zoomControl) {
    vizjson.removeZoomOverlay();
  }

  // if bounds are present zoom and center will not taken into account
  var zoom = parseInt(options.zoom, 10);
  if (!isNaN(zoom)) {
    vizjson.setZoom(zoom);
  }

  // Center coordinates?
  var center_lat = parseFloat(options.center_lat);
  var center_lon = parseFloat(options.center_lon);
  if (!isNaN(center_lat) && !isNaN(center_lon)) {
    vizjson.setCenter([center_lat, center_lon]);
  }

  // Center object
  if (options.center !== undefined) {
    vizjson.setCenter(options.center);
  }

  // Bounds?
  var sw_lat = parseFloat(options.sw_lat);
  var sw_lon = parseFloat(options.sw_lon);
  var ne_lat = parseFloat(options.ne_lat);
  var ne_lon = parseFloat(options.ne_lon);

  if (!isNaN(sw_lat) && !isNaN(sw_lon) && !isNaN(ne_lat) && !isNaN(ne_lon)) {
    vizjson.setBounds([
      [ sw_lat, sw_lon ],
      [ ne_lat, ne_lon ]
    ]);
  }

  if (options.gmaps_base_type) {
    vizjson.enforceGMapsBaseLayer(options.gmaps_base_type, options.gmaps_style);
  }
};

module.exports = createVis;
<commit_msg>Checking logo visibility<commit_after>var _ = require('underscore');
var VisView = require('../vis/vis-view');
var VisModel = require('../vis/vis');
var Loader = require('../core/loader');
var VizJSON = require('./vizjson');

var DEFAULT_OPTIONS = {
  tiles_loader: true,
  loaderControl: true,
  infowindow: true,
  tooltip: true,
  time_slider: true,
  logo: true,
  show_empty_infowindow_fields: false
};

var createVis = function (el, vizjson, options) {
  if (typeof el === 'string') {
    el = document.getElementById(el);
  }
  if (!el) {
    throw new TypeError('a valid DOM element or selector must be provided');
  }
  if (!vizjson) {
    throw new TypeError('a vizjson URL or object must be provided');
  }

  options = _.defaults(options || {}, DEFAULT_OPTIONS);

  var isProtocolHTTPs = window && window.location.protocol && window.location.protocol === 'https:';

  var showLogo = true;
  if (options.logo === false) {
    showLogo = false;
  }
  if (vizjson.logo === false && showLogo) {
    showLogo = false;
  }

  var visModel = new VisModel({
    title: options.title || vizjson.title,
    description: options.description || vizjson.description,
    apiKey: options.apiKey,
    authToken: vizjson.auth_tokens && vizjson.auth_tokens.length && vizjson.auth_tokens[0],
    showLegends: options.legends === true || vizjson.legends === true,
    showLogo: showLogo,
    showEmptyInfowindowFields: options.show_empty_infowindow_fields === true,
    https: isProtocolHTTPs || options.https === true || vizjson.https === true
  });

  new VisView({ // eslint-disable-line
    el: el,
    model: visModel
  });

  if (typeof vizjson === 'string') {
    var url = vizjson;
    Loader.get(url, function (vizjson) {
      if (vizjson) {
        loadVizJSON(visModel, vizjson, options);
      } else {
        throw new Error('error fetching viz.json file');
      }
    });
  } else {
    loadVizJSON(visModel, vizjson, options);
  }

  return visModel;
};

var loadVizJSON = function (visModel, vizjsonData, options) {
  var vizjson = new VizJSON(vizjsonData);
  applyOptionsToVizJSON(vizjson, options);
  visModel.load(vizjson);
  if (!options.skipMapInstantiation) {
    visModel.instantiateMap();
  }
};

var applyOptionsToVizJSON = function (vizjson, options) {
  vizjson.scrollwheel = options.scrollwheel || vizjson.scrollwheel;

  if (!options.tiles_loader || !options.loaderControl) {
    vizjson.removeLoaderOverlay();
  }

  if (options.searchControl === true) {
    vizjson.addSearchOverlay();
  } else if (options.searchControl === false) {
    vizjson.removeSearchOverlay();
  }

  if ((options.title && vizjson.title) || (options.description && vizjson.description)) {
    vizjson.addHeaderOverlay(options.title, options.description, options.shareable);
  }

  if (options.layer_selector) {
    vizjson.addLayerSelectorOverlay();
  }

  if (options.zoomControl !== undefined && !options.zoomControl) {
    vizjson.removeZoomOverlay();
  }

  // if logo is not included, let add it :)
  if (!vizjson.hasOverlay('logo')) {
    vizjson.addLogoOverlay();
  }

  // if bounds are present zoom and center will not taken into account
  var zoom = parseInt(options.zoom, 10);
  if (!isNaN(zoom)) {
    vizjson.setZoom(zoom);
  }

  // Center coordinates?
  var center_lat = parseFloat(options.center_lat);
  var center_lon = parseFloat(options.center_lon);
  if (!isNaN(center_lat) && !isNaN(center_lon)) {
    vizjson.setCenter([center_lat, center_lon]);
  }

  // Center object
  if (options.center !== undefined) {
    vizjson.setCenter(options.center);
  }

  // Bounds?
  var sw_lat = parseFloat(options.sw_lat);
  var sw_lon = parseFloat(options.sw_lon);
  var ne_lat = parseFloat(options.ne_lat);
  var ne_lon = parseFloat(options.ne_lon);

  if (!isNaN(sw_lat) && !isNaN(sw_lon) && !isNaN(ne_lat) && !isNaN(ne_lon)) {
    vizjson.setBounds([
      [ sw_lat, sw_lon ],
      [ ne_lat, ne_lon ]
    ]);
  }

  if (options.gmaps_base_type) {
    vizjson.enforceGMapsBaseLayer(options.gmaps_base_type, options.gmaps_style);
  }
};

module.exports = createVis;
<|endoftext|>


<commit_before>'use strict'

const { BaseService } = require('..')

module.exports = class Maintenance extends BaseService {
  static get route() {
    return {
      base: 'maintenance',
      pattern: ':maintained/:year(\\d{4})',
    }
  }

  static get defaultBadgeData() {
    return {
      label: 'maintained',
    }
  }

  async handle({ maintained, year }) {
    const now = new Date()
    const cy = now.getUTCFullYear() // current year.
    const m = now.getUTCMonth() // month.

    if (maintained === 'no') {
      return this.constructor.render({ message: `no! (as of ${year})` })
    } else if (cy <= year) {
      return this.constructor.render({ message: maintained })
    } else if (parseInt(cy) === parseInt(year) + 1 && parseInt(m) < 3) {
      return this.constructor.render({ message: `stale (as of ${cy})` })
    } else {
      return this.constructor.render({ message: `no! (as of ${year})` })
    }
  }

  static render({ message }) {
    if (message.startsWith('yes')) {
      return {
        message,
        color: 'brightgreen',
      }
    } else if (message.startsWith('no')) {
      return {
        message,
        color: 'red',
      }
    } else {
      return { message }
    }
  }

  static get category() {
    return 'other'
  }
  static get examples() {
    return [
      {
        title: 'Maintenance',
        pattern: ':maintained/:year',
        namedParams: {
          maintained: 'yes',
          year: '2019',
        },
        staticPreview: this.render({ message: 'yes' }),
        keywords: ['maintained'],
      },
    ]
  }
}
<commit_msg>Tweak [maintenance] badge in UI; remove caching (#3040)<commit_after>'use strict'

const { NonMemoryCachingBaseService } = require('..')

module.exports = class Maintenance extends NonMemoryCachingBaseService {
  static get route() {
    return {
      base: 'maintenance',
      pattern: ':maintained/:year(\\d{4})',
    }
  }

  static get defaultBadgeData() {
    return {
      label: 'maintained',
    }
  }

  async handle({ maintained, year }) {
    const now = new Date()
    const cy = now.getUTCFullYear() // current year.
    const m = now.getUTCMonth() // month.

    if (maintained === 'no') {
      return this.constructor.render({ message: `no! (as of ${year})` })
    } else if (cy <= year) {
      return this.constructor.render({ message: maintained })
    } else if (parseInt(cy) === parseInt(year) + 1 && parseInt(m) < 3) {
      return this.constructor.render({ message: `stale (as of ${cy})` })
    } else {
      return this.constructor.render({ message: `no! (as of ${year})` })
    }
  }

  static render({ message }) {
    if (message.startsWith('yes')) {
      return {
        message,
        color: 'brightgreen',
      }
    } else if (message.startsWith('no')) {
      return {
        message,
        color: 'red',
      }
    } else {
      return { message }
    }
  }

  static get category() {
    return 'other'
  }

  static get examples() {
    return [
      {
        title: 'Maintenance',
        pattern: ':maintained(yes|no)/:year',
        namedParams: {
          maintained: 'yes',
          year: '2019',
        },
        staticPreview: this.render({ message: 'yes' }),
        keywords: ['maintained'],
      },
    ]
  }
}
<|endoftext|>


<commit_before>/**
* directive.diDesignPiece Module
*
* Description
*/
angular.module('directive.diDesignPiece', [
    'ngMessages',
    'resource.GameData',
    'service.ROTATION_MATRIX'
])
.factory('CustomPiece', [
    'GameData',
    'ROTATION_MATRIX',
    function (
    GameData,
    ROTATION_MATRIX
){
    var custom = {},
        posToCoord = _.memoize(function (i) {
            var x = i % getCustomPieceWidth(),
                y = (i - x) / getCustomPieceWidth();

            return {
                x: x,
                y: y
            };
        }),
        patternCoord2d = [],
        pattern2d = [];

    function getCustomPieceWidth() {
        return GameData.customPieceWidth;
    }

    function getRotatePattern(matrix, deg) {
        var pattern = [],
            rotationArray = ROTATION_MATRIX[deg];
        // [y, x], index 0 is y, 1 is x
        _.each(rotationArray, function (elem, index) {
            var pos = !_.chain(matrix)
                    .find(function (obj, key) {
                        return elem[1] === obj.x &&
                                elem[0] === obj.y;
                    })
                    .isUndefined()
                    .value();
            if (pos) {
                pattern.push(index);
            }
        });
        pattern2d.push(pattern);
    }

    function setPatternCoord2d() {
        patternCoord2d = [];
        _.each(pattern2d, function (pattern) {
            setPatternToCoord(pattern);
        });
    }

    function setPatternToCoord(pattern) {
        var patternCoord = [];
        _.each(pattern, function (i) {
            patternCoord.push(posToCoord(i));
        });
        patternCoord2d.push(patternCoord);
    }

    custom.generatePatterns = function generatePatterns(pieces) {
        var pattern = [];
        pattern2d = [];
        patternCoord2d = [];
        _.each(pieces, function (piece, index) {
            if (piece.isSelected) {
                pattern.push(index);
            }
        });
        pattern2d.push(pattern);
        custom.generatePatternCoord();
    };

    custom.generatePatternCoord = function generatePatternCoord() {
        // set up the first patter coord
        setPatternToCoord(pattern2d[0]);

        for(var deg in [90, 180, 270]) {
            // deg is index [0, 1, 2]
            // patternCoor2d has init with orgin coord
            // so index + 1 to move to next rotation
            getRotatePattern(patternCoord2d[0], parseInt(deg, 10) + 1);
        }

        setPatternCoord2d();
    };

    custom.getPattern = function getPattern(rotation) {
        return pattern2d[rotation];
    };

    custom.getPatternCoord = function getPatternCoord(rotation) {
        return patternCoord2d[rotation];
    };

    custom.getCustomPiece = function getCustomPiece() {
        return {
            pattern2d: pattern2d,
            patternCoord2d: patternCoord2d
        };
    };

    custom.setCustomPiece = function setCustomPiece(customPiece) {
        pattern2d = customPiece.pattern2d;
        patternCoord2d = customPiece.patternCoord2d;
    };

    custom.hasCustomPiece = function hasCustomPiece() {
        return pattern2d.length > 0;
    };

    return custom;
}])
.controller('DesignPieceCtrl', [
    '$scope',
    'GameData',
    'CustomPiece',
function (
    $scope,
    GameData,
    CustomPiece
){
    var isSumbit = false,
        currentColor = '';

    $scope.pieces = [];
    $scope.colors = randomColor({count: GameData.availableColors});
    if (GameData.getColor() === '') {
        GameData.setColor(randomColor());
    }
    currentColor = GameData.getColor();

    _.each(_.range(16), function (value) {
        $scope.pieces[value] = {
            isSelected: false
        };
    });
    _.each(CustomPiece.getPattern(0), function (index) {
        $scope.pieces[index].isSelected = true;
    });
    $scope.errorCode = {
        maxlength: false,
        minlength: ($scope.pieces.length === 0),
        saved: false
    };

    function checkSelectedField() {
        var res = _.countBy($scope.pieces, function (piece) {
            return piece.isSelected ? 'selected' : 'unselected';
        });
        if (res.selected > GameData.maxCustomPiece) {
            $scope.errorCode.maxlength = true;
        } else {
            $scope.errorCode.maxlength = false;
        }
        if (res.selected > 0) {
            $scope.errorCode.minlength = false;
        } else if (_.isUndefined(res.selected)) {
            $scope.errorCode.minlength = true;
        }
        $scope.errorCode.saved = false;
    }

    function isOk() {
        // check any fields except saved field
        return _.chain($scope.errorCode)
                .find(function (value, key) {
                    return key === 'saved' ? false : value;
                })
                .isUndefined()
                .value();
    }

    $scope.onClick = function onClick(index) {
        var isChecked = $scope.pieces[index].isSelected;
        $scope.pieces[index].isSelected = !isChecked;
        checkSelectedField();
        isSumbit = true;
    };

    $scope.isSelected = function isSelected(index) {
        return $scope.pieces[index].isSelected;
    };

    $scope.savePiece = function savePiece() {
        isSumbit = true;
        if (isOk()) {
            $scope.errorCode.saved = true;
            CustomPiece.generatePatterns($scope.pieces);
            GameData.setColor(currentColor);
        }
    };

    $scope.isSaved = function isSaved() {
        return isSumbit && !$scope.errorCode.saved;
    };

    $scope.getMaxPiece = function getMaxPiece() {
        return GameData.maxCustomPiece;
    };

    $scope.interacted = function interacted() {
        return isSumbit;
    };

    $scope.pickColor = function pickColor(color) {
        $scope.errorCode.saved = false;
        if ($scope.pieces.length > 0) {
            isSumbit = true;
        }
        currentColor = color;
    };

    $scope.getColor = function getColor() {
        return { background: currentColor };
    };

    $scope.getColorByIndex = function getColorByIndex(index) {
        if ($scope.pieces[index].isSelected) {
            return currentColor;
        }
        return;
    };

}])
.directive('diDesignPiece', [
function(
){
    var DesignPiece = {};

    DesignPiece.controller = 'DesignPieceCtrl';

    DesignPiece.templateUrl = 'directives/diDesignPiece/diDesignPiece.tpl.html';

    DesignPiece.restrict = 'A';

    DesignPiece.scope = true;

    DesignPiece.link = function link(scope, element, attrs) {};

    return DesignPiece;
}]);<commit_msg>Add game manager to update the error message's css<commit_after>/**
* directive.diDesignPiece Module
*
* Description
*/
angular.module('directive.diDesignPiece', [
    'ngMessages',
    'resource.GameData',
    'service.ROTATION_MATRIX',
    'service.GameManager'
])
.factory('CustomPiece', [
    'GameData',
    'ROTATION_MATRIX',
    function (
    GameData,
    ROTATION_MATRIX
){
    var custom = {},
        posToCoord = _.memoize(function (i) {
            var x = i % getCustomPieceWidth(),
                y = (i - x) / getCustomPieceWidth();

            return {
                x: x,
                y: y
            };
        }),
        patternCoord2d = [],
        pattern2d = [];

    function getCustomPieceWidth() {
        return GameData.customPieceWidth;
    }

    function getRotatePattern(matrix, deg) {
        var pattern = [],
            rotationArray = ROTATION_MATRIX[deg];
        // [y, x], index 0 is y, 1 is x
        _.each(rotationArray, function (elem, index) {
            var pos = !_.chain(matrix)
                    .find(function (obj, key) {
                        return elem[1] === obj.x &&
                                elem[0] === obj.y;
                    })
                    .isUndefined()
                    .value();
            if (pos) {
                pattern.push(index);
            }
        });
        pattern2d.push(pattern);
    }

    function setPatternCoord2d() {
        patternCoord2d = [];
        _.each(pattern2d, function (pattern) {
            setPatternToCoord(pattern);
        });
    }

    function setPatternToCoord(pattern) {
        var patternCoord = [];
        _.each(pattern, function (i) {
            patternCoord.push(posToCoord(i));
        });
        patternCoord2d.push(patternCoord);
    }

    custom.generatePatterns = function generatePatterns(pieces) {
        var pattern = [];
        pattern2d = [];
        patternCoord2d = [];
        _.each(pieces, function (piece, index) {
            if (piece.isSelected) {
                pattern.push(index);
            }
        });
        pattern2d.push(pattern);
        custom.generatePatternCoord();
    };

    custom.generatePatternCoord = function generatePatternCoord() {
        // set up the first patter coord
        setPatternToCoord(pattern2d[0]);

        for(var deg in [90, 180, 270]) {
            // deg is index [0, 1, 2]
            // patternCoor2d has init with orgin coord
            // so index + 1 to move to next rotation
            getRotatePattern(patternCoord2d[0], parseInt(deg, 10) + 1);
        }

        setPatternCoord2d();
    };

    custom.getPattern = function getPattern(rotation) {
        return pattern2d[rotation];
    };

    custom.getPatternCoord = function getPatternCoord(rotation) {
        return patternCoord2d[rotation];
    };

    custom.getCustomPiece = function getCustomPiece() {
        return {
            pattern2d: pattern2d,
            patternCoord2d: patternCoord2d
        };
    };

    custom.setCustomPiece = function setCustomPiece(customPiece) {
        pattern2d = customPiece.pattern2d;
        patternCoord2d = customPiece.patternCoord2d;
    };

    custom.hasCustomPiece = function hasCustomPiece() {
        return pattern2d.length > 0;
    };

    return custom;
}])
.controller('DesignPieceCtrl', [
    '$scope',
    'GameData',
    'CustomPiece',
    'GameManager',
function (
    $scope,
    GameData,
    CustomPiece,
    GameManager
){
    var isSumbit = false,
        currentColor = '';

    $scope.pieces = [];
    $scope.colors = randomColor({count: GameData.availableColors});
    if (GameData.getColor() === '') {
        GameData.setColor(randomColor());
    }
    currentColor = GameData.getColor();

    _.each(_.range(16), function (value) {
        $scope.pieces[value] = {
            isSelected: false
        };
    });
    _.each(CustomPiece.getPattern(0), function (index) {
        $scope.pieces[index].isSelected = true;
    });
    $scope.errorCode = {
        maxlength: false,
        minlength: ($scope.pieces.length === 0),
        saved: false
    };

    function checkSelectedField() {
        var res = _.countBy($scope.pieces, function (piece) {
            return piece.isSelected ? 'selected' : 'unselected';
        });
        if (res.selected > GameData.maxCustomPiece) {
            $scope.errorCode.maxlength = true;
        } else {
            $scope.errorCode.maxlength = false;
        }
        if (res.selected > 0) {
            $scope.errorCode.minlength = false;
        } else if (_.isUndefined(res.selected)) {
            $scope.errorCode.minlength = true;
        }
        $scope.errorCode.saved = false;
    }

    function isOk() {
        // check any fields except saved field
        return _.chain($scope.errorCode)
                .find(function (value, key) {
                    return key === 'saved' ? false : value;
                })
                .isUndefined()
                .value();
    }

    $scope.onClick = function onClick(index) {
        var isChecked = $scope.pieces[index].isSelected;
        $scope.pieces[index].isSelected = !isChecked;
        checkSelectedField();
        isSumbit = true;
    };

    $scope.isSelected = function isSelected(index) {
        return $scope.pieces[index].isSelected;
    };

    $scope.savePiece = function savePiece() {
        isSumbit = true;
        if (isOk()) {
            $scope.errorCode.saved = true;
            CustomPiece.generatePatterns($scope.pieces);
            GameData.setColor(currentColor);
        }
    };

    $scope.isSaved = function isSaved() {
        return isSumbit && !$scope.errorCode.saved;
    };

    $scope.getMaxPiece = function getMaxPiece() {
        return GameData.maxCustomPiece;
    };

    $scope.interacted = function interacted() {
        return isSumbit;
    };

    $scope.pickColor = function pickColor(color) {
        $scope.errorCode.saved = false;
        if ($scope.pieces.length > 0) {
            isSumbit = true;
        }
        currentColor = color;
    };

    $scope.getColor = function getColor() {
        return { background: currentColor };
    };

    $scope.getColorByIndex = function getColorByIndex(index) {
        if ($scope.pieces[index].isSelected) {
            return currentColor;
        }
        return;
    };

    $scope.getTop = function() {
        var customTop = GameManager.getOpenDesignBeforeStart() ? { top: '-76px' } : {};
        return customTop;
    };

}])
.directive('diDesignPiece', [
function(
){
    var DesignPiece = {};

    DesignPiece.controller = 'DesignPieceCtrl';

    DesignPiece.templateUrl = 'directives/diDesignPiece/diDesignPiece.tpl.html';

    DesignPiece.restrict = 'A';

    DesignPiece.scope = true;

    DesignPiece.link = function link(scope, element, attrs) {};

    return DesignPiece;
}]);<|endoftext|>


<commit_before>const queries = require('./queries');
const userQueries = require('../user/queries');

module.exports = async function deleteUserTag(ctx) {
  const userExternalId = ctx.params.user_id;
  const tagName = ctx.params.tag_name;

  const tags = await queries.deleteTag(tagName);

  if (tags === []) {
    ctx.response.status = 404;
    ctx.fail({tag: `tag with tagName ${tagName} not found`});
    return;
  }

  ctx.success({ id: userExternalId, tag: tagName });
};
<commit_msg>removed unused import<commit_after>const queries = require('./queries');

module.exports = async function deleteUserTag(ctx) {
  const userExternalId = ctx.params.user_id;
  const tagName = ctx.params.tag_name;

  const tags = await queries.deleteTag(tagName);

  if (tags === []) {
    ctx.response.status = 404;
    ctx.fail({tag: `tag with tagName ${tagName} not found`});
    return;
  }

  ctx.success({ id: userExternalId, tag: tagName });
};
<|endoftext|>


<commit_before>import React from 'react';
import { injectIntl } from 'react-intl';
import { connect } from 'react-redux';
import messages from '../../resources/messages';
import AppSubHeader from '../common/header/AppSubHeader';
import { favoriteSource, favoriteCollection } from '../../actions/sourceActions';
import { updateFeedback } from '../../actions/appActions';
import { nullOrUndefined } from '../../lib/formValidators';

const localMessages = {
  sourceFavorited: { id: 'source.favorited', defaultMessage: 'Starred this source' },
  sourceUnfavorited: { id: 'source.unfavorited', defaultMessage: 'Un-starred this source' },
  collectionFavorited: { id: 'collection.favorited', defaultMessage: 'Starred this collection' },
  collectionUnfavorited: { id: 'collection.unfavorited', defaultMessage: 'Un-starred this collection' },
  collectionDynamic: { id: 'collection.unfavorited', defaultMessage: 'Dynamic (can change)' },
  collectionStatic: { id: 'collection.unfavorited', defaultMessage: 'Static (won\'t change)' },
};

const SourceMgrSubHeaderContainer = (props) => {
  const { currentSelection, sourceId, sourceInfo, collectionId, collectionInfo, handleSetFavorited } = props;
  const { formatMessage } = props.intl;
  let content = null;

  if (!nullOrUndefined(sourceId) || currentSelection.includes('sources')) { // for multiple pathways
    let details = '';
    details += `ID #${sourceInfo.id}`;
    details += ` • ${formatMessage(messages.public)} `; // for now, every media source is public
    details += (sourceInfo.is_monitored) ? ` • ${formatMessage(messages.monitored)}` : '';
    content = (

        <AppSubHeader
          title={`${formatMessage(messages.sourceName)}: ${sourceInfo.name}`}
          subTitle={details}
          isFavorite={sourceInfo.isFavorite}
          onSetFavorited={isFav => handleSetFavorited('source', sourceInfo.id, isFav)}
        />

    );
  } else if (!nullOrUndefined(collectionId) || currentSelection.includes('collections')) {
    let details = '';
    details += `ID #${collectionInfo.id}`;
    details += (collectionInfo.show_on_media === 1) ? ` • ${formatMessage(messages.public)}` : ` • ${formatMessage(messages.private)}`;
    details += (collectionInfo.is_static === 1) ? ` • ${formatMessage(localMessages.collectionStatic)}` : ` • ${formatMessage(localMessages.collectionDynamic)}`;
    content = (

        <AppSubHeader
          title={`${formatMessage(messages.collectionName)}: ${collectionInfo.label || collectionInfo.tag}`}
          subTitle={details}
          isFavorite={collectionInfo.isFavorite}
          onSetFavorited={isFav => handleSetFavorited('collection', collectionInfo.id, isFav)}
        />

    );
  } else {
    content = null;
  }
  return (

      {content}

  );
};

SourceMgrSubHeaderContainer.propTypes = {
  // from parent
  // from context
  params: React.PropTypes.object,       // params from router
  location: React.PropTypes.object,
  intl: React.PropTypes.object.isRequired,
  // state
  sourceId: React.PropTypes.number,
  sourceInfo: React.PropTypes.object,
  collectionId: React.PropTypes.number,
  collectionInfo: React.PropTypes.object,
  // from dispatch
  handleSetFavorited: React.PropTypes.func.isRequired,
  // from url
  currentSelection: React.PropTypes.string,
};

const mapStateToProps = state => ({
  sourceId: state.sources.sources.selected.id,
  sourceInfo: state.sources.sources.selected.sourceDetails,
  collectionId: state.sources.collections.selected.id,
  collectionInfo: state.sources.collections.selected.collectionDetails.object,
  currentSelection: state.routing.locationBeforeTransitions.pathname,
});

const mapDispatchToProps = (dispatch, ownProps) => ({
  handleSetFavorited: (type, id, isFav) => {
    switch (type) {
      case 'source':
        dispatch(favoriteSource(id, isFav))
          .then(() => {
            const msg = (isFav) ? localMessages.sourceFavorited : localMessages.sourceUnfavorited;
            dispatch(updateFeedback({ open: true, message: ownProps.intl.formatMessage(msg) }));
          });
        break;
      case 'collection':
        dispatch(favoriteCollection(id, isFav))
          .then(() => {
            const msg = (isFav) ? localMessages.collectionFavorited : localMessages.collectionUnfavorited;
            dispatch(updateFeedback({ open: true, message: ownProps.intl.formatMessage(msg) }));
          });
        break;
      default:
        const error = { message: `unknown type to mark as favorite: ${type}` };
        throw error;
    }
  },
});

export default
  injectIntl(
    connect(mapStateToProps, mapDispatchToProps)(
      SourceMgrSubHeaderContainer
    )
  );
<commit_msg>#427 use with-router to get the location for subheader title<commit_after>import React from 'react';
import { injectIntl } from 'react-intl';
import { connect } from 'react-redux';
import { withRouter } from 'react-router';
import messages from '../../resources/messages';
import AppSubHeader from '../common/header/AppSubHeader';
import { favoriteSource, favoriteCollection } from '../../actions/sourceActions';
import { updateFeedback } from '../../actions/appActions';
import { nullOrUndefined } from '../../lib/formValidators';

const localMessages = {
  sourceFavorited: { id: 'source.favorited', defaultMessage: 'Starred this source' },
  sourceUnfavorited: { id: 'source.unfavorited', defaultMessage: 'Un-starred this source' },
  collectionFavorited: { id: 'collection.favorited', defaultMessage: 'Starred this collection' },
  collectionUnfavorited: { id: 'collection.unfavorited', defaultMessage: 'Un-starred this collection' },
  collectionDynamic: { id: 'collection.unfavorited', defaultMessage: 'Dynamic (can change)' },
  collectionStatic: { id: 'collection.unfavorited', defaultMessage: 'Static (won\'t change)' },
};

const SourceMgrSubHeaderContainer = (props) => {
  const { sourceId, sourceInfo, collectionId, collectionInfo, location, handleSetFavorited } = props;
  const { formatMessage } = props.intl;
  let content = null;

  if (!nullOrUndefined(sourceId) || location.pathname.includes('sources')) { // for multiple pathways
    let details = '';
    details += `ID #${sourceInfo.id}`;
    details += ` • ${formatMessage(messages.public)} `; // for now, every media source is public
    details += (sourceInfo.is_monitored) ? ` • ${formatMessage(messages.monitored)}` : '';
    content = (

        <AppSubHeader
          title={`${formatMessage(messages.sourceName)}: ${sourceInfo.name}`}
          subTitle={details}
          isFavorite={sourceInfo.isFavorite}
          onSetFavorited={isFav => handleSetFavorited('source', sourceInfo.id, isFav)}
        />

    );
  } else if (!nullOrUndefined(collectionId) || location.pathname.includes('collections')) {
    let details = '';
    details += `ID #${collectionInfo.id}`;
    details += (collectionInfo.show_on_media === 1) ? ` • ${formatMessage(messages.public)}` : ` • ${formatMessage(messages.private)}`;
    details += (collectionInfo.is_static === 1) ? ` • ${formatMessage(localMessages.collectionStatic)}` : ` • ${formatMessage(localMessages.collectionDynamic)}`;
    content = (

        <AppSubHeader
          title={`${formatMessage(messages.collectionName)}: ${collectionInfo.label || collectionInfo.tag}`}
          subTitle={details}
          isFavorite={collectionInfo.isFavorite}
          onSetFavorited={isFav => handleSetFavorited('collection', collectionInfo.id, isFav)}
        />

    );
  } else {
    content = null;
  }
  return (

      {content}

  );
};

SourceMgrSubHeaderContainer.propTypes = {
  // from parent
  // from context
  params: React.PropTypes.object,       // params from router
  location: React.PropTypes.object,
  intl: React.PropTypes.object.isRequired,
  // state
  sourceId: React.PropTypes.number,
  sourceInfo: React.PropTypes.object,
  collectionId: React.PropTypes.number,
  collectionInfo: React.PropTypes.object,
  // from dispatch
  handleSetFavorited: React.PropTypes.func.isRequired,
};

const mapStateToProps = state => ({
  sourceId: state.sources.sources.selected.id,
  sourceInfo: state.sources.sources.selected.sourceDetails,
  collectionId: state.sources.collections.selected.id,
  collectionInfo: state.sources.collections.selected.collectionDetails.object,
});

const mapDispatchToProps = (dispatch, ownProps) => ({
  handleSetFavorited: (type, id, isFav) => {
    switch (type) {
      case 'source':
        dispatch(favoriteSource(id, isFav))
          .then(() => {
            const msg = (isFav) ? localMessages.sourceFavorited : localMessages.sourceUnfavorited;
            dispatch(updateFeedback({ open: true, message: ownProps.intl.formatMessage(msg) }));
          });
        break;
      case 'collection':
        dispatch(favoriteCollection(id, isFav))
          .then(() => {
            const msg = (isFav) ? localMessages.collectionFavorited : localMessages.collectionUnfavorited;
            dispatch(updateFeedback({ open: true, message: ownProps.intl.formatMessage(msg) }));
          });
        break;
      default:
        const error = { message: `unknown type to mark as favorite: ${type}` };
        throw error;
    }
  },
});

export default
  injectIntl(
    connect(mapStateToProps, mapDispatchToProps)(
      withRouter(SourceMgrSubHeaderContainer)
    )
  );
<|endoftext|>


<commit_before>/**
 * Creates a DOM input element of type submit, and attaches event listener to
 * it.
 * @param {string} action - The action for the post form.
 * @param {string} value - The value of the button.
 * @return {Node} An input DOM element.
 */
function createInputSubmit(action, value) {
  let input = document.createElement('input');
  input.type = 'submit';
  input.formAction = action;
  input.value = value;
  addEvent(input, 'click', function(e) {
    let el = e.currentTarget;
    el.form.action = el.formAction;
  });
  return input;
}

/**
 * Creates a form with a textarea for users to edit a comment.
 * @param {Node} commentNode - A node containing a comment.
 * @return {Node} A DOM form node.
 */
function createCommentForm(commentNode) {
  let form = document.createElement('form');
  form.id = commentNode.id + '-edit';
  form.dataset.id = commentNode.id;
  form.classList.add('row');
  form.method = 'post';
  form.action = commentNode.querySelector('.edit-comment').href;

  let div = document.createElement('div');
  div.classList.add('col-md-8', 'col-centered');

  let textArea = document.createElement('textarea');
  textArea.classList.add('form-control', 'input-lg');
  textArea.name = 'text';
  textArea.value = commentNode.querySelector('p').innerHTML;

  div.appendChild(textArea);
  div.appendChild(createInputSubmit('cancel', 'Cancel'));
  div.appendChild(createInputSubmit('save', 'Save'));
  form.appendChild(div);

  return form;
}

/**
 * Hides a comment and displays a form to edit the comment.
 * @param {Event} e - An event dispatched to edit a comment.
 */
function tryEditComment(e) {
  e.preventDefault();
  let comment = document.getElementById(e.currentTarget.dataset.id);
  let form = createCommentForm(comment);
  addEvent(form, 'submit', editComment);
  comment.style.display = 'none';
  comment.insertAdjacentElement('afterend', form);
  form.focus();
}

/**
 * Removes the form to edit a comment, and replaces the old comment Node with a
 * new one.
 * @param {object} data - A plain json object with the comment ID and the new
 * html in plain text.
 */
function refreshComment(data) {
  let form = document.getElementById(data.id + '-edit');
  let oldComment = form.previousElementSibling;
  let comments = oldComment.parentNode;
  comments.removeChild(form);
  if (!oldComment.previousElementSibling) {
    // This comment is first comment
    comments.removeChild(oldComment);
    comments.insertAdjacentHTML('afterbegin', data.comment);
  } else {
    // This is not the first comment
    let sibling = oldComment.previousElementSibling;
    comments.removeChild(oldComment);
    sibling.insertAdjacentHTML('afterend', data.comment);
  }
  let comment = comments.querySelector('#' + data.id);
  addEvent(comment.querySelector('.delete-comment'), 'click', deleteComment);
  addEvent(comment.querySelector('.edit-comment'), 'click', tryEditComment);
}

/**
 * Removes the form to edit a comment, and re-displays the original comment.
 * @param {Element} form - A DOM form element.
 */
function putCommentBack(form) {
  form.previousElementSibling.style.display = 'block';
  form.parentNode.removeChild(form);
}

/**
 * Sends request to server to edit comment, and replaces old comment with new
 * comment.
 * @param {Event} e - An event from the form to edit a comment.
 */
function editComment(e) {
  e.preventDefault();
  let form = e.currentTarget;
  if (form.action.includes('cancel')) {
    putCommentBack(form);
    return;
  }
  let xhr = new XMLHttpRequest();
  xhr.onreadystatechange = function() {
    if (this.readyState == 4 && this.status == 200) {
      var data = JSON.parse(this.responseText);
      refreshComment(data);
    }
    // TODO: need to revert changes if there is an error
  };
  xhr.open('POST', '/edit-comment', true);
  let msg = JSON.stringify({
    'id': form.dataset.id,
    'text': form.elements.text.value
  });
  xhr.send(msg);
}

/**
 * Sends request to a server to delete a comment, and removes the comment from
 * the blog page.
 * @param {Event} e - An event to delete a comment.
 */
function deleteComment(e) {
  e.preventDefault();
  let xhr = new XMLHttpRequest();
  xhr.onreadystatechange = function() {
    if (this.readyState == 4 && this.status == 200) {
      var data = JSON.parse(this.responseText);
      if (data.id) {
        var el = document.getElementById(data.id);
        el.parentNode.removeChild(el);
      }
    }
  };
  xhr.open('POST', '/delete-comment', true);
  xhr.send(JSON.stringify({'id': e.currentTarget.dataset.id}));
}

/**
 * Sends request to a server to create a comment, and displays the new comment
 * in the blog page.
 * @param {Event} e - An event to create a comment.
 */
function createComment(e) {
  e.preventDefault();
  let xhr = new XMLHttpRequest();
  xhr.onreadystatechange = function() {
    if (this.readyState == 4 && this.status == 200) {
      let data = JSON.parse(this.responseText);
      let comments = document.querySelector('.blog-comments');
      comments.insertAdjacentHTML('beforeend', data.comment);
      document.querySelector('form').reset();
      let lc = comments.lastChild;
      lc.scrollIntoView();
      addEvent(lc.querySelector('.edit-comment'), 'click', tryEditComment);
      addEvent(lc.querySelector('.delete-comment'), 'click', deleteComment);
    }
  };
  let form = document.querySelector('form');
  xhr.open('POST', form.action, true);
  let msg = JSON.stringify({'text': form.elements.text.value});
  xhr.send(msg);
}

/**
 * Sends a request to a server to update the recommendation count for a given
 * blog post.
 * @param {Event} e - An event to like or unlike a blog post.
 */
function clickLike(e) {
  e.preventDefault();
  let xhr = new XMLHttpRequest();
  xhr.onreadystatechange = function() {
    if (this.readyState == 4 && this.status == 200) {
      let response = JSON.parse(this.responseText);
      if (response.add) {
        // Enable red heart and increase likes count
        let cl = document.getElementById('likes-heart').classList;
        cl.add('red-heart');
        cl.remove('normal');
        let el = document.querySelector('.likes-number');
        el.innerText = parseInt(el.innerText, 10) + 1;
      } else if (response.remove) {
        // Disable red heart and decrease likes count
        let cl = document.getElementById('likes-heart').classList;
        cl.remove('red-heart');
        cl.add('normal');
        let el = document.querySelector('.likes-number');
        let count = parseInt(el.innerText, 10);
        // double check that we are not taking number below zero
        if (count) {
          el.innerText = count - 1;
        }
      }
    }
  };
  let href = document.getElementById('like-button').href;
  xhr.open('GET', href, true);
  xhr.send();
}

(function() {
  let delLinks = document.querySelectorAll('.delete-comment');
  for (let i = 0; i < delLinks.length; i++) {
    addEvent(delLinks[i], 'click', deleteComment);
  }

  let editLinks = document.querySelectorAll('.edit-comment');
  for (let i = 0; i < editLinks.length; i++) {
    addEvent(editLinks[i], 'click', tryEditComment);
  }

  let form = document.querySelector('form');
  if (form.action.includes('create-comment')) {
    addEvent(form, 'submit', createComment);
  }

  addEvent(document.getElementById('like-button'), 'click', clickLike);
})();
<commit_msg>Comment for nameless function<commit_after>/**
 * Creates a DOM input element of type submit, and attaches event listener to
 * it.
 * @param {string} action - The action for the post form.
 * @param {string} value - The value of the button.
 * @return {Node} An input DOM element.
 */
function createInputSubmit(action, value) {
  let input = document.createElement('input');
  input.type = 'submit';
  input.formAction = action;
  input.value = value;
  addEvent(input, 'click', function(e) {
    let el = e.currentTarget;
    el.form.action = el.formAction;
  });
  return input;
}

/**
 * Creates a form with a textarea for users to edit a comment.
 * @param {Node} commentNode - A node containing a comment.
 * @return {Node} A DOM form node.
 */
function createCommentForm(commentNode) {
  let form = document.createElement('form');
  form.id = commentNode.id + '-edit';
  form.dataset.id = commentNode.id;
  form.classList.add('row');
  form.method = 'post';
  form.action = commentNode.querySelector('.edit-comment').href;

  let div = document.createElement('div');
  div.classList.add('col-md-8', 'col-centered');

  let textArea = document.createElement('textarea');
  textArea.classList.add('form-control', 'input-lg');
  textArea.name = 'text';
  textArea.value = commentNode.querySelector('p').innerHTML;

  div.appendChild(textArea);
  div.appendChild(createInputSubmit('cancel', 'Cancel'));
  div.appendChild(createInputSubmit('save', 'Save'));
  form.appendChild(div);

  return form;
}

/**
 * Hides a comment and displays a form to edit the comment.
 * @param {Event} e - An event dispatched to edit a comment.
 */
function tryEditComment(e) {
  e.preventDefault();
  let comment = document.getElementById(e.currentTarget.dataset.id);
  let form = createCommentForm(comment);
  addEvent(form, 'submit', editComment);
  comment.style.display = 'none';
  comment.insertAdjacentElement('afterend', form);
  form.focus();
}

/**
 * Removes the form to edit a comment, and replaces the old comment Node with a
 * new one.
 * @param {object} data - A plain json object with the comment ID and the new
 * html in plain text.
 */
function refreshComment(data) {
  let form = document.getElementById(data.id + '-edit');
  let oldComment = form.previousElementSibling;
  let comments = oldComment.parentNode;
  comments.removeChild(form);
  if (!oldComment.previousElementSibling) {
    // This comment is first comment
    comments.removeChild(oldComment);
    comments.insertAdjacentHTML('afterbegin', data.comment);
  } else {
    // This is not the first comment
    let sibling = oldComment.previousElementSibling;
    comments.removeChild(oldComment);
    sibling.insertAdjacentHTML('afterend', data.comment);
  }
  let comment = comments.querySelector('#' + data.id);
  addEvent(comment.querySelector('.delete-comment'), 'click', deleteComment);
  addEvent(comment.querySelector('.edit-comment'), 'click', tryEditComment);
}

/**
 * Removes the form to edit a comment, and re-displays the original comment.
 * @param {Element} form - A DOM form element.
 */
function putCommentBack(form) {
  form.previousElementSibling.style.display = 'block';
  form.parentNode.removeChild(form);
}

/**
 * Sends request to server to edit comment, and replaces old comment with new
 * comment.
 * @param {Event} e - An event from the form to edit a comment.
 */
function editComment(e) {
  e.preventDefault();
  let form = e.currentTarget;
  if (form.action.includes('cancel')) {
    putCommentBack(form);
    return;
  }
  let xhr = new XMLHttpRequest();
  xhr.onreadystatechange = function() {
    if (this.readyState == 4 && this.status == 200) {
      var data = JSON.parse(this.responseText);
      refreshComment(data);
    }
    // TODO: need to revert changes if there is an error
  };
  xhr.open('POST', '/edit-comment', true);
  let msg = JSON.stringify({
    'id': form.dataset.id,
    'text': form.elements.text.value
  });
  xhr.send(msg);
}

/**
 * Sends request to a server to delete a comment, and removes the comment from
 * the blog page.
 * @param {Event} e - An event to delete a comment.
 */
function deleteComment(e) {
  e.preventDefault();
  let xhr = new XMLHttpRequest();
  xhr.onreadystatechange = function() {
    if (this.readyState == 4 && this.status == 200) {
      var data = JSON.parse(this.responseText);
      if (data.id) {
        var el = document.getElementById(data.id);
        el.parentNode.removeChild(el);
      }
    }
  };
  xhr.open('POST', '/delete-comment', true);
  xhr.send(JSON.stringify({'id': e.currentTarget.dataset.id}));
}

/**
 * Sends request to a server to create a comment, and displays the new comment
 * in the blog page.
 * @param {Event} e - An event to create a comment.
 */
function createComment(e) {
  e.preventDefault();
  let xhr = new XMLHttpRequest();
  xhr.onreadystatechange = function() {
    if (this.readyState == 4 && this.status == 200) {
      let data = JSON.parse(this.responseText);
      let comments = document.querySelector('.blog-comments');
      comments.insertAdjacentHTML('beforeend', data.comment);
      document.querySelector('form').reset();
      let lc = comments.lastChild;
      lc.scrollIntoView();
      addEvent(lc.querySelector('.edit-comment'), 'click', tryEditComment);
      addEvent(lc.querySelector('.delete-comment'), 'click', deleteComment);
    }
  };
  let form = document.querySelector('form');
  xhr.open('POST', form.action, true);
  let msg = JSON.stringify({'text': form.elements.text.value});
  xhr.send(msg);
}

/**
 * Sends a request to a server to update the recommendation count for a given
 * blog post.
 * @param {Event} e - An event to like or unlike a blog post.
 */
function clickLike(e) {
  e.preventDefault();
  let xhr = new XMLHttpRequest();
  xhr.onreadystatechange = function() {
    if (this.readyState == 4 && this.status == 200) {
      let response = JSON.parse(this.responseText);
      if (response.add) {
        // Enable red heart and increase likes count
        let cl = document.getElementById('likes-heart').classList;
        cl.add('red-heart');
        cl.remove('normal');
        let el = document.querySelector('.likes-number');
        el.innerText = parseInt(el.innerText, 10) + 1;
      } else if (response.remove) {
        // Disable red heart and decrease likes count
        let cl = document.getElementById('likes-heart').classList;
        cl.remove('red-heart');
        cl.add('normal');
        let el = document.querySelector('.likes-number');
        let count = parseInt(el.innerText, 10);
        // double check that we are not taking number below zero
        if (count) {
          el.innerText = count - 1;
        }
      }
    }
  };
  let href = document.getElementById('like-button').href;
  xhr.open('GET', href, true);
  xhr.send();
}

/**
 * Attach event listeners buttons.
 */
(function() {
  let delLinks = document.querySelectorAll('.delete-comment');
  for (let i = 0; i < delLinks.length; i++) {
    addEvent(delLinks[i], 'click', deleteComment);
  }

  let editLinks = document.querySelectorAll('.edit-comment');
  for (let i = 0; i < editLinks.length; i++) {
    addEvent(editLinks[i], 'click', tryEditComment);
  }

  let form = document.querySelector('form');
  if (form.action.includes('create-comment')) {
    addEvent(form, 'submit', createComment);
  }

  addEvent(document.getElementById('like-button'), 'click', clickLike);
})();
<|endoftext|>


<commit_before>import { pick, omit, defaults, flatten } from "lodash";
import { Helpers, Events } from "victory-core";
import Scale from "../../helpers/scale";
import Domain from "../../helpers/domain";
// import Data from "../../helpers/data";

export default {
  getBaseProps(props, defaultStyles) { // eslint-disable-line max-statements
    const calculatedValues = this.getCalculatedValues(props, defaultStyles);
    const { data, style, scale } = calculatedValues;
    return data.reduce((memo, datum, index) => {
      const eventKey = datum.eventKey;
      const x = scale.x(datum.x);
      const y1 = scale.y(datum.high);
      const y2 = scale.y(datum.low);
      const candleColor = datum.open > datum.close ?
            props.candleColors.negative : props.candleColors.positive;
      const candleHeight = Math.abs(scale.y(datum.open) - scale.y(datum.close));
      const y = scale.y(Math.max(datum.open, datum.close));
      const size = this.getSize(datum, props, calculatedValues);
      const dataStyle = Object.assign(this.getDataStyles(datum, style.data),
        {stroke: candleColor, fill: candleColor});
      const dataProps = {
        x, y, y1, y2, candleColor, candleHeight, size, scale, data, datum,
        index, style: dataStyle, padding: props.padding, width: props.width
      };

      const text = this.getLabelText(props, datum, index);
      const labelStyle = this.getLabelStyle(style.labels, dataProps);
      const labelProps = {
        style: labelStyle,
        x,
        y: y - labelStyle.padding,
        text,
        index,
        scale,
        datum: dataProps.datum,
        textAnchor: labelStyle.textAnchor,
        verticalAnchor: labelStyle.verticalAnchor || "end",
        angle: labelStyle.angle
      };
      memo[eventKey] = {
        data: dataProps,
        labels: labelProps
      };
      return memo;
    }, {});
  },

  getCalculatedValues(props, defaultStyles) {
    const style = Helpers.getStyles(props.style, defaultStyles, "auto", "100%");
    const data = Events.addEventKeys(props, this.getData(props));
    const range = {
      x: Helpers.getRange(props, "x"),
      y: Helpers.getRange(props, "y")
    };
    const domain = {
      x: this.getDomain(props, "x"),
      y: this.getDomain(props, "y")
    };
    const scale = {
      x: Scale.getBaseScale(props, "x").domain(domain.x).range(range.x),
      y: Scale.getBaseScale(props, "y").domain(domain.y).range(range.y)
    };
    console.log(domain.x);
    console.log(domain.y);
    return {data, scale, style};
  },

  getData(props) {
    return props.data.map((datum) => {
      const x = datum.x;
      const y = [datum.open, datum.close, datum.high, datum.low];
      return Object.assign(
        {},
        datum,
        {x, y}
        );
    });
  },

  getDomain(props, axis) {
    const dataset = this.getData(props);
    const allData = flatten(dataset).map((datum) => {
      return datum[axis];
    });
    const flattened = flatten(allData);
    const min = Math.min(...flattened);
    const max = Math.max(...flattened);
    // TODO: is this the correct behavior, or should we just error. How do we
    // handle charts with just one data point?
    if (min === max) {
      const adjustedMax = max === 0 ? 1 : max;
      return [0, adjustedMax];
    }
    return [min, max];
  },

  // getDomainY(props) {
  //   const dataset = this.getData(props);
  //   const allData = flatten(dataset).map((datum) => {
  //     return datum.y;
  //   });
  //   const flattened = flatten(allData);
  //   const min = Math.min(...flattened);
  //   const max = Math.max(...flattened);
  //   if (min === max) {
  //     const adjustedMax = max === 0 ? 1 : max;
  //     return [0, adjustedMax];
  //   }
  //   return [min, max];

  // },

  getDataStyles(datum, style) {
    const stylesFromData = omit(datum, [
      "x", "y", "size", "name", "label", "open", "close", "high", "low"
    ]);
    const baseDataStyle = defaults({}, stylesFromData, style);
    return Helpers.evaluateStyle(baseDataStyle, datum);
  },

  getLabelText(props, datum, index) {
    const propsLabel = Array.isArray(props.labels) ?
      props.labels[index] : Helpers.evaluateProp(props.labels, datum);
    return datum.label || propsLabel;
  },

  getLabelStyle(labelStyle, dataProps) {
    const { datum, size, style } = dataProps;
    const matchedStyle = pick(style, ["opacity", "fill"]);
    const padding = labelStyle.padding || size * 0.25;
    const baseLabelStyle = defaults({}, labelStyle, matchedStyle, {padding});
    return Helpers.evaluateStyle(baseLabelStyle, datum);
  },

  getSize(data, props) {
    let size;
    if (data.size) {
      size = typeof data.size === "function" ? data.size : Math.max(data.size, 1);
    } else if (typeof props.size === "function") {
      size = props.size;
    } else {
      size = Math.max(props.size, 1);
    }
    return Helpers.evaluateProp(size, data);
  }
};
<commit_msg>removing some console log messages<commit_after>import { pick, omit, defaults, flatten } from "lodash";
import { Helpers, Events } from "victory-core";
import Scale from "../../helpers/scale";
import Domain from "../../helpers/domain";
// import Data from "../../helpers/data";

export default {
  getBaseProps(props, defaultStyles) { // eslint-disable-line max-statements
    const calculatedValues = this.getCalculatedValues(props, defaultStyles);
    const { data, style, scale } = calculatedValues;
    return data.reduce((memo, datum, index) => {
      const eventKey = datum.eventKey;
      const x = scale.x(datum.x);
      const y1 = scale.y(datum.high);
      const y2 = scale.y(datum.low);
      const candleColor = datum.open > datum.close ?
            props.candleColors.negative : props.candleColors.positive;
      const candleHeight = Math.abs(scale.y(datum.open) - scale.y(datum.close));
      const y = scale.y(Math.max(datum.open, datum.close));
      const size = this.getSize(datum, props, calculatedValues);
      const dataStyle = Object.assign(this.getDataStyles(datum, style.data),
        {stroke: candleColor, fill: candleColor});
      const dataProps = {
        x, y, y1, y2, candleColor, candleHeight, size, scale, data, datum,
        index, style: dataStyle, padding: props.padding, width: props.width
      };

      const text = this.getLabelText(props, datum, index);
      const labelStyle = this.getLabelStyle(style.labels, dataProps);
      const labelProps = {
        style: labelStyle,
        x,
        y: y - labelStyle.padding,
        text,
        index,
        scale,
        datum: dataProps.datum,
        textAnchor: labelStyle.textAnchor,
        verticalAnchor: labelStyle.verticalAnchor || "end",
        angle: labelStyle.angle
      };
      memo[eventKey] = {
        data: dataProps,
        labels: labelProps
      };
      return memo;
    }, {});
  },

  getCalculatedValues(props, defaultStyles) {
    const style = Helpers.getStyles(props.style, defaultStyles, "auto", "100%");
    const data = Events.addEventKeys(props, this.getData(props));
    const range = {
      x: Helpers.getRange(props, "x"),
      y: Helpers.getRange(props, "y")
    };
    const domain = {
      x: this.getDomain(props, "x"),
      y: this.getDomain(props, "y")
    };
    const scale = {
      x: Scale.getBaseScale(props, "x").domain(domain.x).range(range.x),
      y: Scale.getBaseScale(props, "y").domain(domain.y).range(range.y)
    };
    return {data, scale, style};
  },

  getData(props) {
    return props.data.map((datum) => {
      const x = datum.x;
      const y = [datum.open, datum.close, datum.high, datum.low];
      return Object.assign(
        {},
        datum,
        {x, y}
        );
    });
  },

  getDomain(props, axis) {
    const dataset = this.getData(props);
    const allData = flatten(dataset).map((datum) => {
      return datum[axis];
    });
    const flattened = flatten(allData);
    const min = Math.min(...flattened);
    const max = Math.max(...flattened);
    // TODO: is this the correct behavior, or should we just error. How do we
    // handle charts with just one data point?
    if (min === max) {
      const adjustedMax = max === 0 ? 1 : max;
      return [0, adjustedMax];
    }
    return [min, max];
  },

  getDataStyles(datum, style) {
    const stylesFromData = omit(datum, [
      "x", "y", "size", "name", "label", "open", "close", "high", "low"
    ]);
    const baseDataStyle = defaults({}, stylesFromData, style);
    return Helpers.evaluateStyle(baseDataStyle, datum);
  },

  getLabelText(props, datum, index) {
    const propsLabel = Array.isArray(props.labels) ?
      props.labels[index] : Helpers.evaluateProp(props.labels, datum);
    return datum.label || propsLabel;
  },

  getLabelStyle(labelStyle, dataProps) {
    const { datum, size, style } = dataProps;
    const matchedStyle = pick(style, ["opacity", "fill"]);
    const padding = labelStyle.padding || size * 0.25;
    const baseLabelStyle = defaults({}, labelStyle, matchedStyle, {padding});
    return Helpers.evaluateStyle(baseLabelStyle, datum);
  },

  getSize(data, props) {
    let size;
    if (data.size) {
      size = typeof data.size === "function" ? data.size : Math.max(data.size, 1);
    } else if (typeof props.size === "function") {
      size = props.size;
    } else {
      size = Math.max(props.size, 1);
    }
    return Helpers.evaluateProp(size, data);
  }
};
<|endoftext|>


<commit_before>import React from 'react';
import Select from 'antd/lib/select';
import 'antd/lib/select/style/css';

const InputFilter = props => {

  const {attribute, filter, handleInputChange} = props;
  const Option = Select.Option;
  const options = attribute.map(a=> (
        <Option value={a} key={a}>{a}</Option>
      ));

  const placeholder = attribute.slice(0,3).map(x=>x).join(", ").toLowerCase();
  return (

{filter}

        <Select multiple
                style={{width: "100%"}}
                placeholder={`${placeholder}...`} onChange={(e)=>handleInputChange(e)}>
          {options}
        </Select>

  );
};

export default InputFilter;<commit_msg>Added proptypes<commit_after>import React from 'react';
import Select from 'antd/lib/select';
import 'antd/lib/select/style/css';

const InputFilter = props => {

  const {attribute, filter, handleInputChange} = props;
  const Option = Select.Option;
  const options = attribute.map(a=> (
        <Option value={a} key={a}>{a}</Option>
      ));

  const placeholder = attribute.slice(0,3).map(x=>x).join(", ").toLowerCase();
  return (

{filter}

        <Select multiple
                style={{width: "100%"}}
                placeholder={`${placeholder}...`} onChange={(e)=>handleInputChange(e)}>
          {options}
        </Select>

  );
};

InputFilter.propTypes = {
  attribute: React.PropTypes.array,
  filter: React.PropTypes.string,
  handleInputChange: React.PropTypes.func
};

export default InputFilter;<|endoftext|>


<commit_before>/**
 * presents the mouse events class
 *
 * NOTE: this class generally is for an internal usage, it builds a new clean
 *       unextended mouse event.
 *       Use the Event general constructor, if you need a usual extened event.
 *
 * Copyright (C) 2008-2009 Nikolay V. Nemshilov aka St. <nemshilov#gma-ilc-om>
 */
Event.Mouse = new Class(Event.Base, {

  extend: {
    NAMES: $w('click middleclick rightclick dblclick mousedown mouseup mouseover mouseout mousemove'),

    Methods: {
      isLeftClick: function() {
        return this.which == 1;
      },

      isRightClick : function() {
        return this.which == 3;
      },

      position: function() {
        return {x: this.pageX, y: this.pageY};
      }
    },

    /**
     * proceses the event extending as if it's a mouse event
     *
     * @param Event new event
     * @return Event extended event
     */
    ext: function(event) {
      $ext(event, this.Methods, true);

      if (Browser.IE) {
        // faking the which button so we could work with it in a crossbrowser way
        event.which = event.button == Event.BUTTONS.RIGHT ? 3 : event.button == Event.BUTTONS.MIDDLE ? 2 : 1;

        // fake the mouse position
        var scrolls = window.scrolls();

        event.pageX = event.clientX + scrolls.x;
        event.pageY = event.clientY + scrolls.y;

        // faking the relatedElement
        event.relatedElement = event.type == 'mouseover' ? event.fromEvent :
          event.type == 'mouseout' ? event.toEvent : null;

        // faking the target property
        event['target'] = event.srcElement;
      }

      // Safari bug fix
      if (event.target && event.target.nodeType == 3)
        event.target = event.target.parentNode;

      return event;
    }
  },

  // default mouse events related options
  Options: {
    pointerX: 0,
    pointerY: 0,
    button:   0
  },

// protecteds
  build: function(options) {
    var event = Browser.IE ? this.$super(options) : document.createEvent("MouseEvent");
    this[Browser.IE ? 'initIE' : 'initW3C'](event, options);
    return event;
  },

  options: function(name, options) {
    options = this.$super(name, options);
    options.button = Event.BUTTONS[options.name == 'rightclick' ? 'RIGHT' : options.name == 'middleclick' ? 'MIDDLE' : 'LEFT'];
    options.name   = Event.realName(options.name);

    return options;
  },

// private
  initIE: function(event, options) {
    event.clientX = options.pointerX;
    event.clientY = options.pointerY;
    event.button  = options.button;
  },

  initW3C: function(event, options) {
    event.initMouseEvent(options.name, options.bubbles, options.cancelable, document.defaultView,
      name == 'dblclick' ? 2 : 1, options.pointerX, options.pointerY, options.pointerX, options.pointerY,
      options.ctrlKey, options.altKey, options.shiftKey, options.metaKey, options.button, options.element
    );
  }
});<commit_msg>nothing<commit_after>/**
 * presents the mouse events class
 *
 * NOTE: this class generally is for an internal usage, it builds a new clean
 *       unextended mouse event.
 *       Use the Event general constructor, if you need a usual extened event.
 *
 * Copyright (C) 2008-2009 Nikolay V. Nemshilov aka St. <nemshilov#gma-ilc-om>
 */
Event.Mouse = new Class(Event.Base, {

  extend: {
    NAMES: $w('click middleclick rightclick dblclick mousedown mouseup mouseover mouseout mousemove'),

    Methods: {
      isLeftClick: function() {
        return this.which == 1;
      },

      isRightClick : function() {
        return this.which == 3;
      },

      position: function() {
        return {x: this.pageX, y: this.pageY};
      }
    },

    /**
     * proceses the event extending as if it's a mouse event
     *
     * @param Event new event
     * @return Event extended event
     */
    ext: function(event) {
      $ext(event, this.Methods, true);

      if (Browser.IE) {
        // faking the which button so we could work with it in a crossbrowser way
        event.which = event.button == Event.BUTTONS.RIGHT ? 3 : event.button == Event.BUTTONS.MIDDLE ? 2 : 1;

        // fake the mouse position
        var scrolls = window.scrolls();

        event.pageX = event.clientX + scrolls.x;
        event.pageY = event.clientY + scrolls.y;

        // faking the relatedElement
        event.relatedElement = event.type == 'mouseover' ? event.fromEvent :
          event.type == 'mouseout' ? event.toEvent : null;

        // faking the target property
        event.target = event.srcElement;
      }

      // Safari bug fix
      if (event.target && event.target.nodeType == 3)
        event.target = event.target.parentNode;

      return event;
    }
  },

  // default mouse events related options
  Options: {
    pointerX: 0,
    pointerY: 0,
    button:   0
  },

// protecteds
  build: function(options) {
    var event = Browser.IE ? this.$super(options) : document.createEvent("MouseEvent");
    this[Browser.IE ? 'initIE' : 'initW3C'](event, options);
    return event;
  },

  options: function(name, options) {
    options = this.$super(name, options);
    options.button = Event.BUTTONS[options.name == 'rightclick' ? 'RIGHT' : options.name == 'middleclick' ? 'MIDDLE' : 'LEFT'];
    options.name   = Event.realName(options.name);

    return options;
  },

// private
  initIE: function(event, options) {
    event.clientX = options.pointerX;
    event.clientY = options.pointerY;
    event.button  = options.button;
  },

  initW3C: function(event, options) {
    event.initMouseEvent(options.name, options.bubbles, options.cancelable, document.defaultView,
      name == 'dblclick' ? 2 : 1, options.pointerX, options.pointerY, options.pointerX, options.pointerY,
      options.ctrlKey, options.altKey, options.shiftKey, options.metaKey, options.button, options.element
    );
  }
});<|endoftext|>


<commit_before>/* =============================================================

	Buoy v1.2
	A simple vanilla JS micro-library by Chris Ferdinandi.
	http://gomakethings.com

	Class handlers by Todd Motto.
	https://github.com/toddmotto/apollo

	Module pattern by Keith Rousseau.
	https://twitter.com/keithtri

	Free to use under the MIT License.
	http://gomakethings.com/mit/

 * ============================================================= */

window.buoy = (function (window, document, undefined) {

	'use strict';

	// Check for classList support
	var classList = document.documentElement.classList;

	// Check if an element has a class
	var hasClass = function (elem, className) {
		if ( classList ) {
			return elem.classList.contains(className);
		} else {
			return new RegExp('(^|\\s)' + className + '(\\s|$)').test(elem.className);
		}
	};

	// Add a class to an element
	var addClass = function (elem, className) {
		if ( !hasClass(elem, className) ) {
			if ( classList ) {
				elem.classList.add(className);
			} else {
				elem.className += (elem.className ? ' ' : '') + className;
			}
		}
	};

	// Remove a class from an element
	var removeClass = function (elem, className) {
		if (hasClass(elem, className)) {
			if ( classList ) {
				elem.classList.remove(className);
			} else {
				elem.className = elem.className.replace(new RegExp('(^|\\s)*' + className + '(\\s|$)*', 'g'), '');
			}
		}
	};

	// Toggle a class on an element
	var toggleClass = function (elem, className) {
		if ( classList ) {
			elem.classList.toggle(className);
		} else {
			if ( hasClass(elem, className ) ) {
				removeClass(elem, className);
			}
			else {
				addClass(elem, className);
			}
		}
	};

	// Get siblings of an element
	var getSiblings = function (elem) {
		var siblings = [];
		var sibling = elem.parentNode.firstChild;
		var skipMe = elem;
		for ( ; sibling; sibling = sibling.nextSibling ) {
			if ( sibling.nodeType == 1 && sibling != elem ) {
				siblings.push( sibling );
			}
		}
		return siblings;
	};

	// Return functions
	return {
		toggleClass: toggleClass,
		removeClass: removeClass,
		addClass: addClass,
		hasClass: hasClass,
		getSiblings: getSiblings
	};

})(window, document);<commit_msg>Updated buoy<commit_after>/* =============================================================

	Buoy v1.2
	A simple vanilla JS micro-library by Chris Ferdinandi.
	http://gomakethings.com

	Class handlers by Todd Motto.
	https://github.com/toddmotto/apollo

	Module pattern by Keith Rousseau.
	https://twitter.com/keithtri

	Free to use under the MIT License.
	http://gomakethings.com/mit/

 * ============================================================= */

window.buoy = (function (window, document, undefined) {

	'use strict';

	// Check for classList support
	// Returns Object
	var classList = document.documentElement.classList;

	// Check if an element has a class
	// Public method
	// Boolean: Returns true if element has class
	var hasClass = function (elem, className) {
		if ( classList ) {
			return elem.classList.contains(className);
		} else {
			return new RegExp('(^|\\s)' + className + '(\\s|$)').test(elem.className);
		}
	};

	// Add a class to an element
	// Public method
	// Runs function
	var addClass = function (elem, className) {
		if ( !hasClass(elem, className) ) {
			if ( classList ) {
				elem.classList.add(className);
			} else {
				elem.className += (elem.className ? ' ' : '') + className;
			}
		}
	};

	// Remove a class from an element
	// Public method
	// Runs function
	var removeClass = function (elem, className) {
		if (hasClass(elem, className)) {
			if ( classList ) {
				elem.classList.remove(className);
			} else {
				elem.className = elem.className.replace(new RegExp('(^|\\s)*' + className + '(\\s|$)*', 'g'), '');
			}
		}
	};

	// Toggle a class on an element
	// Public method
	// Runs function
	var toggleClass = function (elem, className) {
		if ( classList ) {
			elem.classList.toggle(className);
		} else {
			if ( hasClass(elem, className ) ) {
				removeClass(elem, className);
			}
			else {
				addClass(elem, className);
			}
		}
	};

	// Get siblings of an element
	// Public method
	// Returns array of nodes
	var getSiblings = function (elem) {
		var siblings = [];
		var sibling = elem.parentNode.firstChild;
		var skipMe = elem;
		for ( ; sibling; sibling = sibling.nextSibling ) {
			if ( sibling.nodeType == 1 && sibling != elem ) {
				siblings.push( sibling );
			}
		}
		return siblings;
	};

	// Return public functions
	return {
		toggleClass: toggleClass,
		removeClass: removeClass,
		addClass: addClass,
		hasClass: hasClass,
		getSiblings: getSiblings
	};

})(window, document);<|endoftext|>