<commit_before>---
---
$(document).ready(function() {
  'use strict';
  // BROWSER CHECKS
  // =======================================================
  // TODO Need to refactor initial browser checks - this is a mess!
  const isIE11 = !!window.MSInputMethodContext && !!document.documentMode; // Note: does not detect <IE11
  const isMobile = window.matchMedia('only screen and (max-width: 992px)');
  const hasAlgolia = $('#grants .card-panel-header .search');
  let isSupported = browserTest();
  let allowsCookies = cookieTest();
  let allowsLocalStorage = storageTest();

  // Load Vue if supported
  const vue = document.createElement('script');
  // TODO Load bundle.js upon click
  vue.src = '{{ site.baseurl }}/assets/js/bundle.js?v={{ site.time | date: "%Y%m%d"}}';
  if (!isIE11 && !isMobile.matches && allowsCookies && allowsLocalStorage && isSupported) {
    // document.body.appendChild(vue);
  } else {
    $('.js-vue-check').addClass( 'hidden' ); // Hide UI elements created in DOM, but handled by Vue
  }

  // Show message if not supported
  if (isIE11 || !isSupported) {
    const toastContent = 'Your browser is currently not supported.
Many useful features may not work.<button href="https://outdatedbrowser.com/en" class="btn-flat yellow-text toast-action-browser-suggestion">Browser Suggestions</button>';
    M.Toast.dismissAll();
    M.toast({
      'html': toastContent,
      'displayLength': 10000,
    });
    $('.toast-action-browser-suggestion').on('click', function() {
      const target = $(this).attr('href');
      window.location.href = target;
    });
    // Hide Algolia elements
    $('.js-ie-check').addClass( 'hidden' );
  }

  function browserTest() {
    const parent = document.createElement('div');
    const el = document.createElement('span');
    try {
      parent.prepend(el); // Using ParentNode.prepend() as proxy for supported browsers
      return true;
    } catch (e) {
      return false;
    }
  }

  function cookieTest() {
    let cookieEnabled = navigator.cookieEnabled;
    if (!cookieEnabled) {
      document.cookie = 'testcookie';
      cookieEnabled = document.cookie.indexOf('testcookie') !== -1;
      document.cookie = 'testcookie; expires=Thu, 01-Jan-1970 00:00:01 GMT';
    }
    return cookieEnabled || showCookieFail();
  }

  function showCookieFail() {
    if (!isIE11 && isSupported) {
      M.toast({
        'html': 'Enable cookies to view available profile updates',
      });
    }
    return false;
  }

  function storageTest() {
    const test = 'test';
    try {
      localStorage.setItem(test, test);
      localStorage.removeItem(test);
      return true;
    } catch (e) {
      return false;
    }
  }

  // CHART.JS
  // =======================================================
  // Lazy load via Intersection Observer if browser allows
  // Jekyll sets a global variable orgFinancialsStats in profile.html
  const orgCurrentTaxYear = document.querySelector('h1.org-name').dataset.taxYear;
  const chartWrapperOverview = document.getElementById('financial-overview').getElementsByClassName('chart-wrapper')[0];
  const chartWrapperTrends = document.getElementById('financial-trends').getElementsByClassName('chart-wrapper')[0];

  if (!isMobile.matches && 'IntersectionObserver' in window) {
    gaChartsEvents('Charts baseline');
    createChartsObserver();
  }

  function createChartsObserver() {
    let observer;
    let anchor = document.getElementById('financial-overview');
    let config = {
      rootMargin: '0px 0px',
      threshold: 0.01,
    };
    // Initiate observer using Financial Overview section as anchor
    observer = new IntersectionObserver(enableCharts, config);
    observer.observe(anchor);
    // Initiate preloaders / spinners
    // Only need to show Trends chart if more than one tax year is available
    showLoader(chartWrapperOverview);
    if (orgFinancialStats.length > 1) {
      showLoader(chartWrapperTrends);
    }
  }

  function enableCharts(entries, observer) {
    entries.forEach((entry) => {
      if (entry.isIntersecting) {
        gaChartsEvents('Visitor reached financial divs');
        // Run the function that fetches Chart.js and loads it into the DOM
        loadCharts();
        observer.unobserve(entry.target);
      }
    });
  }

  function loadCharts() {
    gaChartsEvents('Loading ChartJS');
    const chartJS = document.createElement('script');
    chartJS.type = 'text/javascript';
    chartJS.src = 'https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.9.3/Chart.min.js';
    // Make Chart.js script available on client
    document.body.appendChild(chartJS);
    // Now that Chart.js script is available, create the charts
    createCharts(chartJS);
  }

  function createCharts(init) {
    const chartsColorPrimary = '#607d8b';
    const chartsColorSecondary = '#c54e00';
    const chartsColorTertiary = '#00bfa5';

    const assets = orgFinancialStats.map(value => value.assets).reverse();
    const distributions = orgFinancialStats.map(value => value.distributions).reverse();
    const contributions = orgFinancialStats.map(value => value.contributions).reverse();
    const years = orgFinancialStats.map(value => value.tax_year).reverse();
    const year1 = orgFinancialStats[0];

    const ctx = document.getElementById('chart-overview').getContext('2d');
    const ctxTrends = document.getElementById('chart-trends').getContext('2d');

    init.onload = function() {
      gaChartsEvents('ChartsJS successfully loaded');
      const chartsFinancialOverview = new Chart(ctx, { /* eslint-disable-line no-unused-vars */
        type: 'horizontalBar',
        responsive: true,
        data: {
          labels: ['Assets EOY', 'Distributions', 'Contributions' ],
          datasets: [{
            data: [
              year1.assets,
              year1.distributions,
              year1.contributions,
            ],
            backgroundColor: [
              chartsColorPrimary,
              chartsColorSecondary,
              chartsColorTertiary,
            ],
          }],
        },
        options: {
          title: {
            display: true,
            text: `Latest Electronic Tax Filing (${orgCurrentTaxYear})`,
          },
          legend: {
            display: false,
          },
          scales: {
            xAxes: [{
              ticks: {
                beginAtZero: true,
                callback: function(value) {
                  return `$${numberHuman(value, 0)}`;
                },
              },
            }],
            yAxes: [ {
              gridLines: {
                display: false,
              },
            }],
          },
          tooltips: {
            callbacks: {
              label: function(tooltipItem) {
                return new Intl.NumberFormat('en-US', {
                  style: 'currency',
                  currency: 'USD',
                  minimumFractionDigits: 0,
                }).format(tooltipItem.value);
              },
            },
          },
          animation: {
            onProgress: function(animation) {
              if (animation.currentStep === 1) {
                hideLoader(chartWrapperOverview);
              }
            },
            onComplete: function() {
              gaChartsEvents('Charts successfully drawn');
            },
          },
        },
      });
      // Only need to show Trends chart if more than one tax year is available
      if (orgFinancialStats.length > 1) {
        const chartsFinancialTrends = new Chart(ctxTrends, { /* eslint-disable-line no-unused-vars */
          type: 'bar',
          responsive: true,
          data: {
            labels: years,
            datasets: [
              {
                label: 'Assets EOY',
                borderColor: chartsColorPrimary,
                data: assets,
                type: 'line',
                fill: false,
              },
              {
                label: 'Distributions',
                backgroundColor: chartsColorSecondary,
                data: distributions,
              },
              {
                label: 'Contributions',
                backgroundColor: chartsColorTertiary,
                data: contributions,
              },
            ],
          },
          options: {
            title: {
              display: true,
              text: 'All Available Electronic Tax Returns',
            },
            legend: {
              display: true,
            },
            scales: {
              yAxes: [{
                ticks: {
                  beginAtZero: true,
                  callback: function(value) {
                    return `$${numberHuman(value, 0)}`;
                  },
                },
              }],
              xAxes: [ {
                gridLines: {
                  display: false,
                },
              }],
            },
            tooltips: {
              callbacks: {
                label: function(tooltipItem) {
                  return new Intl.NumberFormat('en-US', {
                    style: 'currency',
                    currency: 'USD',
                    minimumFractionDigits: 0,
                  }).format(tooltipItem.value);
                },
              },
            },
            animation: {
              onProgress: function(animation) {
                if (animation.currentStep === 1) {
                  hideLoader(chartWrapperTrends);
                }
              },
            },
          },
        });
      }
    };
  }

  function showLoader(el) {
    el.classList.remove('default');
    el.classList.add('loading');
  }

  function hideLoader(el) {
    el.classList.remove('loading');
    el.classList.add('loaded');
  }

  function numberHuman(num, decimals) {
    if (num === null) { return null; } // terminate early
    if (num === 0) { return '0'; } // terminate early
    if (isNaN(num)) { return num; } // terminate early if already a string - handles edge case likely caused by caching
    const fixed = !decimals || decimals < 0 ? 0 : decimals; // number of decimal places to show
    const b = num.toPrecision(2).split('e'); // get power
    const k = b.length === 1 ? 0 : Math.floor(Math.min(b[1].slice(1), 14) / 3); // floor at decimals, ceiling at trillions
    const c = k < 1 ? num.toFixed(0 + fixed) : (num / Math.pow(10, k * 3) ).toFixed(1 + fixed); // divide by power
    const d = c < 0 ? c : Math.abs(c); // enforce -0 is 0
    const e = d + ['', 'K', 'M', 'B', 'T'][k]; // append power
    return e;
  }

  function gaChartsEvents(label) {
    let gaCheck = window[window['GoogleAnalyticsObject'] || 'ga']; // eslint-disable-line dot-notation
    if (typeof gaCheck === 'function') {
      ga('send', 'event', {
        'eventCategory': 'Profile Events',
        'eventAction': 'Charts Loading Sequence',
        'eventLabel': label,
      });
    }
    // Charts needed
    // Charts downloaded
    // Charts embedded
  }

  // NAVBAR
  // =======================================================
  const header = $('.header');
  const navbar = $('.navbar-profile');
  const range = 64; // Height of navbar

  // Set header opacity on page load
  setHeaderOpacity();

  // Adjust opacity after scrolling
  $(window).on('scroll', function() {
    setHeaderOpacity();
  });

  function setHeaderOpacity() {
    let scrollTop = $(window).scrollTop();
    let height = header.outerHeight();
    let offset = height / 2;
    let calc = 1 - (scrollTop - offset + range) / range;
    header.css({ 'opacity': calc });

    if (calc > '1') {
      header.css({ 'opacity': 1 });
      navbar.addClass('affix-top');
      navbar.removeClass('affix');
    } else if ( calc < '0' ) {
      header.css({ 'opacity': 0 });
      navbar.addClass('affix');
      navbar.removeClass('affix-top');
    }
  }

  // INIT MATERIALIZE COMPONENTS
  // =======================================================
  window.onload = function() {
    const elemsNavMore = document.getElementById('primary-navbar-dropdown-trigger');
    const optionsNavMore = {
      'container': 'main-nav',
      'constrainWidth': false,
    };
    M.Dropdown.init(elemsNavMore, optionsNavMore);
    $('.sidenav').sidenav();
    $('#community-sidebar').sidenav({ 'edge': 'right'});
    $('.tooltipped:not(.v-tooltipped)').tooltip(); // :not ensures Vue handles relevant initiation for Vue-controlled elements
    $('.collapsible').collapsible({
      'accordion': false,
    });

    $('.collapsible-grants-table').collapsible({
      'accordion': false,
      // TODO Use onOpenStart to add spinning icon
      // onOpenStart: function(el) {
      //   $(el).find('.collapsible-header i').addClass('md-spin');
      // }
      'onOpenEnd': function(el) {
        $(el).find('.collapsible-header i').removeClass('md-spin');
      },
    });

    // TODO Use onOpenStart to add spinning icon
    // Unsure of root cause - possible Materialize bug?
    $('.collapsible-grants-table .collapsible-header').click(function(e) {
      if (!$(this).parent().hasClass('active')) {
        e.stopPropagation();
        $(this).find('i').addClass('md-spin');
        $(this).find('i').removeClass('bounce');
        setTimeout(function() {
          $('.collapsible-grants-table').collapsible('open');
        }, 100);
      }
    });
  };

  // FIXED HEADERS
  // =======================================================
  // Grants header is fixed only on non-mobile devices with Algolia enabled
  // See also search.js - Need to re-init grants header after search results populate to capture proper div height

  function enableGrantsFixedHeader() {
    const grantsHeader = $('#grants .card-panel-header');
    grantsHeader.addClass('pushpin-nav pushpin-nav-search');
    grantsHeader.attr('data-target', 'grants');
  }

  if (!isMobile.matches && hasAlgolia.length && !isIE11) {
    enableGrantsFixedHeader();
  }

  if ($('.pushpin-nav').length) {
    $('.pushpin-nav').each(function() {
      let $this = $(this);
      let $id = $(this).attr('data-target');
      let $target = $('#' + $(this).attr('data-target'));
      let targetBottom = $target.offset().top + $target.height();
      let targetOffset = 0;
      if ($id === 'main-nav') {
        targetBottom = Infinity;
      } else {
        targetOffset = range;
      }
      $this.pushpin({
        'top': $target.offset().top,
        'bottom': targetBottom,
        'offset': targetOffset,
      });
    });
  }

  // SMOOTH SCROLL
  // =======================================================
  $('.scrolly').click(function(e) {
    e.preventDefault();
    const target = $(this).attr('href');
    const newPosition = $(target).offset().top - 64;
    $('html, body').stop().animate({ 'scrollTop': newPosition }, 500);
  });

  $('.nav-primary li a.scrolly').on('click', function() {
    // collapse mobile header
    if (isMobile.matches) {
      $('.sidenav').sidenav('close');
    }
  });

  // FILINGS
  // =======================================================
  // Add filing links
  $('.js-filings-pdf').each(function() {
    addFilingURL($(this));
  });

  function addFilingURL(el) {
    const ein = el.data('ein');
    const einShort = ein.toString().substring(0, 3);
    const taxPeriod = el.data('tax-period');
    // Foundation Center: http://990s.foundationcenter.org/990pf_pdf_archive/272/272624875/272624875_201412_990PF.pdf
    const urlPDF = 'http://990s.foundationcenter.org/990pf_pdf_archive/' +
                 einShort + '/' +
                 ein + '/' +
                 ein + '_' +
                 taxPeriod + '_990PF.pdf';
    el.attr('data-url-pdf', urlPDF);
    el.attr('href', urlPDF);
  }

  // Check if filing is available
  const gcf = 'https://us-central1-infinite-badge-163220.cloudfunctions.net/checkUrl';

  $('.js-filings-pdf').click(function(e) {
    e.preventDefault();
    M.toast({
      'html': 'Redirecting to latest 990...',
    });
    const elem = $(this);
    const target = $(this).attr('href');
    $.ajax({
      'method': 'POST',
      'url': gcf,
      'data': { 'target': target },
    })
      .done(function( res ) {
        if (res) {
          window.location.href = target;
        } else {
          elem.addClass('disabled');
          M.Toast.dismissAll();
          M.toast({
            'html': 'PDF not yet available. Try a prior year.',
          });
        }
      })
      .fail(function(xhr, textStatus, error) { // eslint-disable-line no-unused-vars
        const toastContent = 'Something went wrong.<button href="http://foundationcenter.org/find-funding/990-finder" class="btn-flat toast-action">Try Here.</button>';
        M.Toast.dismissAll();
        M.toast({
          'html': toastContent,
          'displayLength': 10000,
        });
      });
  });
});
<commit_msg>Minor JS cleanup<commit_after>---
---
$(document).ready(function() {
  'use strict';
  // BROWSER CHECKS
  // =======================================================
  // TODO Need to refactor initial browser checks - this is a mess!
  const isIE11 = !!window.MSInputMethodContext && !!document.documentMode; // Note: does not detect <IE11
  const isMobile = window.matchMedia('only screen and (max-width: 992px)');
  const hasAlgolia = $('#grants .card-panel-header .search');
  let isSupported = browserTest();
  let allowsCookies = cookieTest();
  let allowsLocalStorage = storageTest();

  // Load Vue if supported
  const vue = document.createElement('script');
  // TODO Load bundle.js upon click
  vue.src = '{{ site.baseurl }}/assets/js/bundle.js?v={{ site.time | date: "%Y%m%d"}}';
  if (!isIE11 && !isMobile.matches && allowsCookies && allowsLocalStorage && isSupported) {
    // document.body.appendChild(vue);
  } else {
    $('.js-vue-check').addClass( 'hidden' ); // Hide UI elements created in DOM, but handled by Vue
  }

  // Show message if not supported
  if (isIE11 || !isSupported) {
    const toastContent = 'Your browser is currently not supported.
Many useful features may not work.<button href="https://outdatedbrowser.com/en" class="btn-flat yellow-text toast-action-browser-suggestion">Browser Suggestions</button>';
    M.Toast.dismissAll();
    M.toast({
      'html': toastContent,
      'displayLength': 10000,
    });
    $('.toast-action-browser-suggestion').on('click', function() {
      const target = $(this).attr('href');
      window.location.href = target;
    });
    // Hide Algolia elements
    $('.js-ie-check').addClass( 'hidden' );
  }

  function browserTest() {
    const parent = document.createElement('div');
    const el = document.createElement('span');
    try {
      parent.prepend(el); // Using ParentNode.prepend() as proxy for supported browsers
      return true;
    } catch (e) {
      return false;
    }
  }

  function cookieTest() {
    let cookieEnabled = navigator.cookieEnabled;
    if (!cookieEnabled) {
      document.cookie = 'testcookie';
      cookieEnabled = document.cookie.indexOf('testcookie') !== -1;
      document.cookie = 'testcookie; expires=Thu, 01-Jan-1970 00:00:01 GMT';
    }
    return cookieEnabled || showCookieFail();
  }

  function showCookieFail() {
    if (!isIE11 && isSupported) {
      M.toast({
        'html': 'Enable cookies to view available profile updates',
      });
    }
    return false;
  }

  function storageTest() {
    const test = 'test';
    try {
      localStorage.setItem(test, test);
      localStorage.removeItem(test);
      return true;
    } catch (e) {
      return false;
    }
  }

  // CHART.JS
  // =======================================================
  // Lazy load via Intersection Observer if browser allows
  // Jekyll sets a global variable orgFinancialsStats in profile.html
  const orgCurrentTaxYear = document.querySelector('h1.org-name').dataset.taxYear;
  const chartWrapperOverview = document.getElementById('financial-overview').getElementsByClassName('chart-wrapper')[0];
  const chartWrapperTrends = document.getElementById('financial-trends').getElementsByClassName('chart-wrapper')[0];

  if (!isMobile.matches && 'IntersectionObserver' in window) {
    gaChartsEvents('Charts baseline');
    createChartsObserver();
  }

  function createChartsObserver() {
    let observer;
    let anchor = document.getElementById('financial-overview');
    let config = {
      rootMargin: '0px 0px',
      threshold: 0.01,
    };
    // Initiate observer using Financial Overview section as anchor
    observer = new IntersectionObserver(enableCharts, config);
    observer.observe(anchor);
    // Initiate preloaders / spinners
    // Only need to show Trends chart if more than one tax year is available
    showLoader(chartWrapperOverview);
    if (orgFinancialStats.length > 1) {
      showLoader(chartWrapperTrends);
    }
  }

  function enableCharts(entries, observer) {
    entries.forEach((entry) => {
      if (entry.isIntersecting) {
        gaChartsEvents('Visitor reached financial divs');
        // Run the function that fetches Chart.js and loads it into the DOM
        loadCharts();
        observer.unobserve(entry.target);
      }
    });
  }

  function loadCharts() {
    gaChartsEvents('Loading ChartJS');
    const chartJS = document.createElement('script');
    chartJS.type = 'text/javascript';
    chartJS.src = 'https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.9.3/Chart.min.js';
    // Make Chart.js script available on client
    document.body.appendChild(chartJS);
    // Now that Chart.js script is available, create the charts
    createCharts(chartJS);
  }

  function createCharts(init) {
    const chartsColorPrimary = '#607d8b';
    const chartsColorSecondary = '#c54e00';
    const chartsColorTertiary = '#00bfa5';

    const assets = orgFinancialStats.map(value => value.assets).reverse();
    const distributions = orgFinancialStats.map(value => value.distributions).reverse();
    const contributions = orgFinancialStats.map(value => value.contributions).reverse();
    const years = orgFinancialStats.map(value => value.tax_year).reverse();
    const year1 = orgFinancialStats[0];

    const ctx = document.getElementById('chart-overview').getContext('2d');
    const ctxTrends = document.getElementById('chart-trends').getContext('2d');

    init.onload = function() {
      gaChartsEvents('ChartsJS successfully loaded');
      const chartsFinancialOverview = new Chart(ctx, { /* eslint-disable-line no-unused-vars */
        type: 'horizontalBar',
        responsive: true,
        data: {
          labels: ['Assets EOY', 'Distributions', 'Contributions' ],
          datasets: [{
            data: [
              year1.assets,
              year1.distributions,
              year1.contributions,
            ],
            backgroundColor: [
              chartsColorPrimary,
              chartsColorSecondary,
              chartsColorTertiary,
            ],
          }],
        },
        options: {
          title: {
            display: true,
            text: `Latest Electronic Tax Filing (${orgCurrentTaxYear})`,
          },
          legend: {
            display: false,
          },
          scales: {
            xAxes: [{
              ticks: {
                beginAtZero: true,
                callback: function(value) {
                  return `$${numberHuman(value, 0)}`;
                },
              },
            }],
            yAxes: [ {
              gridLines: {
                display: false,
              },
            }],
          },
          tooltips: {
            callbacks: {
              label: function(tooltipItem) {
                return new Intl.NumberFormat('en-US', {
                  style: 'currency',
                  currency: 'USD',
                  minimumFractionDigits: 0,
                }).format(tooltipItem.value);
              },
            },
          },
          animation: {
            onProgress: function(animation) {
              if (animation.currentStep === 1) {
                hideLoader(chartWrapperOverview);
              }
            },
            onComplete: function() {
              gaChartsEvents('Charts successfully drawn');
            },
          },
        },
      });
      // Only need to show Trends chart if more than one tax year is available
      if (orgFinancialStats.length > 1) {
        const chartsFinancialTrends = new Chart(ctxTrends, { /* eslint-disable-line no-unused-vars */
          type: 'bar',
          responsive: true,
          data: {
            labels: years,
            datasets: [
              {
                label: 'Assets EOY',
                borderColor: chartsColorPrimary,
                data: assets,
                type: 'line',
                fill: false,
              },
              {
                label: 'Distributions',
                backgroundColor: chartsColorSecondary,
                data: distributions,
              },
              {
                label: 'Contributions',
                backgroundColor: chartsColorTertiary,
                data: contributions,
              },
            ],
          },
          options: {
            title: {
              display: true,
              text: 'All Available Electronic Tax Returns',
            },
            legend: {
              display: true,
            },
            scales: {
              yAxes: [{
                ticks: {
                  beginAtZero: true,
                  callback: function(value) {
                    return `$${numberHuman(value, 0)}`;
                  },
                },
              }],
              xAxes: [ {
                gridLines: {
                  display: false,
                },
              }],
            },
            tooltips: {
              callbacks: {
                label: function(tooltipItem) {
                  return new Intl.NumberFormat('en-US', {
                    style: 'currency',
                    currency: 'USD',
                    minimumFractionDigits: 0,
                  }).format(tooltipItem.value);
                },
              },
            },
            animation: {
              onProgress: function(animation) {
                if (animation.currentStep === 1) {
                  hideLoader(chartWrapperTrends);
                }
              },
            },
          },
        });
      }
    };
  }

  function showLoader(el) {
    el.classList.remove('default');
    el.classList.add('loading');
  }

  function hideLoader(el) {
    el.classList.remove('loading');
    el.classList.add('loaded');
  }

  function numberHuman(num, decimals) {
    if (num === null) { return null; } // terminate early
    if (num === 0) { return '0'; } // terminate early
    if (isNaN(num)) { return num; } // terminate early if already a string - handles edge case likely caused by caching
    const fixed = !decimals || decimals < 0 ? 0 : decimals; // number of decimal places to show
    const b = num.toPrecision(2).split('e'); // get power
    const k = b.length === 1 ? 0 : Math.floor(Math.min(b[1].slice(1), 14) / 3); // floor at decimals, ceiling at trillions
    const c = k < 1 ? num.toFixed(0 + fixed) : (num / Math.pow(10, k * 3) ).toFixed(1 + fixed); // divide by power
    const d = c < 0 ? c : Math.abs(c); // enforce -0 is 0
    const e = d + ['', 'K', 'M', 'B', 'T'][k]; // append power
    return e;
  }

  function gaChartsEvents(label) {
    let gaCheck = window[window['GoogleAnalyticsObject'] || 'ga']; // eslint-disable-line dot-notation
    if (typeof gaCheck === 'function') {
      ga('send', 'event', {
        'eventCategory': 'Profile Events',
        'eventAction': 'Charts Loading Sequence',
        'eventLabel': label,
      });
    }
  }

  // NAVBAR
  // =======================================================
  const header = $('.header');
  const navbar = $('.navbar-profile');
  const range = 64; // Height of navbar

  // Set header opacity on page load
  setHeaderOpacity();

  // Adjust opacity after scrolling
  $(window).on('scroll', function() {
    setHeaderOpacity();
  });

  function setHeaderOpacity() {
    let scrollTop = $(window).scrollTop();
    let height = header.outerHeight();
    let offset = height / 2;
    let calc = 1 - (scrollTop - offset + range) / range;
    header.css({ 'opacity': calc });

    if (calc > '1') {
      header.css({ 'opacity': 1 });
      navbar.addClass('affix-top');
      navbar.removeClass('affix');
    } else if ( calc < '0' ) {
      header.css({ 'opacity': 0 });
      navbar.addClass('affix');
      navbar.removeClass('affix-top');
    }
  }

  // INIT MATERIALIZE COMPONENTS
  // =======================================================
  window.onload = function() {
    const elemsNavMore = document.getElementById('primary-navbar-dropdown-trigger');
    const optionsNavMore = {
      'container': 'main-nav',
      'constrainWidth': false,
    };
    M.Dropdown.init(elemsNavMore, optionsNavMore);
    $('.sidenav').sidenav();
    $('#community-sidebar').sidenav({ 'edge': 'right'});
    $('.tooltipped:not(.v-tooltipped)').tooltip(); // :not ensures Vue handles relevant initiation for Vue-controlled elements
    $('.collapsible').collapsible({
      'accordion': false,
    });

    $('.collapsible-grants-table').collapsible({
      'accordion': false,
      // TODO Use onOpenStart to add spinning icon
      // onOpenStart: function(el) {
      //   $(el).find('.collapsible-header i').addClass('md-spin');
      // }
      'onOpenEnd': function(el) {
        $(el).find('.collapsible-header i').removeClass('md-spin');
      },
    });

    // TODO Use onOpenStart to add spinning icon
    // Unsure of root cause - possible Materialize bug?
    $('.collapsible-grants-table .collapsible-header').click(function(e) {
      if (!$(this).parent().hasClass('active')) {
        e.stopPropagation();
        $(this).find('i').addClass('md-spin');
        $(this).find('i').removeClass('bounce');
        setTimeout(function() {
          $('.collapsible-grants-table').collapsible('open');
        }, 100);
      }
    });
  };

  // FIXED HEADERS
  // =======================================================
  // Grants header is fixed only on non-mobile devices with Algolia enabled
  // See also search.js - Need to re-init grants header after search results populate to capture proper div height

  function enableGrantsFixedHeader() {
    const grantsHeader = $('#grants .card-panel-header');
    grantsHeader.addClass('pushpin-nav pushpin-nav-search');
    grantsHeader.attr('data-target', 'grants');
  }

  if (!isMobile.matches && hasAlgolia.length && !isIE11) {
    enableGrantsFixedHeader();
  }

  if ($('.pushpin-nav').length) {
    $('.pushpin-nav').each(function() {
      let $this = $(this);
      let $id = $(this).attr('data-target');
      let $target = $('#' + $(this).attr('data-target'));
      let targetBottom = $target.offset().top + $target.height();
      let targetOffset = 0;
      if ($id === 'main-nav') {
        targetBottom = Infinity;
      } else {
        targetOffset = range;
      }
      $this.pushpin({
        'top': $target.offset().top,
        'bottom': targetBottom,
        'offset': targetOffset,
      });
    });
  }

  // SMOOTH SCROLL
  // =======================================================
  $('.scrolly').click(function(e) {
    e.preventDefault();
    const target = $(this).attr('href');
    const newPosition = $(target).offset().top - 64;
    $('html, body').stop().animate({ 'scrollTop': newPosition }, 500);
  });

  $('.nav-primary li a.scrolly').on('click', function() {
    // collapse mobile header
    if (isMobile.matches) {
      $('.sidenav').sidenav('close');
    }
  });

  // FILINGS
  // =======================================================
  // Add filing links
  $('.js-filings-pdf').each(function() {
    addFilingURL($(this));
  });

  function addFilingURL(el) {
    const ein = el.data('ein');
    const einShort = ein.toString().substring(0, 3);
    const taxPeriod = el.data('tax-period');
    // Foundation Center: http://990s.foundationcenter.org/990pf_pdf_archive/272/272624875/272624875_201412_990PF.pdf
    const urlPDF = 'http://990s.foundationcenter.org/990pf_pdf_archive/' +
                 einShort + '/' +
                 ein + '/' +
                 ein + '_' +
                 taxPeriod + '_990PF.pdf';
    el.attr('data-url-pdf', urlPDF);
    el.attr('href', urlPDF);
  }

  // Check if filing is available
  const gcf = 'https://us-central1-infinite-badge-163220.cloudfunctions.net/checkUrl';

  $('.js-filings-pdf').click(function(e) {
    e.preventDefault();
    M.toast({
      'html': 'Redirecting to latest 990...',
    });
    const elem = $(this);
    const target = $(this).attr('href');
    $.ajax({
      'method': 'POST',
      'url': gcf,
      'data': { 'target': target },
    })
      .done(function( res ) {
        if (res) {
          window.location.href = target;
        } else {
          elem.addClass('disabled');
          M.Toast.dismissAll();
          M.toast({
            'html': 'PDF not yet available. Try a prior year.',
          });
        }
      })
      .fail(function(xhr, textStatus, error) { // eslint-disable-line no-unused-vars
        const toastContent = 'Something went wrong.<button href="http://foundationcenter.org/find-funding/990-finder" class="btn-flat toast-action">Try Here.</button>';
        M.Toast.dismissAll();
        M.toast({
          'html': toastContent,
          'displayLength': 10000,
        });
      });
  });
});
<|endoftext|>


<commit_before>var path 		= require('path');
var rootPath 	= path.normalize(__dirname + '/../../');

module.exports 	= {
    local: {
        baseUrl: 'http://localhost:3030',
        db: 'mongodb://localhost/rp_local',
        rootPath: rootPath,
        port: process.env.PORT || 3030
    },
    staging: {
        baseUrl: 'http://dev-staging.resourceprojects.org',
        db: '@aws-us-east-1-portal.14.dblayer.com:10669,aws-us-east-1-portal.13.dblayer.com:10499/rp_dev?ssl=true',
        rootPath: rootPath,
        port: process.env.PORT || 80
    }
    //staging: {
    //    baseUrl: 'http://dev-staging.resourceprojects.org',
    //    db_1: '@aws-us-east-1-portal.14.dblayer.com:10669/rp_dev?ssl=true',
    //    db_1: '@aws-us-east-1-portal.13.dblayer.com:10499/rp_dev?ssl=true',
    //    rootPath: rootPath,
    //    port: process.env.PORT || 80
    //}
    //production: {
    //    baseUrl: 'http://resourceprojects.org',
    //    db: '@candidate.32.mongolayer.com:10582/rp_production',
    //    rootPath: rootPath,
    //    port: process.env.PORT || 80
    //}
};
<commit_msg>changed staging base url in staging config.<commit_after>var path 		= require('path');
var rootPath 	= path.normalize(__dirname + '/../../');

module.exports 	= {
    local: {
        baseUrl: 'http://localhost:3030',
        db: 'mongodb://localhost/rp_local',
        rootPath: rootPath,
        port: process.env.PORT || 3030
    },
    staging: {
        baseUrl: 'http://dev.resourceprojects.org',
        db: '@aws-us-east-1-portal.14.dblayer.com:10669,aws-us-east-1-portal.13.dblayer.com:10499/rp_dev?ssl=true',
        rootPath: rootPath,
        port: process.env.PORT || 80
    }
    //staging: {
    //    baseUrl: 'http://dev-staging.resourceprojects.org',
    //    db_1: '@aws-us-east-1-portal.14.dblayer.com:10669/rp_dev?ssl=true',
    //    db_1: '@aws-us-east-1-portal.13.dblayer.com:10499/rp_dev?ssl=true',
    //    rootPath: rootPath,
    //    port: process.env.PORT || 80
    //}
    //production: {
    //    baseUrl: 'http://resourceprojects.org',
    //    db: '@candidate.32.mongolayer.com:10582/rp_production',
    //    rootPath: rootPath,
    //    port: process.env.PORT || 80
    //}
};
<|endoftext|>


<commit_before>import _ from 'lodash';
import ReplConstants from '../constants/ReplConstants';
import ReplCommon from './ReplCommon';
import ReplOutput from '../common/ReplOutput';
let babel = require('babel-core');

const awaitMatcher = /^(?:\s*(?:(?:let|var|const)\s)?\s*([^=]+)=\s*|^\s*)(await\s.*)/;
const sourceMatcher = /^\s*(\.source)\s+([^\s]+)\s*$/;
const importMatcher = /\bimport\s+(?:(?:{(.+?)})|(.+?))\s+from\s+(['"])(.+?)\3/g;
const bindAsMatcher = /(.*)\s+as\s+(.*)/;
const asDefaultMatcher = /(?:\*|default)\s+as/;
const USE_STRICT_LENGTH = "'user strict;'".length;

let asyncWrapper = (code, binder) => {
  let assign = binder ? `root.${binder} = result;` : '';
  return `(async function() { let result = (${code}); ${assign} return result; }())`;
};

let importToRequire = (prefix, bindings, asBinding, __, modname) => {
  if(asBinding) {
    return `var ${asBinding.replace(asDefaultMatcher, '')} = (require('${modname}').default || require('${modname}'));`;
  }
  let result = Array.join((bindings).trim()
    .split(',')
    .map(m => {
      const asM = m.trim();
      const asBound = asM.match(bindAsMatcher);
      const [x, y] = asBound ? [asBound[1], asBound[2]] : [asM, asM];
      const attr = x === 'default' || x === '*' ? '' : `.${x}`;
      return `root.${y} = (require('${modname}').default || require('${modname}'))${attr};\n`
    }), '');
  return `${result}; void 0;`;
};

let cook = (plain) => {
  let tplain = plain.trim();
  let source = tplain.match(sourceMatcher);
  if(source) {
    let mod = source[2];
    return {
      local: true,
      output: ReplOutput.source(mod),
      input: `<span class='source'>.source <span class='module'>${mod}</span></span>`
    }
  }

  let output = plain, force = false;

  if(global.Mancy.session.lang === 'js') {
    if(global.Mancy.preferences.asyncWrap) {
      // bare await
      let match = plain.match(awaitMatcher);
      if(match) {
        output = `${asyncWrapper(match[2], match[1])}`;
        force = true;
      }
    }
    if(!force && plain.indexOf('import') !== -1) {
      output = plain.replace(importMatcher, importToRequire);
    }
  }

  return {
    force,
    local: false,
    output: global.Mancy.session.babel && global.Mancy.session.lang === 'js' ? babelTransfrom(output) : output
  };
};

let babelTransfrom = (plain) => {
  try {
    let matchCommonJS = (opt) => opt === 'transform-es2015-modules-commonjs';
    let strict = false;

    if(global.Mancy.session.mode === 'Strict') {
      strict = true;
      if(_.findIndex(ReplConstants.BABEL_OPTIONS.plugins, matchCommonJS) === -1) {
        ReplConstants.BABEL_OPTIONS.plugins.push('transform-es2015-modules-commonjs');
      }
    } else { _.remove(ReplConstants.BABEL_OPTIONS.plugins, matchCommonJS); }

    let code = babel
      .transform(plain, ReplConstants.BABEL_OPTIONS)
      .code;

    // transform imports added by babel scripts into require
    if(!strict && code.indexOf('import') !== -1) {
      code = code.replace(importMatcher, importToRequire);
    }

    return strict ? code.substring(USE_STRICT_LENGTH - 1) : code;
  } catch(e) {
    return e;
  }
}

let ReplInput = {
  transform: (plain) => {
    return cook(plain);
  }
};

export default ReplInput;
<commit_msg>#29, #138  Mitigate vm issue<commit_after>import _ from 'lodash';
import ReplConstants from '../constants/ReplConstants';
import ReplCommon from './ReplCommon';
import ReplOutput from '../common/ReplOutput';
let babel = require('babel-core');

const functionMatcher = /^\s*\bfunction\s+(..*?)\(/;
const awaitMatcher = /^(?:\s*(?:(?:let|var|const)\s)?\s*([^=]+)=\s*|^\s*)(await\s.*)/;
const sourceMatcher = /^\s*(\.source)\s+([^\s]+)\s*$/;
const importMatcher = /\bimport\s+(?:(?:{(.+?)})|(.+?))\s+from\s+(['"])(.+?)\3/g;
const bindAsMatcher = /(.*)\s+as\s+(.*)/;
const asDefaultMatcher = /(?:\*|default)\s+as/;
const USE_STRICT_LENGTH = "'user strict;'".length;

let funTransformer = (source, find, replace) =>
  `var ${replace} = function ${replace}(${source.substring(find.length)}`;
let asyncWrapper = (code, binder) => {
  let assign = binder ? `root.${binder} = result;` : '';
  return `(async function() { let result = (${code}); ${assign} return result; }())`;
};

let importToRequire = (prefix, bindings, asBinding, __, modname) => {
  if(asBinding) {
    return `var ${asBinding.replace(asDefaultMatcher, '')} = (require('${modname}').default || require('${modname}'));`;
  }
  let result = Array.join((bindings).trim()
    .split(',')
    .map(m => {
      const asM = m.trim();
      const asBound = asM.match(bindAsMatcher);
      const [x, y] = asBound ? [asBound[1], asBound[2]] : [asM, asM];
      const attr = x === 'default' || x === '*' ? '' : `.${x}`;
      return `root.${y} = (require('${modname}').default || require('${modname}'))${attr};\n`
    }), '');
  return `${result}; void 0;`;
};

let cook = (plain) => {
  let tplain = plain.trim();
  let source = tplain.match(sourceMatcher);
  if(source) {
    let mod = source[2];
    return {
      local: true,
      output: ReplOutput.source(mod),
      input: `<span class='source'>.source <span class='module'>${mod}</span></span>`
    }
  }

  let output = plain, force = false;

  if(global.Mancy.session.lang === 'js') {
    let funMatch = output.match(functionMatcher);
    if(funMatch) {
      output = `${funTransformer(output, funMatch[0], funMatch[1])}`;
    }
    if(global.Mancy.preferences.asyncWrap) {
      // bare await
      let match = output.match(awaitMatcher);
      if(match) {
        output = `${asyncWrapper(match[2], match[1])}`;
        force = true;
      }
    }
    if(!force && plain.indexOf('import') !== -1) {
      output = plain.replace(importMatcher, importToRequire);
    }
  }

  return {
    force,
    local: false,
    output: global.Mancy.session.babel && global.Mancy.session.lang === 'js' ? babelTransfrom(output) : output
  };
};

let babelTransfrom = (plain) => {
  try {
    let matchCommonJS = (opt) => opt === 'transform-es2015-modules-commonjs';
    let strict = false;

    if(global.Mancy.session.mode === 'Strict') {
      strict = true;
      if(_.findIndex(ReplConstants.BABEL_OPTIONS.plugins, matchCommonJS) === -1) {
        ReplConstants.BABEL_OPTIONS.plugins.push('transform-es2015-modules-commonjs');
      }
    } else { _.remove(ReplConstants.BABEL_OPTIONS.plugins, matchCommonJS); }

    let code = babel
      .transform(plain, ReplConstants.BABEL_OPTIONS)
      .code;

    // transform imports added by babel scripts into require
    if(!strict && code.indexOf('import') !== -1) {
      code = code.replace(importMatcher, importToRequire);
    }

    return strict ? code.substring(USE_STRICT_LENGTH - 1) : code;
  } catch(e) {
    return e;
  }
}

let ReplInput = {
  transform: (plain) => {
    return cook(plain);
  }
};

export default ReplInput;
<|endoftext|>


<commit_before>// @flow

const path = require("path"),
  spawn = require("cross-spawn"),
  fs = require("fs-extra"),
  _ = require("lodash"),
  Murmur = require("murmur-hash-js"),
  Version = require("./version.js"),
  supportsColor = require("supports-color"),
  Runner = require("./runner.js"),
  pipeFilename = require("./pipe-filename.js");

function prepareCompiledJsFile(dest/*:string*/) {
  return Promise.all([
    readUtf8(path.join(__dirname, "..", "templates", "before.js")),
    readUtf8(dest),
    readUtf8(path.join(__dirname, "..", "templates", "after.js"))
  ]).then(([before, content, after]) => {
    const finalContent = [
      before,
      "var Elm = (function(module) { ",
      content,
      "return this.Elm;",
      "})({});",
      "var pipeFilename = " + JSON.stringify(pipeFilename) + ";",
      after
    ].join("\n");

    return fs.writeFile(dest, finalContent);
  });
}

function readUtf8(filepath) {
  return new Promise((resolve, reject) => {
    fs.readFile(filepath, {encoding: "utf8"}, (err, contents) =>
      {
        if (err) {
          reject(err);
        } else {
          resolve(contents);
        }
      }
    );
  });
}

function generateElmJson(projectRootDir/*:string*/, testRootDir/*:string*/, pathToElmBinary /*:string*/, filePaths /*:Array<string>*/) {
  const generatedCodeDir = path.resolve(
    path.join(projectRootDir, "elm-stuff", "generated-code", "elm-explorations", "test")
  );
  const elmJsonPath = path.resolve(path.join(projectRootDir, "elm.json"));
  const generatedSrc = path.join(generatedCodeDir, "src");

  if (!fs.existsSync(testRootDir)) {
    console.error("Error: " + testRootDir + " does not exist. Please create a tests/ directory in your project root!");
    process.exit(1);
  }

  if (!fs.lstatSync(testRootDir).isDirectory()) {
    console.error("Error: " + testRootDir + " exists, but it is not a directory. Please create a tests/ directory in your project root!");
    process.exit(1);
  }

  fs.mkdirpSync(generatedCodeDir);
  fs.mkdirpSync(generatedSrc);

  var projectElmJson = {};

  try {
    projectElmJson = fs.readJsonSync(elmJsonPath);
  } catch (err) {
    console.error("Error reading elm.json: " + err);
    process.exit(1);
  }
  var isPackageProject = projectElmJson.type === "package";

  var testElmJson = {
    type: "application",
    "source-directories": [], // these are added below
    "elm-version": "0.19.0",
    "dependencies": {
      "direct": {},
      "indirect": {}
    },
    "test-dependencies": {
      "direct": {},
      "indirect": {}
    }
  };

  var nodeTestRunnerElmJsonPath = path.resolve(path.join(__dirname, "..", "elm.json"));
  var nodeTestRunnerElmJson = fs.readJsonSync(nodeTestRunnerElmJsonPath);
  // we want to use the version of elm-explorations/test that the user
  // specifies in their own test-dependencies. everything else needs to
  // be included for the test runner to compile.
  delete nodeTestRunnerElmJson.dependencies.direct['elm-explorations/test'];
  addDirectDependencies(nodeTestRunnerElmJson["dependencies"]["direct"], isPackageProject, testElmJson);
  addIndirectDependencies(nodeTestRunnerElmJson["dependencies"]["indirect"], testElmJson);

  if (isPackageProject) {
    addDirectDependencies(projectElmJson["dependencies"], isPackageProject, testElmJson);
    addDirectDependencies(projectElmJson["test-dependencies"], isPackageProject, testElmJson);
    // package projects don't explicitly list their transitive dependencies,
    // to we have to figure out what they are.  We write the elm.json that
    // we have so far, and run elm to see what it thinks is missing.
    fs.writeFileSync(
      path.join(generatedCodeDir, "elm.json"),
      JSON.stringify(testElmJson, null, 4)
    );
    var missingDeps = askElmForMissingTransitiveDependencies(pathToElmBinary, generatedCodeDir);
    addIndirectDependencies(missingDeps, testElmJson);
  } else {
    addDirectDependencies(projectElmJson["dependencies"]["direct"], isPackageProject, testElmJson);
    addIndirectDependencies(projectElmJson["dependencies"]["indirect"], testElmJson);
    addDirectDependencies(projectElmJson["test-dependencies"]["direct"], isPackageProject, testElmJson);
    addIndirectDependencies(projectElmJson["test-dependencies"]["indirect"], testElmJson);
  }

  // Make all the source-directories absolute, and introduce a new one.
  var projectSourceDirs;
  if (isPackageProject) {
    projectSourceDirs = [ "./src" ];
  } else {
    projectSourceDirs = projectElmJson["source-directories"];
  }
  var sourceDirs = projectSourceDirs.map(
    function(src) {
      return path.resolve(path.join(projectRootDir, src));
    }
  ).concat(testRootDir);

  testElmJson["source-directories"] = [
    // Include elm-stuff/generated-sources - since we'll be generating sources in there.
    generatedSrc,

    // NOTE: we must include node-test-runner's Elm source as a source-directory
    // instead of adding it as a dependency so that it can include port modules
    path.resolve(path.join(__dirname, "..", "src"))
  ].concat(sourceDirs);

  // When running node-test-runner's own test suite, the node-test-runner/src folder
  // will get added twice: once because it's the source-directory of the packge being tested,
  // and once because elm-test will always add it.
  // To prevent elm from being confused, we need to remove the duplicate when this happens.
  testElmJson["source-directories"] =
    testElmJson["source-directories"].filter(function(value, index, self) {
      return self.indexOf(value) === index;
    });

  // Generate the new elm.json
  fs.writeFileSync(
    path.join(generatedCodeDir, "elm.json"),
    JSON.stringify(testElmJson, null, 4)
  );

  return [generatedCodeDir, generatedSrc, sourceDirs];
}

function addDirectDependencies(deps/*:Object*/, isPackageProject/*:boolean*/, testElmJson/*:Object*/) {
  Object.keys(deps).forEach(function(name) {
    var version = deps[name];
    if (isPackageProject) {
      // Use the lowest version in the range.
      // NOTE: technically this doesn't work if someone does something weird like:
      //
      // "2.0.0 < v < 3.0.0"
      //
      // ...but we're choosing not to support that right now.
      version = version.split(" ")[0];
    }
    if (testElmJson["dependencies"]["direct"].hasOwnProperty(name)) {
      var existingVersion = testElmJson["dependencies"]["direct"][name];

      // If we have a clash, choose the higher of the two versions.
      // This may not work! It's entirely possible that the result won't
      // compile. We're going to try it and see what happens.
      version = Version.getHigherVersion(version, existingVersion);
    }
    testElmJson["dependencies"]["direct"][name] = version;
  });
}

function addIndirectDependencies(deps, testElmJson/*:Object*/) {
  Object.keys(deps).forEach(function(name) {
    if (testElmJson["dependencies"]["direct"].hasOwnProperty(name)) {
      // already a normal dep
    } else {
      var version = deps[name];

      if (testElmJson["dependencies"]["indirect"].hasOwnProperty(name)) {
        var existingVersion = testElmJson["dependencies"]["indirect"][name];

        // If we have a clash, choose the higher of the two versions.
        // This may not work! It's entirely possible that the result won't
        // compile. We're going to try it and see what happens.
        version = Version.getHigherVersion(version, existingVersion);
      }

      testElmJson["dependencies"]["indirect"][name] = version;
    }
  });
}
function askElmForMissingTransitiveDependencies(pathToElmBinary, pathtoElmProject) {
  var result = spawn.sync(pathToElmBinary, ["make", "--report=json"], {
    silent: true,
    cwd: pathtoElmProject
  });

  if (result.stderr == null) {
    console.error("No output received from elm make when searching for indirect dependencies.");
    process.exit(1);
  }

  // TODO: hopefully the next Elm 0.19 beta will print the JSON to stdout instead
  var output = result.stderr.toString();

  // TODO: hopefully the next Elm 0.19 beta will not have this message (only occurs on linux) mixed in with the JSON output
  output = output.replace(/^elm:.*no version information available \(required by elm\)\n/, '');

  var report = JSON.parse(output);
  if (report.type === "error" && report.title === "NO INPUT") {
    // all transtive dependencies are listed already (elm is complaining that we didn't tell it which .elm file to build, which happens after the dependency validations)
    return {};
  } else if (report.type === "error" && report.title === "MISSING DEPENDENCIES") {
    var missingDeps = {};
    // parse the missing dependencies and versions from the error report
    report.message[1].string.replace(/"([^"]*)": "([^"]*)"/g, function(_, name, version) {
      missingDeps[name] = version;
    })
    return missingDeps;
  } else {
    console.error("elm-test internal error: got an unexpected result from 'elm make' when validating transitive dependencies.  Please report this at https://github.com/rtfeldman/node-test-runner/issues");
    process.exit(1);
    return {}; // This makes flow happy because it doesn't know process.exit will stop everything
  }
}

function generateMainModule(fuzz/*:number*/, seed/*:number*/, report/*:string*/, testFilePaths/*:Array<string>*/, tests/*:Array<Object>*/, generatedSrc/*:string*/, processes/*:number*/) {
  // Building things like:
  //
  // import MyTests
  //
  // MyTests.suite
  const imports = _.map(tests, function(test) {
    return "import " + test.name;
  });
  const testList = _.map(tests, function(mod) {
    return (
      '    Test.describe "' +
      mod.name +
      '" [' +
      _.map(mod.tests, function(test) {
        return mod.name + "." + test;
      }).join(",\n    ") +
      "]"
    );
  });

  if (testList.length === 0) {
    const errorMessage =
      testFilePaths.length > 0
        ? "I couldn't find any exposed values of type Test in files matching \"" +
          testFilePaths.toString() +
          '"\n\nMaybe try running elm-test with no arguments?'
        : "I couldn't find any exposed values of type Test in any *.elm files in the tests/ directory of your project's root directory.\n\nTo generate some initial tests to get things going, run elm-test init";

    console.error(errorMessage);
    process.exit(1);
  }

  function sanitizedToString(str) {
    return '"' + str.replace(/\\/g, "\\\\").replace(/"/g, '\\"') + '"';
  }

  function getReportCode() {
    switch (report) {
      case "json":
        return "JsonReport";
      case "junit":
        return "JUnitReport";
      default:
        if (supportsColor) {
          return "(ConsoleReport UseColor)";
        } else {
          return "(ConsoleReport Monochrome)";
        }
    }
  }

  if (isNaN(seed)) {
    seed = Math.floor(Math.random() * (407199254740991)) + 1000;
  }

  var opts = {
    fuzz: isNaN(fuzz) ? "Nothing" : "Just " + fuzz,
    seed: seed,
    report: getReportCode(),
    paths: testFilePaths.map(sanitizedToString).join(",")
  };

  var optsCode =
    "{ runs = " +
    opts.fuzz +
    ", report = " +
    opts.report +
    ", seed = " +
    opts.seed +
    ", processes = " +
    processes +
    ", paths = [" +
    opts.paths +
    "]}";

  const testFileBody = [
    imports.join("\n"),
    "",
    "import Test.Reporter.Reporter exposing (Report(..))",
    "import Console.Text exposing (UseColor(..))",
    "import Test.Runner.Node",
    "import Test",
    "",
    "main : Test.Runner.Node.TestProgram",
    "main =",
    "    [ " + testList.join(", ") + " ]",
    "        |> Test.concat",
    "        |> Test.Runner.Node.run " + optsCode
  ].join("\n");

  // Generate a filename that incorporates the hash of file contents.
  // This way, if you run e.g. `elm-test Foo.elm` and then `elm-test Bar.elm`
  // and then re-run `elm-test Foo.elm` we still have a cached `Main` for
  // `Foo.elm` (assuming none of its necessary imports have changed - and
  // why would they?) so we don't have to recompile it.
  const salt = Murmur.murmur3(testFileBody);
  const moduleName = "Main" + salt;
  const mainPath = path.join(generatedSrc, "Test", "Generated");
  const mainFile = path.join(mainPath, moduleName + ".elm");
  // We'll be putting the generated Main in something like this:
  //
  // my-project-name/elm-stuff/generated-code/elm-community/elm-test/src/Test/Generated/Main123456.elm
  const testFileContents = [
    "module Test.Generated." + moduleName + " exposing (main)",
    testFileBody
  ].join("\n\n");

  // Make sure src/Test/Generated/ exists so we can write the file there.
  fs.mkdirpSync(mainPath);

  // Always write the file, in order to update its timestamp. This is important,
  // because if we run `elm-make Main123456.elm` and that file's timestamp did
  // not change, elm-make will short-circuit and not recompile *anything* - even
  // if some of Main's dependencies (such as an individual test file) changed.
  fs.writeFileSync(mainFile, testFileContents);

  return mainFile;
}

module.exports = {prepareCompiledJsFile, generateElmJson, generateMainModule};

<commit_msg>Fix typo<commit_after>// @flow

const path = require("path"),
  spawn = require("cross-spawn"),
  fs = require("fs-extra"),
  _ = require("lodash"),
  Murmur = require("murmur-hash-js"),
  Version = require("./version.js"),
  supportsColor = require("supports-color"),
  Runner = require("./runner.js"),
  pipeFilename = require("./pipe-filename.js");

function prepareCompiledJsFile(dest/*:string*/) {
  return Promise.all([
    readUtf8(path.join(__dirname, "..", "templates", "before.js")),
    readUtf8(dest),
    readUtf8(path.join(__dirname, "..", "templates", "after.js"))
  ]).then(([before, content, after]) => {
    const finalContent = [
      before,
      "var Elm = (function(module) { ",
      content,
      "return this.Elm;",
      "})({});",
      "var pipeFilename = " + JSON.stringify(pipeFilename) + ";",
      after
    ].join("\n");

    return fs.writeFile(dest, finalContent);
  });
}

function readUtf8(filepath) {
  return new Promise((resolve, reject) => {
    fs.readFile(filepath, {encoding: "utf8"}, (err, contents) =>
      {
        if (err) {
          reject(err);
        } else {
          resolve(contents);
        }
      }
    );
  });
}

function generateElmJson(projectRootDir/*:string*/, testRootDir/*:string*/, pathToElmBinary /*:string*/, filePaths /*:Array<string>*/) {
  const generatedCodeDir = path.resolve(
    path.join(projectRootDir, "elm-stuff", "generated-code", "elm-explorations", "test")
  );
  const elmJsonPath = path.resolve(path.join(projectRootDir, "elm.json"));
  const generatedSrc = path.join(generatedCodeDir, "src");

  if (!fs.existsSync(testRootDir)) {
    console.error("Error: " + testRootDir + " does not exist. Please create a tests/ directory in your project root!");
    process.exit(1);
  }

  if (!fs.lstatSync(testRootDir).isDirectory()) {
    console.error("Error: " + testRootDir + " exists, but it is not a directory. Please create a tests/ directory in your project root!");
    process.exit(1);
  }

  fs.mkdirpSync(generatedCodeDir);
  fs.mkdirpSync(generatedSrc);

  var projectElmJson = {};

  try {
    projectElmJson = fs.readJsonSync(elmJsonPath);
  } catch (err) {
    console.error("Error reading elm.json: " + err);
    process.exit(1);
  }
  var isPackageProject = projectElmJson.type === "package";

  var testElmJson = {
    type: "application",
    "source-directories": [], // these are added below
    "elm-version": "0.19.0",
    "dependencies": {
      "direct": {},
      "indirect": {}
    },
    "test-dependencies": {
      "direct": {},
      "indirect": {}
    }
  };

  var nodeTestRunnerElmJsonPath = path.resolve(path.join(__dirname, "..", "elm.json"));
  var nodeTestRunnerElmJson = fs.readJsonSync(nodeTestRunnerElmJsonPath);
  // we want to use the version of elm-explorations/test that the user
  // specifies in their own test-dependencies. everything else needs to
  // be included for the test runner to compile.
  delete nodeTestRunnerElmJson.dependencies.direct['elm-explorations/test'];
  addDirectDependencies(nodeTestRunnerElmJson["dependencies"]["direct"], isPackageProject, testElmJson);
  addIndirectDependencies(nodeTestRunnerElmJson["dependencies"]["indirect"], testElmJson);

  if (isPackageProject) {
    addDirectDependencies(projectElmJson["dependencies"], isPackageProject, testElmJson);
    addDirectDependencies(projectElmJson["test-dependencies"], isPackageProject, testElmJson);
    // package projects don't explicitly list their transitive dependencies,
    // to we have to figure out what they are.  We write the elm.json that
    // we have so far, and run elm to see what it thinks is missing.
    fs.writeFileSync(
      path.join(generatedCodeDir, "elm.json"),
      JSON.stringify(testElmJson, null, 4)
    );
    var missingDeps = askElmForMissingTransitiveDependencies(pathToElmBinary, generatedCodeDir);
    addIndirectDependencies(missingDeps, testElmJson);
  } else {
    addDirectDependencies(projectElmJson["dependencies"]["direct"], isPackageProject, testElmJson);
    addIndirectDependencies(projectElmJson["dependencies"]["indirect"], testElmJson);
    addDirectDependencies(projectElmJson["test-dependencies"]["direct"], isPackageProject, testElmJson);
    addIndirectDependencies(projectElmJson["test-dependencies"]["indirect"], testElmJson);
  }

  // Make all the source-directories absolute, and introduce a new one.
  var projectSourceDirs;
  if (isPackageProject) {
    projectSourceDirs = [ "./src" ];
  } else {
    projectSourceDirs = projectElmJson["source-directories"];
  }
  var sourceDirs = projectSourceDirs.map(
    function(src) {
      return path.resolve(path.join(projectRootDir, src));
    }
  ).concat(testRootDir);

  testElmJson["source-directories"] = [
    // Include elm-stuff/generated-sources - since we'll be generating sources in there.
    generatedSrc,

    // NOTE: we must include node-test-runner's Elm source as a source-directory
    // instead of adding it as a dependency so that it can include port modules
    path.resolve(path.join(__dirname, "..", "src"))
  ].concat(sourceDirs);

  // When running node-test-runner's own test suite, the node-test-runner/src folder
  // will get added twice: once because it's the source-directory of the packge being tested,
  // and once because elm-test will always add it.
  // To prevent elm from being confused, we need to remove the duplicate when this happens.
  testElmJson["source-directories"] =
    testElmJson["source-directories"].filter(function(value, index, self) {
      return self.indexOf(value) === index;
    });

  // Generate the new elm.json
  fs.writeFileSync(
    path.join(generatedCodeDir, "elm.json"),
    JSON.stringify(testElmJson, null, 4)
  );

  return [generatedCodeDir, generatedSrc, sourceDirs];
}

function addDirectDependencies(deps/*:Object*/, isPackageProject/*:boolean*/, testElmJson/*:Object*/) {
  Object.keys(deps).forEach(function(name) {
    var version = deps[name];
    if (isPackageProject) {
      // Use the lowest version in the range.
      // NOTE: technically this doesn't work if someone does something weird like:
      //
      // "2.0.0 < v < 3.0.0"
      //
      // ...but we're choosing not to support that right now.
      version = version.split(" ")[0];
    }
    if (testElmJson["dependencies"]["direct"].hasOwnProperty(name)) {
      var existingVersion = testElmJson["dependencies"]["direct"][name];

      // If we have a clash, choose the higher of the two versions.
      // This may not work! It's entirely possible that the result won't
      // compile. We're going to try it and see what happens.
      version = Version.getHigherVersion(version, existingVersion);
    }
    testElmJson["dependencies"]["direct"][name] = version;
  });
}

function addIndirectDependencies(deps, testElmJson/*:Object*/) {
  Object.keys(deps).forEach(function(name) {
    if (testElmJson["dependencies"]["direct"].hasOwnProperty(name)) {
      // already a normal dep
    } else {
      var version = deps[name];

      if (testElmJson["dependencies"]["indirect"].hasOwnProperty(name)) {
        var existingVersion = testElmJson["dependencies"]["indirect"][name];

        // If we have a clash, choose the higher of the two versions.
        // This may not work! It's entirely possible that the result won't
        // compile. We're going to try it and see what happens.
        version = Version.getHigherVersion(version, existingVersion);
      }

      testElmJson["dependencies"]["indirect"][name] = version;
    }
  });
}
function askElmForMissingTransitiveDependencies(pathToElmBinary, pathToElmProject) {
  var result = spawn.sync(pathToElmBinary, ["make", "--report=json"], {
    silent: true,
    cwd: pathToElmProject
  });

  if (result.stderr == null) {
    console.error("No output received from elm make when searching for indirect dependencies.");
    process.exit(1);
  }

  // TODO: hopefully the next Elm 0.19 beta will print the JSON to stdout instead
  var output = result.stderr.toString();

  // TODO: hopefully the next Elm 0.19 beta will not have this message (only occurs on linux) mixed in with the JSON output
  output = output.replace(/^elm:.*no version information available \(required by elm\)\n/, '');

  var report = JSON.parse(output);
  if (report.type === "error" && report.title === "NO INPUT") {
    // all transtive dependencies are listed already (elm is complaining that we didn't tell it which .elm file to build, which happens after the dependency validations)
    return {};
  } else if (report.type === "error" && report.title === "MISSING DEPENDENCIES") {
    var missingDeps = {};
    // parse the missing dependencies and versions from the error report
    report.message[1].string.replace(/"([^"]*)": "([^"]*)"/g, function(_, name, version) {
      missingDeps[name] = version;
    })
    return missingDeps;
  } else {
    console.error("elm-test internal error: got an unexpected result from 'elm make' when validating transitive dependencies.  Please report this at https://github.com/rtfeldman/node-test-runner/issues");
    process.exit(1);
    return {}; // This makes flow happy because it doesn't know process.exit will stop everything
  }
}

function generateMainModule(fuzz/*:number*/, seed/*:number*/, report/*:string*/, testFilePaths/*:Array<string>*/, tests/*:Array<Object>*/, generatedSrc/*:string*/, processes/*:number*/) {
  // Building things like:
  //
  // import MyTests
  //
  // MyTests.suite
  const imports = _.map(tests, function(test) {
    return "import " + test.name;
  });
  const testList = _.map(tests, function(mod) {
    return (
      '    Test.describe "' +
      mod.name +
      '" [' +
      _.map(mod.tests, function(test) {
        return mod.name + "." + test;
      }).join(",\n    ") +
      "]"
    );
  });

  if (testList.length === 0) {
    const errorMessage =
      testFilePaths.length > 0
        ? "I couldn't find any exposed values of type Test in files matching \"" +
          testFilePaths.toString() +
          '"\n\nMaybe try running elm-test with no arguments?'
        : "I couldn't find any exposed values of type Test in any *.elm files in the tests/ directory of your project's root directory.\n\nTo generate some initial tests to get things going, run elm-test init";

    console.error(errorMessage);
    process.exit(1);
  }

  function sanitizedToString(str) {
    return '"' + str.replace(/\\/g, "\\\\").replace(/"/g, '\\"') + '"';
  }

  function getReportCode() {
    switch (report) {
      case "json":
        return "JsonReport";
      case "junit":
        return "JUnitReport";
      default:
        if (supportsColor) {
          return "(ConsoleReport UseColor)";
        } else {
          return "(ConsoleReport Monochrome)";
        }
    }
  }

  if (isNaN(seed)) {
    seed = Math.floor(Math.random() * (407199254740991)) + 1000;
  }

  var opts = {
    fuzz: isNaN(fuzz) ? "Nothing" : "Just " + fuzz,
    seed: seed,
    report: getReportCode(),
    paths: testFilePaths.map(sanitizedToString).join(",")
  };

  var optsCode =
    "{ runs = " +
    opts.fuzz +
    ", report = " +
    opts.report +
    ", seed = " +
    opts.seed +
    ", processes = " +
    processes +
    ", paths = [" +
    opts.paths +
    "]}";

  const testFileBody = [
    imports.join("\n"),
    "",
    "import Test.Reporter.Reporter exposing (Report(..))",
    "import Console.Text exposing (UseColor(..))",
    "import Test.Runner.Node",
    "import Test",
    "",
    "main : Test.Runner.Node.TestProgram",
    "main =",
    "    [ " + testList.join(", ") + " ]",
    "        |> Test.concat",
    "        |> Test.Runner.Node.run " + optsCode
  ].join("\n");

  // Generate a filename that incorporates the hash of file contents.
  // This way, if you run e.g. `elm-test Foo.elm` and then `elm-test Bar.elm`
  // and then re-run `elm-test Foo.elm` we still have a cached `Main` for
  // `Foo.elm` (assuming none of its necessary imports have changed - and
  // why would they?) so we don't have to recompile it.
  const salt = Murmur.murmur3(testFileBody);
  const moduleName = "Main" + salt;
  const mainPath = path.join(generatedSrc, "Test", "Generated");
  const mainFile = path.join(mainPath, moduleName + ".elm");
  // We'll be putting the generated Main in something like this:
  //
  // my-project-name/elm-stuff/generated-code/elm-community/elm-test/src/Test/Generated/Main123456.elm
  const testFileContents = [
    "module Test.Generated." + moduleName + " exposing (main)",
    testFileBody
  ].join("\n\n");

  // Make sure src/Test/Generated/ exists so we can write the file there.
  fs.mkdirpSync(mainPath);

  // Always write the file, in order to update its timestamp. This is important,
  // because if we run `elm-make Main123456.elm` and that file's timestamp did
  // not change, elm-make will short-circuit and not recompile *anything* - even
  // if some of Main's dependencies (such as an individual test file) changed.
  fs.writeFileSync(mainFile, testFileContents);

  return mainFile;
}

module.exports = {prepareCompiledJsFile, generateElmJson, generateMainModule};

<|endoftext|>


<commit_before>
define([
  'app/config',
  'ember',
  'ember-data',
  'lib/marked',
  'plugins/text!app/templates/application.hbs',
  'plugins/text!app/templates/index.hbs',
  'plugins/text!app/templates/posts.hbs',
  'plugins/text!app/templates/profile.hbs',
  'plugins/moment',
  'plugins/resize'
],
function (Config, Ember, DS, marked, applicationTemplate, indexTemplate, postsTemplate, profileTemplate) {

  Ember.TEMPLATES['application'] = Ember.Handlebars.compile(applicationTemplate);
  Ember.TEMPLATES['index'] = Ember.Handlebars.compile(indexTemplate);
  Ember.TEMPLATES['profile'] = Ember.Handlebars.compile(profileTemplate);
  Ember.TEMPLATES['posts'] = Ember.Handlebars.compile(postsTemplate);

  var cl = console.log.bind(console);

  var App = Ember.Application.create({
    LOG_TRANSITIONS: true,
    rootElement: '#ember-container'
  });

  window.App = App;

  //-------------------------
  // Store
  //-------------------------
  App.Store = DS.Store.extend({
    revision: 12,
    adapter: DS.RESTAdapter
  });

  DS.RESTAdapter.reopen({
    namespace: 'api'
  });

  //-------------------------
  // Models
  //-------------------------
  App.Post = DS.Model.extend({
    title: DS.attr('string'),
    created: DS.attr('number'),
    userId: DS.attr('string'),
    userName: DS.attr('string'),
    userAvatar: DS.attr('string'),
    isMyPost: DS.attr('boolean')
  });

  App.User = DS.Model.extend({
    name: DS.attr('string'),
    avatar: DS.attr('string'),
    isMyUser: DS.attr('boolean'),
    email: DS.attr('string')
  });

  //-------------------------
  // Views
  //-------------------------
  App.PostsView = Ember.View.extend({
    templateName: 'posts'
  });

  App.IndexView = Ember.View.extend({
    keyPress: function(event) {
      if (this.get('controller').get('postButtonDisabled')) {
        return;
      }

      // Ctrl + Enter.
      if (event.ctrlKey && event.which === 13) {
        this.get('controller').send('createNewPost');
      }
    }
  });

  //-------------------------
  // Controllers
  //-------------------------
  App.ApplicationController = Ember.Controller.extend({
    needs: ['posts', 'users']
  });

  App.IndexController = Ember.Controller.extend({
    needs: ['posts', 'users'],
    myUserBinding: 'controllers.users.myUser',
    newPostTitle: '',

    postBoxDisabled: function() {
      return !(this.get('myUser.isLoaded') && (this.get('myUser.length') > 0));
    }.property('myUser.isLoaded', 'myUser.length'),

    postButtonDisabled: function() {
      return !(!this.get('postBoxDisabled') && this.get('newPostTitle.length') > 0);
    }.property('postBoxDisabled', 'newPostTitle'),

    createNewPost: function() {
      var self = this;
      var myUser = this.get('controllers.users.myUser.firstObject.user');

      var newPost = App.Post.createRecord({
        user: myUser,
        title: this.get('newPostTitle')
      });
      newPost.on('didCreate', function() {
        self.set('newPostTitle', '');
      });
      newPost.get('transaction').commit();
    }
  });

  App.ProfileController = Ember.Controller.extend({
    needs: ['posts', 'users'],
    myUserBinding: 'controllers.users.myUser',
    filterByUserBinding: 'controllers.posts.filterByUser',
    newName: '',
    newEmail: '',

    // Helper to disable the button when the fields are not filled.
    createButtonDisabled: function() {
      return this.get('newName.length') === 0;
    }.property('newName'),

    createNew: function() {
      var self = this;

      var newUser = App.User.createRecord({
        name: this.get('newName'),
        email: this.get('newEmail'),
        isMyUser: true
      });

      newUser.on('didCreate', function() {
        self.set('myUser', App.User.filter(function(user) {
          return user.get('isMyUser') === true;
        }));
        self.set('filterByUser', self.get('myUser'));
      });

      newUser.get('transaction').commit();
    }
  });

  App.UsersController = Ember.ArrayController.extend({
    // This is set to a FilteredRecordArray by the router. Just use the
    // first object in the array.
    myUser: []
  });

  App.PostsController = Ember.ArrayController.extend({
    filterByUser: [],
    sortProperties: ['created'],
    sortAscending: false,

    filteredPosts: function() {
      if (this.get('filterByUser.length') > 0) {
        var filterUserId = this.get('filterByUser.firstObject.id');
        if (filterUserId) {
          return this.get('arrangedContent').filterProperty('userId', filterUserId);
        }
      }
      return this.get('arrangedContent');
    }.property('content.[]', 'filterByUser.[]'),

    deletePost: function(id) {
      if (confirm('Are you sure you want to delete this post?')) {
        var post = App.Post.find(id);
        post.deleteRecord();
        post.get('transaction').commit();
      }
    },

    loadMore: function() {
      App.Post.find({before : this.get('filteredPosts.lastObject.id')});
    }
  });

  //-------------------------
  // Router
  //-------------------------
  App.Router.map(function() {
    this.resource('index', {path: '/'});
    this.resource('profile', {path: '/profile'});
  });

  App.ApplicationRoute = Ember.Route.extend({
    setupController: function(controller) {
      controller.set('controllers.posts.content', App.Post.find());
      controller.set('controllers.users.myUser', App.User.find({'is_my_user': true}));
      var refreshUI = function() {
        App.Post.find();
        setTimeout(refreshUI, Config.ui.pollInterval);
      };
      setTimeout(refreshUI, 5000);
    }
  });

  App.IndexRoute = Ember.Route.extend({
    setupController: function(controller) {
      var postsController = controller.get('controllers.posts');
      postsController.set('filterByUser', []);
    }
  });

  App.ProfileRoute = Ember.Route.extend({
    setupController: function(controller) {
      var postsController = controller.get('controllers.posts');
      var usersController = controller.get('controllers.users');
      postsController.set('filterByUser', usersController.get('myUser'));
    }
  });

  //-------------------------
  // Handlebars
  //-------------------------
  Ember.Handlebars.registerBoundHelper('nanoDate', function(value, options) {
    var escaped = Handlebars.Utils.escapeExpression(value);
    var ms = Math.round(escaped / Math.pow(10, 6));
    return new Handlebars.SafeString(moment(ms).fromNow());
  });

  Ember.Handlebars.registerBoundHelper('markdown', function(content) {
    marked.setOptions({
      gfm: true,
      sanitize: true
    });
    return new Handlebars.SafeString(marked(content));
  });

  $('.time').moment({ frequency: 5000 });

});
<commit_msg>Enabled breaks.<commit_after>
define([
  'app/config',
  'ember',
  'ember-data',
  'lib/marked',
  'plugins/text!app/templates/application.hbs',
  'plugins/text!app/templates/index.hbs',
  'plugins/text!app/templates/posts.hbs',
  'plugins/text!app/templates/profile.hbs',
  'plugins/moment',
  'plugins/resize'
],
function (Config, Ember, DS, marked, applicationTemplate, indexTemplate, postsTemplate, profileTemplate) {

  Ember.TEMPLATES['application'] = Ember.Handlebars.compile(applicationTemplate);
  Ember.TEMPLATES['index'] = Ember.Handlebars.compile(indexTemplate);
  Ember.TEMPLATES['profile'] = Ember.Handlebars.compile(profileTemplate);
  Ember.TEMPLATES['posts'] = Ember.Handlebars.compile(postsTemplate);

  var cl = console.log.bind(console);

  var App = Ember.Application.create({
    LOG_TRANSITIONS: true,
    rootElement: '#ember-container'
  });

  window.App = App;

  //-------------------------
  // Store
  //-------------------------
  App.Store = DS.Store.extend({
    revision: 12,
    adapter: DS.RESTAdapter
  });

  DS.RESTAdapter.reopen({
    namespace: 'api'
  });

  //-------------------------
  // Models
  //-------------------------
  App.Post = DS.Model.extend({
    title: DS.attr('string'),
    created: DS.attr('number'),
    userId: DS.attr('string'),
    userName: DS.attr('string'),
    userAvatar: DS.attr('string'),
    isMyPost: DS.attr('boolean')
  });

  App.User = DS.Model.extend({
    name: DS.attr('string'),
    avatar: DS.attr('string'),
    isMyUser: DS.attr('boolean'),
    email: DS.attr('string')
  });

  //-------------------------
  // Views
  //-------------------------
  App.PostsView = Ember.View.extend({
    templateName: 'posts'
  });

  App.IndexView = Ember.View.extend({
    keyPress: function(event) {
      if (this.get('controller').get('postButtonDisabled')) {
        return;
      }

      // Ctrl + Enter.
      if (event.ctrlKey && event.which === 13) {
        this.get('controller').send('createNewPost');
      }
    }
  });

  //-------------------------
  // Controllers
  //-------------------------
  App.ApplicationController = Ember.Controller.extend({
    needs: ['posts', 'users']
  });

  App.IndexController = Ember.Controller.extend({
    needs: ['posts', 'users'],
    myUserBinding: 'controllers.users.myUser',
    newPostTitle: '',

    postBoxDisabled: function() {
      return !(this.get('myUser.isLoaded') && (this.get('myUser.length') > 0));
    }.property('myUser.isLoaded', 'myUser.length'),

    postButtonDisabled: function() {
      return !(!this.get('postBoxDisabled') && this.get('newPostTitle.length') > 0);
    }.property('postBoxDisabled', 'newPostTitle'),

    createNewPost: function() {
      var self = this;
      var myUser = this.get('controllers.users.myUser.firstObject.user');

      var newPost = App.Post.createRecord({
        user: myUser,
        title: this.get('newPostTitle')
      });
      newPost.on('didCreate', function() {
        self.set('newPostTitle', '');
      });
      newPost.get('transaction').commit();
    }
  });

  App.ProfileController = Ember.Controller.extend({
    needs: ['posts', 'users'],
    myUserBinding: 'controllers.users.myUser',
    filterByUserBinding: 'controllers.posts.filterByUser',
    newName: '',
    newEmail: '',

    // Helper to disable the button when the fields are not filled.
    createButtonDisabled: function() {
      return this.get('newName.length') === 0;
    }.property('newName'),

    createNew: function() {
      var self = this;

      var newUser = App.User.createRecord({
        name: this.get('newName'),
        email: this.get('newEmail'),
        isMyUser: true
      });

      newUser.on('didCreate', function() {
        self.set('myUser', App.User.filter(function(user) {
          return user.get('isMyUser') === true;
        }));
        self.set('filterByUser', self.get('myUser'));
      });

      newUser.get('transaction').commit();
    }
  });

  App.UsersController = Ember.ArrayController.extend({
    // This is set to a FilteredRecordArray by the router. Just use the
    // first object in the array.
    myUser: []
  });

  App.PostsController = Ember.ArrayController.extend({
    filterByUser: [],
    sortProperties: ['created'],
    sortAscending: false,

    filteredPosts: function() {
      if (this.get('filterByUser.length') > 0) {
        var filterUserId = this.get('filterByUser.firstObject.id');
        if (filterUserId) {
          return this.get('arrangedContent').filterProperty('userId', filterUserId);
        }
      }
      return this.get('arrangedContent');
    }.property('content.[]', 'filterByUser.[]'),

    deletePost: function(id) {
      if (confirm('Are you sure you want to delete this post?')) {
        var post = App.Post.find(id);
        post.deleteRecord();
        post.get('transaction').commit();
      }
    },

    loadMore: function() {
      App.Post.find({before : this.get('filteredPosts.lastObject.id')});
    }
  });

  //-------------------------
  // Router
  //-------------------------
  App.Router.map(function() {
    this.resource('index', {path: '/'});
    this.resource('profile', {path: '/profile'});
  });

  App.ApplicationRoute = Ember.Route.extend({
    setupController: function(controller) {
      controller.set('controllers.posts.content', App.Post.find());
      controller.set('controllers.users.myUser', App.User.find({'is_my_user': true}));
      var refreshUI = function() {
        App.Post.find();
        setTimeout(refreshUI, Config.ui.pollInterval);
      };
      setTimeout(refreshUI, 5000);
    }
  });

  App.IndexRoute = Ember.Route.extend({
    setupController: function(controller) {
      var postsController = controller.get('controllers.posts');
      postsController.set('filterByUser', []);
    }
  });

  App.ProfileRoute = Ember.Route.extend({
    setupController: function(controller) {
      var postsController = controller.get('controllers.posts');
      var usersController = controller.get('controllers.users');
      postsController.set('filterByUser', usersController.get('myUser'));
    }
  });

  //-------------------------
  // Handlebars
  //-------------------------
  Ember.Handlebars.registerBoundHelper('nanoDate', function(value, options) {
    var escaped = Handlebars.Utils.escapeExpression(value);
    var ms = Math.round(escaped / Math.pow(10, 6));
    return new Handlebars.SafeString(moment(ms).fromNow());
  });

  Ember.Handlebars.registerBoundHelper('markdown', function(content) {
    marked.setOptions({
      gfm: true,
      sanitize: true,
      breaks: true
    });
    return new Handlebars.SafeString(marked(content));
  });

  $('.time').moment({ frequency: 5000 });

});
<|endoftext|>


<commit_before>import { logIfUnequal, capFirst, formatFinancialYear, ratingColor } from '../utils.js';

class ReportCard {
  constructor($element, report) {
    this.$element = $element;
    logIfUnequal(1, this.$element.length);
    this.$element.find(".audit-outcome__year").text(formatFinancialYear(report.date));
  }
}

export class AuditOpinions {
  selector = "#audit-outcomes";

  constructor(reports) {
    this.$element = $(this.selector);
    logIfUnequal(1, this.$element.length);

    reports.values.forEach((report, index) => {
      const $element = this.$element.find(`.audit-outcome:eq(${ index })`);
      new ReportCard($element, report);
    });
  }
}
<commit_msg>WIP audit outcome icons<commit_after>import { logIfUnequal, capFirst, formatFinancialYear, ratingColor } from '../utils.js';

class Icon {
  constructor($template, iconClasses, color) {
    this.$element = $template.clone();
    this.$element.removeClass("audit-outcome__icon--green audit-outcome__icon--yellow");
    this.$element.addClass(`audit-outcome__icon--${ color }`);
  }

  render() {
    return this.$element;
  }
}

class ReportCard {
  constructor($element, report) {
    this.$element = $element;
    logIfUnequal(1, this.$element.length);
    this.$element.find(".audit-outcome__year").text(formatFinancialYear(report.date));
    this.$element.find(".audit-outcome__heading").text(report.result);
    this.$element.find(".audit-outcome__download").attr("href", report.report_url);
    if (report.report_url) {
    }

    this.rating = report.rating;
    this.initIcons();
  }

  initIcons() {
    const $iconContainer = this.$element.find(".audit-outcome__icons");
    const $iconTemplate = $iconContainer.find(".audit-outcome__icon").first().clone();
    $iconContainer.empty();

    if (this.rating == "unqualified") {
      //   thumbs-up, cup
      $iconContainer.append(new Icon($iconTemplate, "thumbs-up", "green").render());
      $iconContainer.append(new Icon($iconTemplate, "trophy", "yellow").render());
    } else if (this.rating == "unqualified_emphasis_of_matter") {
      // unqualified_emphasis_of_matter
      //   thumbs-up
      $iconContainer.append(new Icon($iconTemplate, "thumbs-up", "green").render());
    } else if (this.rating == "unqualified") {

      // qualified
      //   thumbs-down
      $iconContainer.append(new Icon($iconTemplate, "thumbs-down", "green").render());
    } else if (this.rating == "unqualified") {

      // adverse
      // disclaimer
      //   exclamation-mark
      $iconContainer.append(new Icon($iconTemplate, "exclamation-mark", "green").render());
    } else if (this.rating == "unqualified") {
      // outstanding
      //   clock
      $iconContainer.append(new Icon($iconTemplate, "clock", "green").render());
    }
    $iconContainer.removeClass("hidden");
  }
}

export class AuditOpinions {
  selector = "#audit-outcomes";

  constructor(reports) {
    this.$element = $(this.selector);
    logIfUnequal(1, this.$element.length);

    reports.values.forEach((report, index) => {
      const $element = this.$element.find(`.audit-outcome:eq(${ index })`);
      new ReportCard($element, report);
    });
  }
}
<|endoftext|>