<commit_before>// ==UserScript==
// @name        TV Calendar Search
// @namespace   http://lepko.net/
// @version     1.0.1
// @run-at      document-start
// @match       *://*.pogdesign.co.uk/cat/*
// @require     https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.17.1/moment.min.js
// @require     https://cdnjs.cloudflare.com/ajax/libs/moment-timezone/0.5.11/moment-timezone-with-data-2010-2020.min.js
// @require     https://raw.githubusercontent.com/LepkoQQ/userjs/02a2d6a8d5d9dc06e8c2792c49f652164be49eff/dist/utils.js
// @grant       GM_xmlhttpRequest
// @grant       GM_getValue
// @grant       GM_setValue
// @grant       unsafeWindow
// @nocompat    Chrome
// ==/UserScript==

/* global _:false, moment:false */

/* async menu */
const AsyncMenu = (function asyncMenu() {
  'use strict';

  _.addCSS(`
    #ext_asyncmenu,
    #ext_asyncmenu * {
      box-sizing: border-box;
      cursor: default;
      margin: 0;
      padding: 0;
      user-select: none;
    }
    #ext_asyncmenu {
      position: fixed;
      top: 328px;
      display: inline-block;
      border: 1px solid #bababa;
      background-color: #fff;
      box-shadow: 2px 2px 2px 0px rgba(0, 0, 0, 0.5);
      white-space: nowrap;
      z-index: 2147483647;
    }
    #ext_asyncmenu table {
      margin: 2px -1px;
      border: 0;
      border-spacing: 0;
      border-collapse: collapse;
      font-family: "Segoe UI", sans-serif;
    }
    #ext_asyncmenu tr:not(.ext_separator):not(.ext_disabled):hover {
      background-color: #4281f4;
      color: #fff;
    }
    #ext_asyncmenu tr:not(.ext_separator):not(.ext_disabled):hover td {
      color: inherit;
    }
    #ext_asyncmenu td {
      padding-left: 26px;
      font-size: 12px;
      line-height: 2;
      text-align: left;
      color: #000;
    }
    #ext_asyncmenu td:nth-child(2) {
      padding-right: 26px;
      padding-left: 17px;
      text-align: right;
      color: #999;
    }
    #ext_asyncmenu tr.ext_disabled td {
      color: #999;
    }
    #ext_asyncmenu tr.ext_separator td {
      padding: 0;
    }
    #ext_asyncmenu tr.ext_separator td hr {
      height: 1px;
      border: 0;
      background-color: #e9e9e9;
      margin: 5px 1px;
    }
    @keyframes ext_spinner {
      0%   { transform: rotate(0deg);   }
      100% { transform: rotate(360deg); }
    }
    .ext_spinner {
      width: 16px;
      height: 16px;
      border: 4px solid #4281f4;
      border-right-color: transparent;
      border-radius: 50% !important;
      animation: ext_spinner 1s infinite linear;
    }
  `);

  function createTable(values) {
    const table = _.create('table');
    values.forEach((entry) => {
      if (entry.data === 'spinner') {
        table.appendChild(_.create('tr.ext_disabled', {
          innerHTML: '

	Loading...	',
        }));
      } else if (entry.data === 'separator') {
        table.appendChild(_.create('tr.ext_separator', {
          innerHTML: '
	',
        }));
      } else {
        const row = _.create('tr', {
          innerHTML: `<td>${entry.left || ''}</td><td>${entry.right || ''}</td>`,
        });
        if (entry.data === 'disabled') {
          row.className = 'ext_disabled';
        } else if (entry.click) {
          const onClickFunc = typeof entry.click === 'function' ? entry.click : () => {
            const url = entry.click;
            if (url.startsWith('http:') || url.startsWith('https:')) {
              _.safeWindowOpen(url);
            } else {
              window.location = url;
            }
          };
          row.addEventListener('click', onClickFunc);
        }
        table.appendChild(row);
      }
    });
    return table;
  }

  return class Menu {
    constructor(event, options = {}) {
      this.options = options;
      this.options.logger = this.options.logger ? this.options.logger.push('menu') : _.logger('menu');
      this.options.values = Promise.resolve(this.options.values);

      this.pos = {
        x: event.clientX,
        y: event.clientY,
      };

      this.closeMenu = this.closeMenu.bind(this);
      document.addEventListener('mousedown', this.closeMenu, true);
      document.addEventListener('scroll', this.closeMenu);

      this.openMenu();

      this.options.logger.log('created menu');
    }

    destroy() {
      document.removeEventListener('mousedown', this.closeMenu, true);
      document.removeEventListener('scroll', this.closeMenu);

      this.menuElement.remove();
      this.menuElement = null;

      this.destroyed = true;
      this.options.logger.log('destroyed menu');
    }

    closeMenu(event) {
      if (this.menuElement && !this.menuElement.contains(event.target)) {
        event.preventDefault();
        event.stopPropagation();
        this.destroy();
      }
    }

    openMenu() {
      this.menuElement = document.body.appendChild(_.create('#ext_asyncmenu'));

      this.setContent([{ data: 'spinner' }]);

      this.options.values
        .then((values) => {
          this.setContent(values);
        })
        .catch((error) => {
          this.setContent([{
            data: 'disabled',
            left: 'Error',
            right: error,
          }]);
          this.options.logger.error(error);
        });
    }

    setContent(values) {
      const table = createTable(values);
      this.menuElement.innerHTML = '';
      this.menuElement.appendChild(table);
      this.reposition();
    }

    reposition() {
      let x = this.pos.x;
      let y = this.pos.y;
      const windowSize = _.getSize(window);
      const menuSize = _.getSize(this.menuElement);
      if (windowSize.width - x < menuSize.width) {
        x = windowSize.width - menuSize.width;
      }
      if (windowSize.height - y < menuSize.height) {
        y -= menuSize.height;
      }
      this.menuElement.style.top = `${y}px`;
      this.menuElement.style.left = `${x}px`;
    }
  };
}());

(function main(window) {
  'use strict';

  const LOGGER = _.logger('tv calendar search');
  LOGGER.log('starting...');

  const ICON = `
    <svg xmlns="http://www.w3.org/2000/svg" width="12" height="13">
      <g stroke-width="2" fill="none">
        <path d="M11.29 11.71l-4-4" />
        <circle cx="5" cy="5" r="4" />
      </g>
    </svg>
  `;

  _.addCSS(`
    * {
      border-radius: 0 !important;
    }
    #cal .ep {
      position: relative;
    }
    #cal .ep .ext_searchicon {
      display: flex;
      align-items: center;
      justify-content: center;
      position: absolute;
      top: 0;
      right: 0;
      width: 20px;
      height: 20px;
      margin: 3px;
      padding: 0;
      border: 0;
      background: rgba(255, 255, 255, 0.2);
      opacity: 0.5;
      cursor: pointer;
    }
    #cal .ep .ext_searchicon svg {
      stroke: #fff;
    }
    #cal .ep .ext_searchicon:hover {
      opacity: 0.9;
    }
    #cal .ep p a:nth-of-type(2) {
      text-transform: uppercase;
    }
    #cal.ext_hidewatched .ep.infochecked {
      display: none;
    }
    button.ext_togglewatched {
      border: 0;
      background: #444;
      color: #fff;
      padding: 4px 7px;
      font-size: 11px;
      opacity: 0.5;
      cursor: pointer;
    }
    button.ext_togglewatched:hover {
      opacity: 0.9;
    }
    footer {
      display: none;
    }
  `);

  const API = (function api() {
    let API_OPTIONS = _.getJSON('api-options');

    if (!API_OPTIONS) {
      const data = window.prompt('API JSON data:');
      if (data) {
        API_OPTIONS = JSON.parse(data);
        _.putJSON('api-options', API_OPTIONS);
      }
    }

    function getToken() {
      if (API_OPTIONS.token) {
        if (API_OPTIONS.token.token) {
          if (Date.now() < API_OPTIONS.token.time + API_OPTIONS.token.maxtime) {
            return Promise.resolve(API_OPTIONS.token.token);
          }
        } else {
          const url = _.toURI(API_OPTIONS.endpoint, API_OPTIONS.token.params);
          return _.ajax(url, { logger: LOGGER }).send()
            .then(responseText => JSON.parse(responseText))
            .then((json) => {
              API_OPTIONS.token.token = json[API_OPTIONS.token.token_key];
              API_OPTIONS.token.time = Date.now();
              return API_OPTIONS.token.token;
            });
        }
      }
      return Promise.resolve();
    }

    function getApiRequestUrl(query) {
      return Promise.resolve()
        .then(() => getToken())
        .then((token) => {
          const queryParams = Object.assign({}, API_OPTIONS.search.params);
          queryParams[API_OPTIONS.search.search_key] = query;
          if (token) {
            queryParams[API_OPTIONS.token.token_key] = token;
          }
          return _.toURI(API_OPTIONS.endpoint, queryParams);
        });
    }

    function fetchJSON(query) {
      return Promise.resolve()
        .then(() => getApiRequestUrl(query))
        .then(url => _.ajax(url, { logger: LOGGER }).send())
        .then(response => JSON.parse(response));
    }

    function getResults(query, mustContain) {
      return fetchJSON(query)
        .then((json) => {
          let results = json[API_OPTIONS.results.results_key];
          const numResults = results ? results.length : 0;
          LOGGER.log(`${query} returned ${numResults} results!`);

          if (!numResults) {
            return [{
              left: `<b>No results: ${query}</b>`,
            }];
          }

          if (mustContain) {
            const regexString = mustContain.split(' ').join('(?:[ .-])');
            const regex = new RegExp(`\\b${regexString}\\b`, 'i');
            results = results.filter(result => (
              result[API_OPTIONS.results.name_key].search(regex) !== -1
            ));
            LOGGER.log(`> ${results.length} results contain "${mustContain}"!`);
          }

          if (!results.length) {
            return [{
              left: `<b>No results: ${query}</b>`,
            }];
          }

          results.forEach((result) => {
            result.moment = moment(
              result[API_OPTIONS.results.date_key],
              API_OPTIONS.results.date_format);
          });

          results.sort((a, b) => b[API_OPTIONS.results.sort_key] - a[API_OPTIONS.results.sort_key]);

          const values = [{
            left: `<b>${query}</b>`,
          },
          {
            data: 'separator',
          }];

          results.forEach((result) => {
            values.push({
              left: result[API_OPTIONS.results.name_key],
              right: `${result[API_OPTIONS.results.sort_key]}/${result[API_OPTIONS.results.sort_key2]} @ ${result.moment.format('YYYY-MM-DD HH:mm')}`,
              click: result[API_OPTIONS.results.link_key],
            });
          });

          return values;
        })
        .catch(error => [{
          left: `<b>${error.name}</b>`,
          right: error.message,
        }]);
    }

    return {
      getResults,
    };
  }());

  function createMenu(event, options = {}) {
    return new AsyncMenu(event, options);
  }

  function getFormattedDate(id) {
    const split = id.split('_');
    const isoDate = `${split[3]}-${split[2].padStart(2, '0')}-${split[1].padStart(2, '0')}`;
    const m = moment(isoDate).tz('America/Los_Angeles');
    return m.format('YYYY MM DD');
  }

  function getShowData(showName) {
    return _.getJSON(`showdata-${showName}`, {
      searchName: showName,
      useDate: false,
    });
  }

  function setShowData(showName, storedData) {
    _.putJSON(`showdata-${showName}`, storedData);
  }

  function getQueryData(element) {
    const links = _.all('a', element);

    // remove text in parenthesis eg. "The Voice (US)" -> "The Voice"
    const showName = links[0].textContent.replace(/\(.+\)/g, '').trim();
    const storedData = getShowData(showName);

    let epString;
    if (storedData.useDate) {
      epString = getFormattedDate(element.parentElement.parentElement.id);
    } else {
      epString = links[1].textContent.toUpperCase().trim();
    }

    return {
      mustContain: epString,
      query: `${storedData.searchName} ${epString}`,
      values: [{
        data: 'separator',
      }, {
        left: 'Change search name...',
        click: () => {
          const newName = window.prompt('Search name:', storedData.searchName);
          if (newName) {
            storedData.searchName = newName;
            setShowData(showName, storedData);
          }
        },
      }, {
        left: `Using ${storedData.useDate ? 'date' : 'episode number'} (click to change)`,
        right: epString,
        click: () => {
          storedData.useDate = !storedData.useDate;
          setShowData(showName, storedData);
        },
      }],
    };
  }

  function addLink(element) {
    element.appendChild(_.create('button.ext_searchicon', {
      innerHTML: ICON,
      events: {
        click(event) {
          event.preventDefault();
          const queryData = getQueryData(event.currentTarget.parentElement);
          const values = Promise.resolve()
            .then(() => API.getResults(queryData.query, queryData.mustContain))
            .then(results => [...results, ...queryData.values]);
          createMenu(event, { logger: LOGGER, values });
        },
      },
    }));
  }

  function onToggleButtonClicked() {
    _.get('#cal').classList.toggle('ext_hidewatched');
  }

  function addToggleWatchedButton() {
    const replaceElem = _.get('.replace');
    const button = _.create('button.ext_togglewatched', {
      textContent: 'Toggle Watched',
      events: {
        click: onToggleButtonClicked,
      },
    });
    replaceElem.innerHTML = '';
    replaceElem.appendChild(button);
  }

  document.addEventListener('DOMContentLoaded', () => {
    LOGGER.log('ready', window.location.href);

    const cal = _.get('#cal');
    if (cal) {
      LOGGER.log('calendar detected');
      _.all('.ep > span', cal).forEach(addLink);
      addToggleWatchedButton();
      onToggleButtonClicked();
      const today = _.get('.today', cal);
      if (today) {
        today.classList.add('day');
      }
    }
  });
}(this.unsafeWindow || window));
<commit_msg>fix missing button container element<commit_after>// ==UserScript==
// @name        TV Calendar Search
// @namespace   http://lepko.net/
// @version     1.0.2
// @run-at      document-start
// @match       *://*.pogdesign.co.uk/cat/*
// @require     https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.17.1/moment.min.js
// @require     https://cdnjs.cloudflare.com/ajax/libs/moment-timezone/0.5.11/moment-timezone-with-data-2010-2020.min.js
// @require     https://raw.githubusercontent.com/LepkoQQ/userjs/02a2d6a8d5d9dc06e8c2792c49f652164be49eff/dist/utils.js
// @grant       GM_xmlhttpRequest
// @grant       GM_getValue
// @grant       GM_setValue
// @grant       unsafeWindow
// @nocompat    Chrome
// ==/UserScript==

/* global _:false, moment:false */

/* async menu */
const AsyncMenu = (function asyncMenu() {
  'use strict';

  _.addCSS(`
    #ext_asyncmenu,
    #ext_asyncmenu * {
      box-sizing: border-box;
      cursor: default;
      margin: 0;
      padding: 0;
      user-select: none;
    }
    #ext_asyncmenu {
      position: fixed;
      top: 328px;
      display: inline-block;
      border: 1px solid #bababa;
      background-color: #fff;
      box-shadow: 2px 2px 2px 0px rgba(0, 0, 0, 0.5);
      white-space: nowrap;
      z-index: 2147483647;
    }
    #ext_asyncmenu table {
      margin: 2px -1px;
      border: 0;
      border-spacing: 0;
      border-collapse: collapse;
      font-family: "Segoe UI", sans-serif;
    }
    #ext_asyncmenu tr:not(.ext_separator):not(.ext_disabled):hover {
      background-color: #4281f4;
      color: #fff;
    }
    #ext_asyncmenu tr:not(.ext_separator):not(.ext_disabled):hover td {
      color: inherit;
    }
    #ext_asyncmenu td {
      padding-left: 26px;
      font-size: 12px;
      line-height: 2;
      text-align: left;
      color: #000;
    }
    #ext_asyncmenu td:nth-child(2) {
      padding-right: 26px;
      padding-left: 17px;
      text-align: right;
      color: #999;
    }
    #ext_asyncmenu tr.ext_disabled td {
      color: #999;
    }
    #ext_asyncmenu tr.ext_separator td {
      padding: 0;
    }
    #ext_asyncmenu tr.ext_separator td hr {
      height: 1px;
      border: 0;
      background-color: #e9e9e9;
      margin: 5px 1px;
    }
    @keyframes ext_spinner {
      0%   { transform: rotate(0deg);   }
      100% { transform: rotate(360deg); }
    }
    .ext_spinner {
      width: 16px;
      height: 16px;
      border: 4px solid #4281f4;
      border-right-color: transparent;
      border-radius: 50% !important;
      animation: ext_spinner 1s infinite linear;
    }
    #ext_buttoncontainer {
      text-align: center;
      padding: 5px;
    }
  `);

  function createTable(values) {
    const table = _.create('table');
    values.forEach((entry) => {
      if (entry.data === 'spinner') {
        table.appendChild(_.create('tr.ext_disabled', {
          innerHTML: '

	Loading...	',
        }));
      } else if (entry.data === 'separator') {
        table.appendChild(_.create('tr.ext_separator', {
          innerHTML: '
	',
        }));
      } else {
        const row = _.create('tr', {
          innerHTML: `<td>${entry.left || ''}</td><td>${entry.right || ''}</td>`,
        });
        if (entry.data === 'disabled') {
          row.className = 'ext_disabled';
        } else if (entry.click) {
          const onClickFunc = typeof entry.click === 'function' ? entry.click : () => {
            const url = entry.click;
            if (url.startsWith('http:') || url.startsWith('https:')) {
              _.safeWindowOpen(url);
            } else {
              window.location = url;
            }
          };
          row.addEventListener('click', onClickFunc);
        }
        table.appendChild(row);
      }
    });
    return table;
  }

  return class Menu {
    constructor(event, options = {}) {
      this.options = options;
      this.options.logger = this.options.logger ? this.options.logger.push('menu') : _.logger('menu');
      this.options.values = Promise.resolve(this.options.values);

      this.pos = {
        x: event.clientX,
        y: event.clientY,
      };

      this.closeMenu = this.closeMenu.bind(this);
      document.addEventListener('mousedown', this.closeMenu, true);
      document.addEventListener('scroll', this.closeMenu);

      this.openMenu();

      this.options.logger.log('created menu');
    }

    destroy() {
      document.removeEventListener('mousedown', this.closeMenu, true);
      document.removeEventListener('scroll', this.closeMenu);

      this.menuElement.remove();
      this.menuElement = null;

      this.destroyed = true;
      this.options.logger.log('destroyed menu');
    }

    closeMenu(event) {
      if (this.menuElement && !this.menuElement.contains(event.target)) {
        event.preventDefault();
        event.stopPropagation();
        this.destroy();
      }
    }

    openMenu() {
      this.menuElement = document.body.appendChild(_.create('#ext_asyncmenu'));

      this.setContent([{ data: 'spinner' }]);

      this.options.values
        .then((values) => {
          this.setContent(values);
        })
        .catch((error) => {
          this.setContent([{
            data: 'disabled',
            left: 'Error',
            right: error,
          }]);
          this.options.logger.error(error);
        });
    }

    setContent(values) {
      const table = createTable(values);
      this.menuElement.innerHTML = '';
      this.menuElement.appendChild(table);
      this.reposition();
    }

    reposition() {
      let x = this.pos.x;
      let y = this.pos.y;
      const windowSize = _.getSize(window);
      const menuSize = _.getSize(this.menuElement);
      if (windowSize.width - x < menuSize.width) {
        x = windowSize.width - menuSize.width;
      }
      if (windowSize.height - y < menuSize.height) {
        y -= menuSize.height;
      }
      this.menuElement.style.top = `${y}px`;
      this.menuElement.style.left = `${x}px`;
    }
  };
}());

(function main(window) {
  'use strict';

  const LOGGER = _.logger('tv calendar search');
  LOGGER.log('starting...');

  const ICON = `
    <svg xmlns="http://www.w3.org/2000/svg" width="12" height="13">
      <g stroke-width="2" fill="none">
        <path d="M11.29 11.71l-4-4" />
        <circle cx="5" cy="5" r="4" />
      </g>
    </svg>
  `;

  _.addCSS(`
    * {
      border-radius: 0 !important;
    }
    #cal .ep {
      position: relative;
    }
    #cal .ep .ext_searchicon {
      display: flex;
      align-items: center;
      justify-content: center;
      position: absolute;
      top: 0;
      right: 0;
      width: 20px;
      height: 20px;
      margin: 3px;
      padding: 0;
      border: 0;
      background: rgba(255, 255, 255, 0.2);
      opacity: 0.5;
      cursor: pointer;
    }
    #cal .ep .ext_searchicon svg {
      stroke: #fff;
    }
    #cal .ep .ext_searchicon:hover {
      opacity: 0.9;
    }
    #cal .ep p a:nth-of-type(2) {
      text-transform: uppercase;
    }
    #cal.ext_hidewatched .ep.infochecked {
      display: none;
    }
    button.ext_togglewatched {
      border: 0;
      background: #444;
      color: #fff;
      padding: 4px 7px;
      font-size: 11px;
      opacity: 0.5;
      cursor: pointer;
    }
    button.ext_togglewatched:hover {
      opacity: 0.9;
    }
    footer {
      display: none;
    }
  `);

  const API = (function api() {
    let API_OPTIONS = _.getJSON('api-options');

    if (!API_OPTIONS) {
      const data = window.prompt('API JSON data:');
      if (data) {
        API_OPTIONS = JSON.parse(data);
        _.putJSON('api-options', API_OPTIONS);
      }
    }

    function getToken() {
      if (API_OPTIONS.token) {
        if (API_OPTIONS.token.token) {
          if (Date.now() < API_OPTIONS.token.time + API_OPTIONS.token.maxtime) {
            return Promise.resolve(API_OPTIONS.token.token);
          }
        } else {
          const url = _.toURI(API_OPTIONS.endpoint, API_OPTIONS.token.params);
          return _.ajax(url, { logger: LOGGER }).send()
            .then(responseText => JSON.parse(responseText))
            .then((json) => {
              API_OPTIONS.token.token = json[API_OPTIONS.token.token_key];
              API_OPTIONS.token.time = Date.now();
              return API_OPTIONS.token.token;
            });
        }
      }
      return Promise.resolve();
    }

    function getApiRequestUrl(query) {
      return Promise.resolve()
        .then(() => getToken())
        .then((token) => {
          const queryParams = Object.assign({}, API_OPTIONS.search.params);
          queryParams[API_OPTIONS.search.search_key] = query;
          if (token) {
            queryParams[API_OPTIONS.token.token_key] = token;
          }
          return _.toURI(API_OPTIONS.endpoint, queryParams);
        });
    }

    function fetchJSON(query) {
      return Promise.resolve()
        .then(() => getApiRequestUrl(query))
        .then(url => _.ajax(url, { logger: LOGGER }).send())
        .then(response => JSON.parse(response));
    }

    function getResults(query, mustContain) {
      return fetchJSON(query)
        .then((json) => {
          let results = json[API_OPTIONS.results.results_key];
          const numResults = results ? results.length : 0;
          LOGGER.log(`${query} returned ${numResults} results!`);

          if (!numResults) {
            return [{
              left: `<b>No results: ${query}</b>`,
            }];
          }

          if (mustContain) {
            const regexString = mustContain.split(' ').join('(?:[ .-])');
            const regex = new RegExp(`\\b${regexString}\\b`, 'i');
            results = results.filter(result => (
              result[API_OPTIONS.results.name_key].search(regex) !== -1
            ));
            LOGGER.log(`> ${results.length} results contain "${mustContain}"!`);
          }

          if (!results.length) {
            return [{
              left: `<b>No results: ${query}</b>`,
            }];
          }

          results.forEach((result) => {
            result.moment = moment(
              result[API_OPTIONS.results.date_key],
              API_OPTIONS.results.date_format);
          });

          results.sort((a, b) => b[API_OPTIONS.results.sort_key] - a[API_OPTIONS.results.sort_key]);

          const values = [{
            left: `<b>${query}</b>`,
          },
          {
            data: 'separator',
          }];

          results.forEach((result) => {
            values.push({
              left: result[API_OPTIONS.results.name_key],
              right: `${result[API_OPTIONS.results.sort_key]}/${result[API_OPTIONS.results.sort_key2]} @ ${result.moment.format('YYYY-MM-DD HH:mm')}`,
              click: result[API_OPTIONS.results.link_key],
            });
          });

          return values;
        })
        .catch(error => [{
          left: `<b>${error.name}</b>`,
          right: error.message,
        }]);
    }

    return {
      getResults,
    };
  }());

  function createMenu(event, options = {}) {
    return new AsyncMenu(event, options);
  }

  function getFormattedDate(id) {
    const split = id.split('_');
    const isoDate = `${split[3]}-${split[2].padStart(2, '0')}-${split[1].padStart(2, '0')}`;
    const m = moment(isoDate).tz('America/Los_Angeles');
    return m.format('YYYY MM DD');
  }

  function getShowData(showName) {
    return _.getJSON(`showdata-${showName}`, {
      searchName: showName,
      useDate: false,
    });
  }

  function setShowData(showName, storedData) {
    _.putJSON(`showdata-${showName}`, storedData);
  }

  function getQueryData(element) {
    const links = _.all('a', element);

    // remove text in parenthesis eg. "The Voice (US)" -> "The Voice"
    const showName = links[0].textContent.replace(/\(.+\)/g, '').trim();
    const storedData = getShowData(showName);

    let epString;
    if (storedData.useDate) {
      epString = getFormattedDate(element.parentElement.parentElement.id);
    } else {
      epString = links[1].textContent.toUpperCase().trim();
    }

    return {
      mustContain: epString,
      query: `${storedData.searchName} ${epString}`,
      values: [{
        data: 'separator',
      }, {
        left: 'Change search name...',
        click: () => {
          const newName = window.prompt('Search name:', storedData.searchName);
          if (newName) {
            storedData.searchName = newName;
            setShowData(showName, storedData);
          }
        },
      }, {
        left: `Using ${storedData.useDate ? 'date' : 'episode number'} (click to change)`,
        right: epString,
        click: () => {
          storedData.useDate = !storedData.useDate;
          setShowData(showName, storedData);
        },
      }],
    };
  }

  function addLink(element) {
    element.appendChild(_.create('button.ext_searchicon', {
      innerHTML: ICON,
      events: {
        click(event) {
          event.preventDefault();
          const queryData = getQueryData(event.currentTarget.parentElement);
          const values = Promise.resolve()
            .then(() => API.getResults(queryData.query, queryData.mustContain))
            .then(results => [...results, ...queryData.values]);
          createMenu(event, { logger: LOGGER, values });
        },
      },
    }));
  }

  function onToggleButtonClicked() {
    _.get('#cal').classList.toggle('ext_hidewatched');
  }

  function addToggleWatchedButton(cal) {
    const replaceElem = cal.parentElement.insertBefore(_.create('#ext_buttoncontainer'), cal);
    const button = _.create('button.ext_togglewatched', {
      textContent: 'Toggle Watched',
      events: {
        click: onToggleButtonClicked,
      },
    });
    replaceElem.innerHTML = '';
    replaceElem.appendChild(button);
  }

  document.addEventListener('DOMContentLoaded', () => {
    LOGGER.log('ready', window.location.href);

    const cal = _.get('#cal');
    if (cal) {
      LOGGER.log('calendar detected');
      _.all('.ep > span', cal).forEach(addLink);
      addToggleWatchedButton(cal);
      onToggleButtonClicked();
      const today = _.get('.today', cal);
      if (today) {
        today.classList.add('day');
      }
    }
  });
}(this.unsafeWindow || window));
<|endoftext|>


<commit_before>"use strict";
/**
 *
 */
App.CategorySerializer = App.Serializer.extend({});

/**
 *
 */
App.CategoryController = Ember.ObjectController.extend(
{
  actions:
  {
    show: function(product)
    {
      this.transitionToRoute('product', product);//'product', product.id);
    },
    toggle: function(id)
    {
      Ember.$('ul[data-catid=' + id + ']').prev().toggleClass('down');
      Ember.$('ul[data-catid=' + id + ']').toggle();
    }
  }
});
<commit_msg>quick health check when opening up a category tree<commit_after>"use strict";
/**
 *
 */
App.CategorySerializer = App.Serializer.extend({});

/**
 *
 */
App.CategoryController = Ember.ObjectController.extend(
{
  needs: ['user'],

  actions:
  {
    show: function(product)
    {
      this.transitionToRoute('product', product);//'product', product.id);
    },
    toggle: function(id)
    {
      this.get('controllers.user').send('healthCheck');

      Ember.$('ul[data-catid=' + id + ']').prev().toggleClass('down');
      Ember.$('ul[data-catid=' + id + ']').toggle();
    }
  }
});
<|endoftext|>


<commit_before>// System modules.
var fs = require('fs');
var path = require('path');

// Third-party modules.
var et = require('elementtree');
var shelljs = require('shelljs');
var Q = require('q');

var utils = require('./utils');
var cordova = require('cordova');
var cordovaLib = cordova.cordova_lib;

// Returns a promise.
module.exports = exports = function postPrepareCommand(opts) {
  var hasAndroid = fs.existsSync(path.join('platforms', 'android'));
  var hasIos = fs.existsSync(path.join('platforms', 'ios'));

  if (!fs.existsSync('platforms')) {
    return Q.reject('No platforms directory found. Please run script from the root of your project.');
  }

  var p = Q();
  if (hasAndroid) {
    p = p.then(function() { return postPrepareInternal('android'); });
  }
  if (hasIos) {
    p = p.then(function() { return postPrepareInternal('ios'); });
  }
  return p;
};

// Internal function called potentially multiple times to cover all platforms.
function postPrepareInternal(platform) {
  var root = utils.assetDirForPlatform(platform);

  /* Android asset packager ignores, by default, directories beginning with
     underscores. This can be fixed with an update to the project.properties
     file, but only when compiling with ant. There is a bug outstanding to
     fix this behaviour in Eclipse/ADT as well.

     References:
       https://code.google.com/p/android/issues/detail?id=5343
       https://code.google.com/p/android/issues/detail?id=41237
   */
  var badPath = path.join(utils.assetDirForPlatform(platform), '_locales');
  var betterPath = path.join(utils.assetDirForPlatform(platform), 'CCA_locales');
  var promise = Q();
  if (fs.existsSync(badPath)) {
    console.log('## Pre-processing _locales for ' + platform);
    fs.renameSync(badPath, betterPath);
    promise = Q.ninvoke(fs, 'readdir', betterPath)
    .then(function(files) {
      for (var i=0; i<files.length; i++) {
        var fullName = path.join(betterPath, files[i]);
        var adjustedFilename= files[i].replace('-', '_').toLowerCase();
        if (files[i] !== adjustedFilename) {
          var stats = fs.statSync(fullName);
          if (stats.isDirectory()) {
            fs.renameSync(fullName, path.join(betterPath, adjustedFilename));
          }
        }
      }
    });
  }

  return promise.then(function() {
    return require('./get-manifest')('www', platform);
  }).then(function(manifest) {
    if (!manifest || !manifest.icons) return;
    var iconMap = {};
    var iPhoneFiles = {
        'icon-40': true,
        'icon-small': true,
        'icon.png': true,
        'icon@2x': true,
        'icon-72': true,
        'icon-72@2x': true
    };
    var iPadFiles = {
        'icon-small': true,
        'icon-40': true,
        'icon-50': true,
        'icon-76': true,
        'icon': true,
        'icon@2x': true,
        'icon-72': true,
        'icon-72@2x': true
    };
    var infoPlistXml = null;
    var infoPlistPath = null;
    var iosIconDir = null;

    if (platform === "android") {
      iconMap = {
        "36": [path.join('res','drawable-ldpi','icon.png')],
        "48": [path.join('res','drawable-mdpi','icon.png')],
        "72": [path.join('res','drawable-hdpi','icon.png')],
        "96": [path.join('res','drawable-xhdpi','icon.png')],
        "144": [path.join('res','drawable-xxhdpi','icon.png')],
        "192": [path.join('res','drawable-xxxhdpi','icon.png')]
      };
    } else if (platform === "ios") {
      var platforms = cordovaLib.cordova_platforms;
      var parser = new platforms.ios.parser(path.join('platforms','ios'));
      iconMap = {
        "-1": [path.join(parser.originalName, 'Resources','icons','icon-60.png')], // this file exists in the template but isn't used.
        "29": [path.join(parser.originalName, 'Resources','icons','icon-small.png')],
        "40": [path.join(parser.originalName, 'Resources','icons','icon-40.png')],
        "50": [path.join(parser.originalName, 'Resources','icons','icon-50.png')],
        "57": [path.join(parser.originalName, 'Resources','icons','icon.png')],
        "58": [path.join(parser.originalName, 'Resources','icons','icon-small@2x.png')],
        "72": [path.join(parser.originalName, 'Resources','icons','icon-72.png')],
        "76": [path.join(parser.originalName, 'Resources','icons','icon-76.png')],
        "80": [path.join(parser.originalName, 'Resources','icons','icon-40@2x.png')],
        "100": [path.join(parser.originalName, 'Resources','icons','icon-50@2x.png')],
        "114": [path.join(parser.originalName, 'Resources','icons','icon@2x.png')],
        "120": [path.join(parser.originalName, 'Resources','icons','icon-60@2x.png')],
        "144": [path.join(parser.originalName, 'Resources','icons','icon-72@2x.png')],
        "152": [path.join(parser.originalName, 'Resources','icons','icon-76@2x.png')]
      };
      infoPlistPath = path.join('platforms', 'ios', parser.originalName, parser.originalName + '-Info.plist');
      infoPlistXml = et.parse(fs.readFileSync(infoPlistPath, 'utf-8'));
      iosIconDir = path.join(parser.originalName, 'Resources', 'icons');
    }
    function copyIcon(size, dstPath) {
      shelljs.mkdir('-p', path.dirname(dstPath));
      shelljs.cp('-f', path.join('www', utils.fixPathSlashes(manifest.icons[size])), dstPath);
      if (shelljs.error()) {
        console.log("Error copying " + size + "px icon file: " + shelljs.error());
      }
    }
    var missingIcons = [];
    var dstPath;
    if (iconMap) {
      //console.log('## Copying icons for ' + platform);
      for (var size in iconMap) {
        for (var i=0; i < iconMap[size].length; i++) {
          dstPath = path.join('platforms', platform, iconMap[size][i]);
          if (manifest.icons[size]) {
            //console.log("Copying " + size + "px icon file");
            copyIcon(size, dstPath);
          } else {
            missingIcons.push(dstPath);
          }
        }
      }
      // Find the largest icon.
      var bestSize = '0';
      for (size in manifest.icons) {
        bestSize = +size > +bestSize ? size : bestSize;
      }
      missingIcons.forEach(function(dstPath) {
        var imgName = path.basename(dstPath).replace(/\..*?$/, '');
        // Leave at least one icon.
        if (imgName != 'icon') {
          delete iPadFiles[imgName];
          delete iPhoneFiles[imgName];
        }
        // TODO: need to remove the iOS assets from the Xcode project file (ugh).
        if (platform == 'android') {
          shelljs.rm('-f', dstPath);
        } else if (platform == 'ios') {
          // Fill in all missing iOS icons with the largest resolution we have.
          copyIcon(bestSize, dstPath);
        }
      });
      // Use the largest icon as the default Android one.
      if (platform == 'android') {
        dstPath = path.join('platforms', platform, 'res', 'drawable', 'icon.png');
        copyIcon(bestSize, dstPath);
      }
      if (infoPlistXml) {
        var findArrayNode = function(key) {
          var foundNode = null;
          var foundKey = 0;
          infoPlistXml.iter('*', function(e) {
            if (foundKey === 0) {
              if (e.text == key) {
                foundKey = 1;
              }
            } else if (foundKey == 1) {
              if (e.text == 'CFBundleIconFiles') {
                foundKey = 2;
              }
            } else if (foundKey == 2) {
              if (e.tag == 'array') {
                foundNode = e;
                foundKey = 3;
              }
            }
          });
          return foundNode;
        };
        var setValues = function(key, vals) {
          var node = findArrayNode(key);
          node.clear();
          for (var imgName in vals) {
            et.SubElement(node, 'string').text = imgName;
          }
        };
        setValues('CFBundleIcons', iPhoneFiles);
        setValues('CFBundleIcons~ipad', iPadFiles);
        fs.writeFileSync(infoPlistPath, et.tostring(infoPlistXml.getroot(), {indent: 8}), 'utf8');
      }
    }
  })
  // Merge the manifests.
  .then(function() {
    return require('./get-manifest')('www', platform)
      .then(function(manifest) {
        return Q.ninvoke(fs, 'writeFile', path.join(root, 'manifest.json'), JSON.stringify(manifest))
          .then(function() {
            if (platform === 'android' && manifest) {
              // Write manifest.short_name as launcher_name in Android strings.xml
              if (manifest.short_name) {
                var stringsPath = path.join('platforms', 'android', 'res', 'values', 'strings.xml');
                var strings = et.parse(fs.readFileSync(stringsPath, 'utf-8'));
                strings.find('./string/[@name="launcher_name"]').text = manifest.short_name;
                fs.writeFileSync(stringsPath, strings.write({indent: 4}), 'utf-8');
              }

              // Update Android Theme to Translucent
              var androidManifestPath = path.join('platforms', 'android', 'AndroidManifest.xml');
              var androidManifest = et.parse(fs.readFileSync(androidManifestPath, 'utf-8'));
              var theme = manifest.androidTheme || "@android:style/Theme.Translucent";
              androidManifest.find('./application/activity').attrib["android:theme"] = theme;
              fs.writeFileSync(androidManifestPath, androidManifest.write({indent: 4}), 'utf-8');
            }
          });
      });
  });
}
<commit_msg>Pretty print `manifest.json` within `platforms/` directory<commit_after>// System modules.
var fs = require('fs');
var path = require('path');

// Third-party modules.
var et = require('elementtree');
var shelljs = require('shelljs');
var Q = require('q');

var utils = require('./utils');
var cordova = require('cordova');
var cordovaLib = cordova.cordova_lib;

// Returns a promise.
module.exports = exports = function postPrepareCommand(opts) {
  var hasAndroid = fs.existsSync(path.join('platforms', 'android'));
  var hasIos = fs.existsSync(path.join('platforms', 'ios'));

  if (!fs.existsSync('platforms')) {
    return Q.reject('No platforms directory found. Please run script from the root of your project.');
  }

  var p = Q();
  if (hasAndroid) {
    p = p.then(function() { return postPrepareInternal('android'); });
  }
  if (hasIos) {
    p = p.then(function() { return postPrepareInternal('ios'); });
  }
  return p;
};

// Internal function called potentially multiple times to cover all platforms.
function postPrepareInternal(platform) {
  var root = utils.assetDirForPlatform(platform);

  /* Android asset packager ignores, by default, directories beginning with
     underscores. This can be fixed with an update to the project.properties
     file, but only when compiling with ant. There is a bug outstanding to
     fix this behaviour in Eclipse/ADT as well.

     References:
       https://code.google.com/p/android/issues/detail?id=5343
       https://code.google.com/p/android/issues/detail?id=41237
   */
  var badPath = path.join(utils.assetDirForPlatform(platform), '_locales');
  var betterPath = path.join(utils.assetDirForPlatform(platform), 'CCA_locales');
  var promise = Q();
  if (fs.existsSync(badPath)) {
    console.log('## Pre-processing _locales for ' + platform);
    fs.renameSync(badPath, betterPath);
    promise = Q.ninvoke(fs, 'readdir', betterPath)
    .then(function(files) {
      for (var i=0; i<files.length; i++) {
        var fullName = path.join(betterPath, files[i]);
        var adjustedFilename= files[i].replace('-', '_').toLowerCase();
        if (files[i] !== adjustedFilename) {
          var stats = fs.statSync(fullName);
          if (stats.isDirectory()) {
            fs.renameSync(fullName, path.join(betterPath, adjustedFilename));
          }
        }
      }
    });
  }

  return promise.then(function() {
    return require('./get-manifest')('www', platform);
  }).then(function(manifest) {
    if (!manifest || !manifest.icons) return;
    var iconMap = {};
    var iPhoneFiles = {
        'icon-40': true,
        'icon-small': true,
        'icon.png': true,
        'icon@2x': true,
        'icon-72': true,
        'icon-72@2x': true
    };
    var iPadFiles = {
        'icon-small': true,
        'icon-40': true,
        'icon-50': true,
        'icon-76': true,
        'icon': true,
        'icon@2x': true,
        'icon-72': true,
        'icon-72@2x': true
    };
    var infoPlistXml = null;
    var infoPlistPath = null;
    var iosIconDir = null;

    if (platform === "android") {
      iconMap = {
        "36": [path.join('res','drawable-ldpi','icon.png')],
        "48": [path.join('res','drawable-mdpi','icon.png')],
        "72": [path.join('res','drawable-hdpi','icon.png')],
        "96": [path.join('res','drawable-xhdpi','icon.png')],
        "144": [path.join('res','drawable-xxhdpi','icon.png')],
        "192": [path.join('res','drawable-xxxhdpi','icon.png')]
      };
    } else if (platform === "ios") {
      var platforms = cordovaLib.cordova_platforms;
      var parser = new platforms.ios.parser(path.join('platforms','ios'));
      iconMap = {
        "-1": [path.join(parser.originalName, 'Resources','icons','icon-60.png')], // this file exists in the template but isn't used.
        "29": [path.join(parser.originalName, 'Resources','icons','icon-small.png')],
        "40": [path.join(parser.originalName, 'Resources','icons','icon-40.png')],
        "50": [path.join(parser.originalName, 'Resources','icons','icon-50.png')],
        "57": [path.join(parser.originalName, 'Resources','icons','icon.png')],
        "58": [path.join(parser.originalName, 'Resources','icons','icon-small@2x.png')],
        "72": [path.join(parser.originalName, 'Resources','icons','icon-72.png')],
        "76": [path.join(parser.originalName, 'Resources','icons','icon-76.png')],
        "80": [path.join(parser.originalName, 'Resources','icons','icon-40@2x.png')],
        "100": [path.join(parser.originalName, 'Resources','icons','icon-50@2x.png')],
        "114": [path.join(parser.originalName, 'Resources','icons','icon@2x.png')],
        "120": [path.join(parser.originalName, 'Resources','icons','icon-60@2x.png')],
        "144": [path.join(parser.originalName, 'Resources','icons','icon-72@2x.png')],
        "152": [path.join(parser.originalName, 'Resources','icons','icon-76@2x.png')]
      };
      infoPlistPath = path.join('platforms', 'ios', parser.originalName, parser.originalName + '-Info.plist');
      infoPlistXml = et.parse(fs.readFileSync(infoPlistPath, 'utf-8'));
      iosIconDir = path.join(parser.originalName, 'Resources', 'icons');
    }
    function copyIcon(size, dstPath) {
      shelljs.mkdir('-p', path.dirname(dstPath));
      shelljs.cp('-f', path.join('www', utils.fixPathSlashes(manifest.icons[size])), dstPath);
      if (shelljs.error()) {
        console.log("Error copying " + size + "px icon file: " + shelljs.error());
      }
    }
    var missingIcons = [];
    var dstPath;
    if (iconMap) {
      //console.log('## Copying icons for ' + platform);
      for (var size in iconMap) {
        for (var i=0; i < iconMap[size].length; i++) {
          dstPath = path.join('platforms', platform, iconMap[size][i]);
          if (manifest.icons[size]) {
            //console.log("Copying " + size + "px icon file");
            copyIcon(size, dstPath);
          } else {
            missingIcons.push(dstPath);
          }
        }
      }
      // Find the largest icon.
      var bestSize = '0';
      for (size in manifest.icons) {
        bestSize = +size > +bestSize ? size : bestSize;
      }
      missingIcons.forEach(function(dstPath) {
        var imgName = path.basename(dstPath).replace(/\..*?$/, '');
        // Leave at least one icon.
        if (imgName != 'icon') {
          delete iPadFiles[imgName];
          delete iPhoneFiles[imgName];
        }
        // TODO: need to remove the iOS assets from the Xcode project file (ugh).
        if (platform == 'android') {
          shelljs.rm('-f', dstPath);
        } else if (platform == 'ios') {
          // Fill in all missing iOS icons with the largest resolution we have.
          copyIcon(bestSize, dstPath);
        }
      });
      // Use the largest icon as the default Android one.
      if (platform == 'android') {
        dstPath = path.join('platforms', platform, 'res', 'drawable', 'icon.png');
        copyIcon(bestSize, dstPath);
      }
      if (infoPlistXml) {
        var findArrayNode = function(key) {
          var foundNode = null;
          var foundKey = 0;
          infoPlistXml.iter('*', function(e) {
            if (foundKey === 0) {
              if (e.text == key) {
                foundKey = 1;
              }
            } else if (foundKey == 1) {
              if (e.text == 'CFBundleIconFiles') {
                foundKey = 2;
              }
            } else if (foundKey == 2) {
              if (e.tag == 'array') {
                foundNode = e;
                foundKey = 3;
              }
            }
          });
          return foundNode;
        };
        var setValues = function(key, vals) {
          var node = findArrayNode(key);
          node.clear();
          for (var imgName in vals) {
            et.SubElement(node, 'string').text = imgName;
          }
        };
        setValues('CFBundleIcons', iPhoneFiles);
        setValues('CFBundleIcons~ipad', iPadFiles);
        fs.writeFileSync(infoPlistPath, et.tostring(infoPlistXml.getroot(), {indent: 8}), 'utf8');
      }
    }
  })
  // Merge the manifests.
  .then(function() {
    return require('./get-manifest')('www', platform)
      .then(function(manifest) {
        return Q.ninvoke(fs, 'writeFile', path.join(root, 'manifest.json'), JSON.stringify(manifest, null, 4))
          .then(function() {
            if (platform === 'android' && manifest) {
              // Write manifest.short_name as launcher_name in Android strings.xml
              if (manifest.short_name) {
                var stringsPath = path.join('platforms', 'android', 'res', 'values', 'strings.xml');
                var strings = et.parse(fs.readFileSync(stringsPath, 'utf-8'));
                strings.find('./string/[@name="launcher_name"]').text = manifest.short_name;
                fs.writeFileSync(stringsPath, strings.write({indent: 4}), 'utf-8');
              }

              // Update Android Theme to Translucent
              var androidManifestPath = path.join('platforms', 'android', 'AndroidManifest.xml');
              var androidManifest = et.parse(fs.readFileSync(androidManifestPath, 'utf-8'));
              var theme = manifest.androidTheme || "@android:style/Theme.Translucent";
              androidManifest.find('./application/activity').attrib["android:theme"] = theme;
              fs.writeFileSync(androidManifestPath, androidManifest.write({indent: 4}), 'utf-8');
            }
          });
      });
  });
}
<|endoftext|>


<commit_before>
exports.Base = require('./base');
exports.Dot = require('./dot');
exports.Doc = require('./doc');
exports.TAP = require('./tap');
exports.JSON = require('./json');
exports.HTML = require('./html');
exports.List = require('./list');
exports.Min = require('./min');
exports.Spec = require('./spec');
exports.Nyan = require('./nyan');
exports.XUnit = require('./xunit');
exports.Progress = require('./progress');
exports.Landing = require('./landing');
exports.JSONCov = require('./json-cov');
exports.HTMLCov = require('./html-cov');
exports.JSONStream = require('./json-stream');
exports.Teamcity = require('./teamcity');
<commit_msg>add missing Markdown export<commit_after>
exports.Base = require('./base');
exports.Dot = require('./dot');
exports.Doc = require('./doc');
exports.TAP = require('./tap');
exports.JSON = require('./json');
exports.HTML = require('./html');
exports.List = require('./list');
exports.Min = require('./min');
exports.Spec = require('./spec');
exports.Nyan = require('./nyan');
exports.XUnit = require('./xunit');
exports.Markdown = require('./markdown');
exports.Progress = require('./progress');
exports.Landing = require('./landing');
exports.JSONCov = require('./json-cov');
exports.HTMLCov = require('./html-cov');
exports.JSONStream = require('./json-stream');
exports.Teamcity = require('./teamcity');
<|endoftext|>


<commit_before>const path = require('path')
const webpack = require('webpack')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const OfflinePlugin = require('offline-plugin')

module.exports = require('./webpack.base.babel')({
  // Skip hot-loading in production
  entry: [
    path.join(process.cwd(), 'app/app.js')
  ],

  // Utilize long-term caching by adding content hashes to compiled assets
  output: {
    filename: '[name].[chunkhash].js',
    chunkFilename: '[name].[chunkhash].chunk.js'
  },

  plugins: [
    new webpack.optimize.CommonsChunkPlugin({
      name: 'vendor',
      children: true,
      minChunks: 2,
      async: true
    }),

    // Minify and optimize index.html
    new HtmlWebpackPlugin({
      template: 'app/index.html',
      minify: {
        removeComments: true,
        collapseWhitespace: true,
        removeRedundantAttributes: true,
        useShortDoctype: true,
        removeEmptyAttributes: true,
        removeStyleLinkTypeAttributes: true,
        keepClosingSlash: true,
        minifyJS: true,
        minifyCSS: true,
        minifyURLs: true
      },
      inject: true
    }),

    new OfflinePlugin({
      relativePaths: false,
      publicPath: '/',
      excludes: ['.htaccess'],
      caches: {
        main: [':rest:'],

        // All chunks marked as `additional`, loaded after main section
        // and do not prevent SW to install. Change to `optional` if
        // you do not want them to be preloaded at all (cached on first load)
        additional: ['*.chunk.js']
      },
      safeToUseOptionalCaches: true,
      AppCache: false
    })
  ],

  performance: {
    assetFilter: (assetFilename) => !(/(\.map$)|(^(main\.|favicon\.))/.test(assetFilename))
  }
})
<commit_msg>decrease bundle size<commit_after>const path = require('path')
const webpack = require('webpack')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const OfflinePlugin = require('offline-plugin')

module.exports = require('./webpack.base.babel')({
  // Skip hot-loading in production
  entry: [
    path.join(process.cwd(), 'app/app.js')
  ],

  // Utilize long-term caching by adding content hashes to compiled assets
  output: {
    filename: '[name].[chunkhash].js',
    chunkFilename: '[name].[chunkhash].chunk.js'
  },

  plugins: [
    new webpack.LoaderOptionsPlugin({
      minimize: true,
      debug: false
    }),

    new webpack.optimize.UglifyJsPlugin({
      compress: {
        unused: true,
        dead_code: true,
        warnings: false
      }
    }),

    new webpack.optimize.CommonsChunkPlugin({
      name: 'vendor',
      children: true,
      minChunks: 2,
      async: true
    }),

    // Minify and optimize index.html
    new HtmlWebpackPlugin({
      template: 'app/index.html',
      minify: {
        removeComments: true,
        collapseWhitespace: true,
        removeRedundantAttributes: true,
        useShortDoctype: true,
        removeEmptyAttributes: true,
        removeStyleLinkTypeAttributes: true,
        keepClosingSlash: true,
        minifyJS: true,
        minifyCSS: true,
        minifyURLs: true
      },
      inject: true
    }),

    new OfflinePlugin({
      relativePaths: false,
      publicPath: '/',
      excludes: ['.htaccess'],
      caches: {
        main: [':rest:'],

        // All chunks marked as `additional`, loaded after main section
        // and do not prevent SW to install. Change to `optional` if
        // you do not want them to be preloaded at all (cached on first load)
        additional: ['*.chunk.js']
      },
      safeToUseOptionalCaches: true,
      AppCache: false
    })
  ],

  performance: {
    assetFilter: (assetFilename) => !(/(\.map$)|(^(main\.|favicon\.))/.test(assetFilename))
  }
})
<|endoftext|>


<commit_before>const tsc = require('typescript');
const tsConfig = require('./tsconfig.json');

module.exports = {
	process(src, path) {
		if (path.endsWith('.ts') || path.endsWith('.tsx')) {
			return tsc.transpile(src, tsConfig.compilerOptions, path, []);
		}
		return src;
	},
};<commit_msg>Remove preprocessor.js - no longer required due to ts-jest<commit_after><|endoftext|>


<commit_before>/*
 * response-stream.js: A Stream focused on writing any relevant information to
 * a raw http.ServerResponse object.
 *
 * (C) 2011, Nodejitsu Inc.
 * MIT LICENSE
 *
 */

var util = require('util'),
    HttpStream = require('./http-stream');

//
// ### function RequestStream (options)
//
//
var ResponseStream = module.exports = function (options) {
  options = options || {};
  HttpStream.call(this, options);

  this.writeable = true;
  this.response = options.response;
  this.once('pipe', this.pipeResponse);
};

util.inherits(ResponseStream, HttpStream);

ResponseStream.prototype.writeHead = function (statusCode, headers) {
  if (headers) {
    for (var key in headers) {
      this.response.setHeader(key, headers[key]);
    }
  }

  this.response.statusCode = statusCode;
};

ResponseStream.prototype.end = function (data) {
  if (data && this.writable) {
    this.emit('data', data);
  }

  this.modified = true;
  this.emit('end');
};

ResponseStream.prototype.write = function (data) {
  this.modified = true;

  if (this.writable) {
    this.emit('data', data);
  }
};<commit_msg>[api fix] Remove bad `pipe` handler and setup pass-thru for `*Header` events<commit_after>/*
 * response-stream.js: A Stream focused on writing any relevant information to
 * a raw http.ServerResponse object.
 *
 * (C) 2011, Nodejitsu Inc.
 * MIT LICENSE
 *
 */

var util = require('util'),
    HttpStream = require('./http-stream');

//
// ### function RequestStream (options)
//
//
var ResponseStream = module.exports = function (options) {
  options = options || {};
  HttpStream.call(this, options);

  this.writeable = true;
  this.response = options.response;
};

util.inherits(ResponseStream, HttpStream);

ResponseStream.prototype.writeHead = function (statusCode, headers) {
  if (headers) {
    for (var key in headers) {
      this.response.setHeader(key, headers[key]);
    }
  }

  this.response.statusCode = statusCode;
};

//
// Create pass-thru for the necessary
// `http.ServerResponse` methods.
//
['setHeader', 'getHeader', 'removeHeader'].forEach(function (method) {
  ResponseStream.prototype[method] = function () {
    return this.response[method].apply(this.response, arguments);
  };
});

ResponseStream.prototype.end = function (data) {
  if (data && this.writable) {
    this.emit('data', data);
  }

  this.modified = true;
  this.emit('end');
};

ResponseStream.prototype.write = function (data) {
  this.modified = true;

  if (this.writable) {
    this.emit('data', data);
  }
};<|endoftext|>


<commit_before>/* ***** BEGIN LICENSE BLOCK *****
 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is MiniTrayR extension
 *
 * The Initial Developer of the Original Code is
 * Nils Maier.
 * Portions created by the Initial Developer are Copyright (C) 2008
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 *   Nils Maier <MaierMan@web.de>
 *
 * Alternatively, the contents of this file may be used under the terms of
 * either the GNU General Public License Version 2 or later (the "GPL"), or
 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the MPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the MPL, the GPL or the LGPL.
 *
 * ***** END LICENSE BLOCK ***** */

var gMinTrayR = {};
addEventListener(
  "load",
  function() {
    removeEventListener("load", arguments.callee, true);

    Components.utils.import("resource://mintrayr/mintrayr.jsm", gMinTrayR);
    gMinTrayR = new gMinTrayR.MinTrayR(
      document.getElementById('MinTrayR_context'),
      "messenger.watchmessenger",
      function() {
        try {
          // Seamonkey hack
          let n = document.querySelector('#menu_NewPopup menuitem');
          if (n && !n.id)  {
            n.id = 'newNewMsgCmd';
          }
        }
        catch (ex) {
          // no-op
        }
        this.cloneToMenu('MinTrayR_sep-top', ['newNewMsgCmd'], false);
        this.cloneToMenu('MinTrayR_sep-bottom', ['menu_FileQuitItem'], true);
        document
          .getElementById('MinTrayR_newNewMsgCmd')
          .setAttribute(
            'label',
            this.menu.getAttribute('mintrayr_newmessage')
          );
      });
  },
  true
);
<commit_msg>Add "Get new mail" and "Address book" (non-mac) menu items<commit_after>/* ***** BEGIN LICENSE BLOCK *****
 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is MiniTrayR extension
 *
 * The Initial Developer of the Original Code is
 * Nils Maier.
 * Portions created by the Initial Developer are Copyright (C) 2008
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 *   Nils Maier <MaierMan@web.de>
 *
 * Alternatively, the contents of this file may be used under the terms of
 * either the GNU General Public License Version 2 or later (the "GPL"), or
 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the MPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the MPL, the GPL or the LGPL.
 *
 * ***** END LICENSE BLOCK ***** */

var gMinTrayR = {};
addEventListener(
  "load",
  function() {
    removeEventListener("load", arguments.callee, true);

    Components.utils.import("resource://mintrayr/mintrayr.jsm", gMinTrayR);
    gMinTrayR = new gMinTrayR.MinTrayR(
      document.getElementById('MinTrayR_context'),
      "messenger.watchmessenger",
      function() {
        try {
          // Seamonkey hack
          let n = document.querySelector('#menu_NewPopup menuitem');
          if (n && !n.id)  {
            n.id = 'newNewMsgCmd';
          }
        }
        catch (ex) {
          // no-op
        }
        this.cloneToMenu('MinTrayR_sep-top', ["newNewMsgCmd", "button-getAllNewMsg", "addressBook"], false);
        this.cloneToMenu('MinTrayR_sep-bottom', ['menu_FileQuitItem'], true);
        document
          .getElementById('MinTrayR_newNewMsgCmd')
          .setAttribute(
            'label',
            this.menu.getAttribute('mintrayr_newmessage')
          );
      });
  },
  true
);
<|endoftext|>


<commit_before>function validateJsonBody(input_area) {
    var contentType = $("#id_mock_content_type").val();

    if (contentType == "application/json;charset=UTF-8") {
        var jsonInput = $("#json_body");

        if (jsonInput.val().length > 0) {
            jsonInput.validateJSON({
                compress: false,
                reformat: true,
                'onSuccess': function (json) {
                    jsonInput.parent().removeClass('has-error').addClass('has-success');
                },
                'onError': function (error) {
                    jsonInput.parent().addClass('has-error');
                    xx = input_area.selectionStart;
                    yy = input_area.selectionEnd = xx + 100
                    jsonInput.selectRange(xx, yy);
                }
            });
        }
    }
    if (contentType == "text/xml") {

    }
    if (contentType == "text/html") {

    }
}

function httpGet()
{
    var theUrl = document.getElementById('mock_request_url');
    var urlInput = theUrl.value;
    var xmlHttp = new XMLHttpRequest();
    xmlHttp.open( "GET", urlInput, false );
    xmlHttp.send( null );
    document.getElementById('mock_data_response').value = xmlHttp.responseText;
}

function urlChanged() {
    var theUrl = document.getElementById('mock_request_url');
    var c = theUrl.value;
    theUrl.value = c;
    if (isURL(c) == false) {
       var getBtn = document.getElementById('get_button');
        getBtn.disabled = true;
    } else {
        var getBtn = document.getElementById('get_button');
        getBtn.disabled = false;
    }
}

function isURL(textval) {
    var urlregex = /^(https?|ftp):\/\/([a-zA-Z0-9.-]+(:[a-zA-Z0-9.&%$-]+)*@)*((25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9][0-9]?)(\.(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])){3}|([a-zA-Z0-9-]+\.)*[a-zA-Z0-9-]+\.(com|edu|gov|int|mil|net|org|biz|arpa|info|name|pro|aero|coop|museum|[a-zA-Z]{2}))(:[0-9]+)*(\/($|[a-zA-Z0-9.,?'\\+&%$#=~_-]+))*$/;
    return urlregex.test(textval);
}

function getCloningData() {
    $("#form_form").attr("action","/mock/clone");
    $("#form_form").attr("method","get");
    document.getElementById("form_form").submit();
}

function addMoreRow() {
    var new_row =
    "\

\

\
            Mock Request Name\
            <input class='form-control' name='mock_name_xx' placeholder='Mock name' type='text'>\

\

\
            Mock URL\
            <input class='form-control' name='mock_url_xx' placeholder='Mock URL' type='url'>\

\

\
            Test Env\
            <select class='form-control' name='mock_environment_xx'>\
              <option id='integration'>integration</option>\
              <option id='production'>production</option>\
              <option id='quality'>quality</option>\
            </select>\

\
            <button class='btn btn-primary' type = 'button' row='xx' id= 'delete_button_xx' onclick= 'deleteRow(this)'> Delete</button>\
            Pending

\

"

    var first_row=$('div[id^="row_"]').last().attr('id');
    var row_name_split_arr = (first_row.split("_"));
    var current_row_number = parseInt(row_name_split_arr[row_name_split_arr.length - 1]) + 1;
    var row = new_row.replace(/xx/g,current_row_number);

    $("#row_1").append(row)
}

function deleteRow(row) {
    var button_row = $(row).attr('row');
    var row = parseInt(button_row);
    $("#row_"+row).empty();
    $("#row_"+row).remove();
}

function validateBatchCloneData() {
    mock_names = $('input[name^="mock_name_"]');
    mock_urls = $('input[name^="mock_url_"]');
    mock_envs = $('select[name^="mock_environment_"]');
    var stateOK = true;

    mock_names.each(function(index) {
            if ($(this).val().trim().length == 0) {
                stateOK = false
            }
    });

    mock_urls.each(function(index) {
        if ($(this).val().trim().length == 0) {
            stateOK = false
        }
    });

    mock_envs.each(function(index) {
        if ($(this).val().trim().length == 0) {
            stateOK = false
        }
    });

    if (stateOK) {
        var mock_row_states = $('label[id^="status_row_"]');
        mock_row_states.each(function (index) {
            $(this).text('Processing ...');
        });
        $(':button').prop('disabled', true);
    }
    return stateOK;
}

function cloneRowOnSubmit(row) {
    if (validateBatchCloneData()) {

        mock_names = $('input[name^="mock_name_"]');
        mock_urls = $('input[name^="mock_url_"]');
        mock_envs = $('select[name^="mock_environment_"]');
        mock_row_states = $('label[id^="status_row_"]');

        var total_rows = mock_row_states.length;
        var count = 0;

        mock_names.each(function(index) {

            var mock_name = $(this).val();
            var mock_url= mock_urls[index].value;
            var mock_env= mock_envs[index].value;
            var mock_row_status= mock_row_states[index];

            $.ajax({
                method: "POST",
                url: "/mock/clone/batch",
                data: { name: mock_name, url: mock_url, mock_test_environment: mock_env}
            })
                .done(function (msg) {
//                    alert("Data processed: " + msg);
//                    mock_row_status.textContent = 'Done';
                    mock_row_status.textContent = msg;
                    switch (msg) {
                        case 'Updated':
                            mock_row_status.setAttribute('class','label label-info');
                            break;

                        case 'Error Updating':
                            mock_row_status.setAttribute('class','label label-warning');
                            break;

                        case 'Created':
                            mock_row_status.setAttribute('class','label label-success');
                            break;

                        case 'Error Creating':
                            mock_row_status.setAttribute('class','label label-danger');
                            break;

                    }
                    count = count + 1;
                    if (total_rows == count) {
                        $(':button').prop('disabled', false);
                    }
                });
        });

    } else {
        alert("Please supply all data. Mock Name and URL are required.");
    }
}<commit_msg>Reset json body when GETting<commit_after>function validateJsonBody(input_area) {
    var contentType = $("#id_mock_content_type").val();

    if (contentType == "application/json;charset=UTF-8") {
        var jsonInput = $("#json_body");

        if (jsonInput.val().length > 0) {
            jsonInput.validateJSON({
                compress: false,
                reformat: true,
                'onSuccess': function (json) {
                    jsonInput.parent().removeClass('has-error').addClass('has-success');
                },
                'onError': function (error) {
                    jsonInput.parent().addClass('has-error');
                    xx = input_area.selectionStart;
                    yy = input_area.selectionEnd = xx + 100
                    jsonInput.selectRange(xx, yy);
                }
            });
        }
    }
    if (contentType == "text/xml") {

    }
    if (contentType == "text/html") {

    }
}

function httpGet()
{
    var theUrl = document.getElementById('mock_request_url');
    var urlInput = theUrl.value;
    var xmlHttp = new XMLHttpRequest();
    xmlHttp.open( "GET", urlInput, false );
    xmlHttp.send( null );
    document.getElementById('mock_data_response').value = xmlHttp.responseText;
}

function urlChanged() {
    var theUrl = document.getElementById('mock_request_url');
    var c = theUrl.value;
    theUrl.value = c;
    if (isURL(c) == false) {
       var getBtn = document.getElementById('get_button');
        getBtn.disabled = true;
    } else {
        var getBtn = document.getElementById('get_button');
        getBtn.disabled = false;
    }
}

function isURL(textval) {
    var urlregex = /^(https?|ftp):\/\/([a-zA-Z0-9.-]+(:[a-zA-Z0-9.&%$-]+)*@)*((25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9][0-9]?)(\.(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])){3}|([a-zA-Z0-9-]+\.)*[a-zA-Z0-9-]+\.(com|edu|gov|int|mil|net|org|biz|arpa|info|name|pro|aero|coop|museum|[a-zA-Z]{2}))(:[0-9]+)*(\/($|[a-zA-Z0-9.,?'\\+&%$#=~_-]+))*$/;
    return urlregex.test(textval);
}

function getCloningData() {
    $("#form_form").attr("action","/mock/clone");
    $("#form_form").attr("method","get");
    $("textarea#json_body").val('');
    document.getElementById("form_form").submit();
}

function addMoreRow() {
    var new_row =
    "\

\

\
            Mock Request Name\
            <input class='form-control' name='mock_name_xx' placeholder='Mock name' type='text'>\

\

\
            Mock URL\
            <input class='form-control' name='mock_url_xx' placeholder='Mock URL' type='url'>\

\

\
            Test Env\
            <select class='form-control' name='mock_environment_xx'>\
              <option id='integration'>integration</option>\
              <option id='production'>production</option>\
              <option id='quality'>quality</option>\
            </select>\

\
            <button class='btn btn-primary' type = 'button' row='xx' id= 'delete_button_xx' onclick= 'deleteRow(this)'> Delete</button>\
            Pending

\

"

    var first_row=$('div[id^="row_"]').last().attr('id');
    var row_name_split_arr = (first_row.split("_"));
    var current_row_number = parseInt(row_name_split_arr[row_name_split_arr.length - 1]) + 1;
    var row = new_row.replace(/xx/g,current_row_number);

    $("#row_1").append(row)
}

function deleteRow(row) {
    var button_row = $(row).attr('row');
    var row = parseInt(button_row);
    $("#row_"+row).empty();
    $("#row_"+row).remove();
}

function validateBatchCloneData() {
    mock_names = $('input[name^="mock_name_"]');
    mock_urls = $('input[name^="mock_url_"]');
    mock_envs = $('select[name^="mock_environment_"]');
    var stateOK = true;

    mock_names.each(function(index) {
            if ($(this).val().trim().length == 0) {
                stateOK = false
            }
    });

    mock_urls.each(function(index) {
        if ($(this).val().trim().length == 0) {
            stateOK = false
        }
    });

    mock_envs.each(function(index) {
        if ($(this).val().trim().length == 0) {
            stateOK = false
        }
    });

    if (stateOK) {
        var mock_row_states = $('label[id^="status_row_"]');
        mock_row_states.each(function (index) {
            $(this).text('Processing ...');
        });
        $(':button').prop('disabled', true);
    }
    return stateOK;
}

function cloneRowOnSubmit(row) {
    if (validateBatchCloneData()) {

        mock_names = $('input[name^="mock_name_"]');
        mock_urls = $('input[name^="mock_url_"]');
        mock_envs = $('select[name^="mock_environment_"]');
        mock_row_states = $('label[id^="status_row_"]');

        var total_rows = mock_row_states.length;
        var count = 0;

        mock_names.each(function(index) {

            var mock_name = $(this).val();
            var mock_url= mock_urls[index].value;
            var mock_env= mock_envs[index].value;
            var mock_row_status= mock_row_states[index];

            $.ajax({
                method: "POST",
                url: "/mock/clone/batch",
                data: { name: mock_name, url: mock_url, mock_test_environment: mock_env}
            })
                .done(function (msg) {
//                    alert("Data processed: " + msg);
//                    mock_row_status.textContent = 'Done';
                    mock_row_status.textContent = msg;
                    switch (msg) {
                        case 'Updated':
                            mock_row_status.setAttribute('class','label label-info');
                            break;

                        case 'Error Updating':
                            mock_row_status.setAttribute('class','label label-warning');
                            break;

                        case 'Created':
                            mock_row_status.setAttribute('class','label label-success');
                            break;

                        case 'Error Creating':
                            mock_row_status.setAttribute('class','label label-danger');
                            break;

                    }
                    count = count + 1;
                    if (total_rows == count) {
                        $(':button').prop('disabled', false);
                    }
                });
        });

    } else {
        alert("Please supply all data. Mock Name and URL are required.");
    }
}<|endoftext|>


<commit_before>define([
	'underscore'
	, 'marionette'
	, 'moment'
	, 'app'
	, 'models/contraceptive'
	, 'fullcalendar'
], function (_, Marionette, Moment, App, ContraceptiveModel) {
	var itemView = Marionette.ItemView.extend({
		template: 'app/templates/contraceptive/list.tpl'
		, tagName: 'div'
		, className: 'box'
		, events: {
			"shown.bs.tab #tabCalendar": 'startCalendar'
		}
		, initialize: function(coll, model) {
			this.collection = coll;
			this.model = model;
		}
		, serializeData: function() {
			var attrToView = _.clone(this.attributes) || {},
				accountFrom,
				accountTo
			;

			attrToView.notTakenYet = _.filter(this.collection, function(model) {
				return model.state == 'NOT_TAKEN_YET';
			});
			attrToView.taken = 0;
			attrToView.missed = 0;
			attrToView.void = 0;
			attrToView.isMenstruating = 0;

			return attrToView;
		}

		, startCalendar: function(ev) {
			var coll = this.collection.toJSON();

			this.$el.find('#calendar').fullCalendar({
				defaultDate: new Moment()
				, eventClick: function(event, jsEvent, view) {
					window.location.href = '#contraceptive/' + event.id;
				}
				, header: {
					left: 'prev,next',
					center: 'title',
					right: 'today'
				}
				, editable: false
				, eventLimit: false
				, eventSources: [
					{
						events: this.filterCollByState(coll, 'TAKEN'),
						backgroundColor: 'green'
					},
					{
						events: this.filterCollByState(coll, 'MISSED'),
						backgroundColor: 'red'
					},
					{
						events: this.filterCollByState(coll, 'NOT_TAKEN_YET'),
						backgroundColor: 'yellow'
					},
					{
						events: this.filterCollByState(coll, 'VOID'),
						backgroundColor: 'grey'
					},
					{
						events: this.filterCollByState(coll, 'IS_MENSTRUATING'),
						backgroundColor: 'blue'
					}
				]
			});
		}

		/*
		 * Filter and return elements by state to adapt to fullcalendar data model.
		 *
		 * Contraceptive data model: see contraceptive model class;
		 * Fullcalendar data model: {id: 1, start: '2015-04-01', title: 'taken', allDay: true};
		 */
		, filterCollByState: function(coll, state) {
			var collByState = _.filter(coll, function(model) {
					return model.state == state;
				}),
				filtered = [],
				el
			;

			_.forEach(collByState, function(element, index, list) {
				el = {allDay: true};
				el.id = element.id;
				el.start = Moment(element.date, 'DD/MM/YYYY');
				el.title = element.state;

				filtered.push(el);
			});

			return filtered;
		}
	});

	return itemView;
});
<commit_msg>Calc Contraceptive list<commit_after>define([
	'underscore'
	, 'marionette'
	, 'moment'
	, 'app'
	, 'models/contraceptive'
	, 'fullcalendar'
], function (_, Marionette, Moment, App, ContraceptiveModel) {
	var itemView = Marionette.ItemView.extend({
		template: 'app/templates/contraceptive/list.tpl'
		, tagName: 'div'
		, className: 'box'
		, events: {
			"shown.bs.tab #tabCalendar": 'startCalendar'
		}
		, initialize: function(coll, model) {
			this.collection = coll;
			this.model = model;
		}
		, serializeData: function() {
			var attrToView = _.clone(this.attributes) || {},
				notTakenYet,
				taken,
				missed,
				voidDay,
				isMenstruating
			;

			notTakenYet = _.filter(this.collection.toJSON(), function(model) {
				return model.state == 'NOT_TAKEN_YET';
			});
			taken = _.filter(this.collection.toJSON(), function(model) {
				return model.state == 'TAKEN';
			});
			missed = _.filter(this.collection.toJSON(), function(model) {
				return model.state == 'MISSED';
			});
			voidDay = _.filter(this.collection.toJSON(), function(model) {
				return model.state == 'VOID';
			});
			isMenstruating = _.filter(this.collection.toJSON(), function(model) {
				return model.state == 'IS_MENSTRUATING';
			});

			attrToView.notTakenYet = notTakenYet.length;
			attrToView.taken = taken.length;
			attrToView.missed = missed.lengeth;
			attrToView.void = voidDay.lengeth;
			attrToView.isMenstruating = isMenstruating.length;

			return attrToView;
		}

		, startCalendar: function(ev) {
			var coll = this.collection.toJSON();

			this.$el.find('#calendar').fullCalendar({
				defaultDate: new Moment()
				, eventClick: function(event, jsEvent, view) {
					window.location.href = '#contraceptive/' + event.id;
				}
				, header: {
					left: 'prev,next',
					center: 'title',
					right: 'today'
				}
				, editable: false
				, eventLimit: false
				, eventSources: [
					{
						events: this.filterCollByState(coll, 'TAKEN'),
						backgroundColor: 'green'
					},
					{
						events: this.filterCollByState(coll, 'MISSED'),
						backgroundColor: 'red'
					},
					{
						events: this.filterCollByState(coll, 'NOT_TAKEN_YET'),
						backgroundColor: 'yellow'
					},
					{
						events: this.filterCollByState(coll, 'VOID'),
						backgroundColor: 'grey'
					},
					{
						events: this.filterCollByState(coll, 'IS_MENSTRUATING'),
						backgroundColor: 'blue'
					}
				]
			});
		}

		/*
		 * Filter and return elements by state to adapt to fullcalendar data model.
		 *
		 * Contraceptive data model: see contraceptive model class;
		 * Fullcalendar data model: {id: 1, start: '2015-04-01', title: 'taken', allDay: true};
		 */
		, filterCollByState: function(coll, state) {
			var collByState = _.filter(coll, function(model) {
					return model.state == state;
				}),
				filtered = [],
				el
			;

			_.forEach(collByState, function(element, index, list) {
				el = {allDay: true};
				el.id = element.id;
				el.start = Moment(element.date, 'DD/MM/YYYY');
				el.title = element.state;

				filtered.push(el);
			});

			return filtered;
		}
	});

	return itemView;
});
<|endoftext|>


<commit_before>// Styles
import 'reveal.js/css/reveal.css'
import 'reveal.js/css/theme/black.css'
import 'reveal.js/lib/css/zenburn.css' // code highlighting theme

// Copy notes HTML file to the output
import 'reveal.js/plugin/notes/notes.html'

// Initialize `reveal.js`
Reveal.initialize({
    // Display the page number of the current slide
	slideNumber: true,

    dependencies: [
        // Cross-browser shim that fully implements classList - https://github.com/eligrey/classList.js/
        {
            src: require('reveal.js/lib/js/classList.js'),
            condition: function() { return !document.body.classList; }
        },

        // Interpret Markdown in <section> elements
        {
            src: require('reveal.js/plugin/markdown/marked.js'),
            condition: function() { return !!document.querySelector( '[data-markdown]' ); }
        },
        {
            src: require('reveal.js/plugin/markdown/markdown.js'),
            condition: function() { return !!document.querySelector( '[data-markdown]' ); }
        },

        // Syntax highlight for <code> elements
        {
            src: require('reveal.js/plugin/highlight/highlight.js'),
            async: true,
            callback: function() { hljs.initHighlightingOnLoad(); }
        },

        // Zoom in and out with Alt+click
        {
            src: require('reveal.js/plugin/zoom-js/zoom.js'),
            async: true
        },

        // Speaker notes
        {
            src: require('reveal.js/plugin/notes/notes.js'),
            async: true
        },

        // MathJax
        {
            src: require('reveal.js/plugin/math/math.js'),
            async: true
        }
    ]
});

<commit_msg>Add search capability to slides<commit_after>// Styles
import 'reveal.js/css/reveal.css'
import 'reveal.js/css/theme/black.css'
import 'reveal.js/lib/css/zenburn.css' // code highlighting theme

// Copy notes HTML file to the output
import 'reveal.js/plugin/notes/notes.html'

// Initialize `reveal.js`
Reveal.initialize({
    // Display the page number of the current slide
	slideNumber: true,

    dependencies: [
        // Cross-browser shim that fully implements classList - https://github.com/eligrey/classList.js/
        {
            src: require('reveal.js/lib/js/classList.js'),
            condition: function() { return !document.body.classList; }
        },

        // Interpret Markdown in <section> elements
        {
            src: require('reveal.js/plugin/markdown/marked.js'),
            condition: function() { return !!document.querySelector( '[data-markdown]' ); }
        },
        {
            src: require('reveal.js/plugin/markdown/markdown.js'),
            condition: function() { return !!document.querySelector( '[data-markdown]' ); }
        },

        // Syntax highlight for <code> elements
        {
            src: require('reveal.js/plugin/highlight/highlight.js'),
            async: true,
            callback: function() { hljs.initHighlightingOnLoad(); }
        },

        // MathJax
        {
            src: require('reveal.js/plugin/math/math.js'),
            async: true
        },

        // Zoom in and out with Alt+click
        {
            src: require('reveal.js/plugin/zoom-js/zoom.js'),
            async: true
        },

        // Search slides with Ctrl+Shift+F
        {
            src: require('reveal.js/plugin/search/search.js'),
            async: true
        },

        // Speaker notes with F
        {
            src: require('reveal.js/plugin/notes/notes.js'),
            async: true
        },
    ]
});

<|endoftext|>


<commit_before>import AbstractWidget from "ievv_jsbase/widget/AbstractWidget";

export default class DataListDisplayByStateWidget extends AbstractWidget {
  getDefaultConfig() {
    return {
      signalNameSpace: null,
      displayStyle: 'block',
      // Valid values:
      // - 'initializing'
      // - 'currentResultIsNotEmpty'
      // - 'currentResultIsEmpty'
      // - 'hasSearchString'
      // - 'noSearchString'
      // - 'focus'
      // - 'blur'
      showStates: []
    };
  }

  constructor(element, widgetInstanceId) {
    super(element, widgetInstanceId);
    this._name = `django_cradmin.widgets.DataListDisplayByStateWidget.${widgetInstanceId}`;
    this.logger = new window.ievv_jsbase_core.LoggerSingleton().getLogger(
      this._name);
    if (this.config.signalNameSpace == null) {
      throw new Error('The signalNameSpace config is required.');
    }
    this._onStateChangeSignal = this._onStateChangeSignal.bind(this);
    this.stateSet = new Set(['initializing']);
    this._refresh();
    this.initializeSignalHandlers();
  }

  initializeSignalHandlers() {
    new window.ievv_jsbase_core.SignalHandlerSingleton().addReceiver(
      `${this.config.signalNameSpace}.StateChange`,
      this._name,
      this._onStateChangeSignal
    );
  }

  destroy() {
    new window.ievv_jsbase_core.SignalHandlerSingleton().removeReceiver(
      `${this.config.signalNameSpace}.StateChange`,
      this._name
    );
  }

  _display(display) {
    if(display) {
      this.element.setAttribute('style', `display: ${this.config.displayStyle}`);
    } else {
      this.element.setAttribute('style', 'display: none');
    }
  }

  _onStateChangeSignal(receivedSignalInfo) {
    this.logger.debug('Received:', receivedSignalInfo.toString());
    const state = receivedSignalInfo.data.state;
    const stateChanges = receivedSignalInfo.data.stateChanges;

    this.stateSet.delete('initializing');
    if(stateChanges.has('data')) {
      if(state.data.count > 0) {
        this.stateSet.add('currentResultIsNotEmpty');
        this.stateSet.delete('currentResultIsEmpty');
      } else {
        this.stateSet.add('currentResultIsEmpty');
        this.stateSet.delete('currentResultIsNotEmpty');
      }
    }
    if(stateChanges.has('searchString')) {
      if (state.searchString.length > 0) {
        this.stateSet.add('hasSearchString');
        this.stateSet.delete('noSearchString');
      } else {
        this.stateSet.add('noSearchString');
        this.stateSet.delete('hasSearchString');
      }
    }
    if(stateChanges.has('focus')) {
      if (state.focus) {
        this.stateSet.add('focus');
        this.stateSet.delete('blur');
      } else {
        this.stateSet.add('blur');
        this.stateSet.delete('focus');
      }
    }

    // TODO: Update with filters
    if(state.searchString == '' && state.data.count == 0) {
      this.stateSet.add('empty');
      this.stateSet.delete('notEmpty');
    } else {
      this.stateSet.delete('empty');
      this.stateSet.add('notEmpty');
    }
    this._refresh();
  }

  _refresh() {
    console.log(this.stateSet);
    let display = false;
    for(let state of this.stateSet) {
      if(this.config.showStates.indexOf(state) != -1) {
        display = true;
        break;
      }
    }
    this._display(display);
  }
}
<commit_msg>javascript...DataListDisplayByStateWidget: Docs.<commit_after>import AbstractWidget from "ievv_jsbase/widget/AbstractWidget";

export default class DataListDisplayByStateWidget extends AbstractWidget {
  getDefaultConfig() {
    return {
      signalNameSpace: null,
      displayStyle: 'block',
      // Valid values:
      // - 'initializing'
      // - 'currentResultIsNotEmpty'
      // - 'currentResultIsEmpty'
      // - 'hasSearchString'
      // - 'noSearchString'
      // - 'focus'
      // - 'blur'
      // - 'empty'
      // - 'notEmpty'
      showStates: []
    };
  }

  constructor(element, widgetInstanceId) {
    super(element, widgetInstanceId);
    this._name = `django_cradmin.widgets.DataListDisplayByStateWidget.${widgetInstanceId}`;
    this.logger = new window.ievv_jsbase_core.LoggerSingleton().getLogger(
      this._name);
    if (this.config.signalNameSpace == null) {
      throw new Error('The signalNameSpace config is required.');
    }
    this._onStateChangeSignal = this._onStateChangeSignal.bind(this);
    this.stateSet = new Set(['initializing']);
    this._refresh();
    this.initializeSignalHandlers();
  }

  initializeSignalHandlers() {
    new window.ievv_jsbase_core.SignalHandlerSingleton().addReceiver(
      `${this.config.signalNameSpace}.StateChange`,
      this._name,
      this._onStateChangeSignal
    );
  }

  destroy() {
    new window.ievv_jsbase_core.SignalHandlerSingleton().removeReceiver(
      `${this.config.signalNameSpace}.StateChange`,
      this._name
    );
  }

  _display(display) {
    if(display) {
      this.element.setAttribute('style', `display: ${this.config.displayStyle}`);
    } else {
      this.element.setAttribute('style', 'display: none');
    }
  }

  _onStateChangeSignal(receivedSignalInfo) {
    this.logger.debug('Received:', receivedSignalInfo.toString());
    const state = receivedSignalInfo.data.state;
    const stateChanges = receivedSignalInfo.data.stateChanges;

    this.stateSet.delete('initializing');
    if(stateChanges.has('data')) {
      if(state.data.count > 0) {
        this.stateSet.add('currentResultIsNotEmpty');
        this.stateSet.delete('currentResultIsEmpty');
      } else {
        this.stateSet.add('currentResultIsEmpty');
        this.stateSet.delete('currentResultIsNotEmpty');
      }
    }
    if(stateChanges.has('searchString')) {
      if (state.searchString.length > 0) {
        this.stateSet.add('hasSearchString');
        this.stateSet.delete('noSearchString');
      } else {
        this.stateSet.add('noSearchString');
        this.stateSet.delete('hasSearchString');
      }
    }
    if(stateChanges.has('focus')) {
      if (state.focus) {
        this.stateSet.add('focus');
        this.stateSet.delete('blur');
      } else {
        this.stateSet.add('blur');
        this.stateSet.delete('focus');
      }
    }

    // TODO: Update with filters
    if(state.searchString == '' && state.data.count == 0) {
      this.stateSet.add('empty');
      this.stateSet.delete('notEmpty');
    } else {
      this.stateSet.delete('empty');
      this.stateSet.add('notEmpty');
    }
    this._refresh();
  }

  _refresh() {
    let display = false;
    for(let state of this.stateSet) {
      if(this.config.showStates.indexOf(state) != -1) {
        display = true;
        break;
      }
    }
    this._display(display);
  }
}
<|endoftext|>