<commit_before>// Universal Subtitles, universalsubtitles.org
//
// Copyright (C) 2010 Participatory Culture Foundation
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program.  If not, see
// http://www.gnu.org/licenses/agpl-3.0.html.

goog.provide('mirosubs.subtitle.BottomFinishedPanel');

mirosubs.subtitle.BottomFinishedPanel = function(dialog) {
    goog.ui.Component.call(this);
    this.dialog_ = dialog;
};
goog.inherits(mirosubs.subtitle.BottomFinishedPanel, goog.ui.Component);

mirosubs.subtitle.BottomFinishedPanel.prototype.createDom = function() {
    mirosubs.subtitle.BottomFinishedPanel.superClass_.createDom.call(this);
    this.getElement().className = 'mirosubs-translating';
    var $d = goog.bind(this.getDomHelper().createDom, this.getDomHelper());
    var attrs = {'className': 'mirosubs-done', 'href':'#'};
    this.addTranslationLink_ = $d('a', attrs, 'Add a Translation Now');
    this.askAFriendLink_ = $d('a', attrs, 'Ask a Friend to Translate');
    this.getElement().appendChild(
        $d('div', 'mirosubs-buttons',
           this.addTranslationLink_,
           this.askAFriendLink_));
    this.getElement().appendChild(
        $d('p', null,
           ['Your subtitles are now ready to be translated -- by you ',
            'and by others.  The best way to get translation help is ',
            'to reach out to your friends or people in your community ',
            'or orgnization.'].join('')));
    this.getElement().appendChild(
        $d('p', null,
           ["Do you know someone who speaks a language that you’d like ",
            "to translate into?"].join('')));
};
mirosubs.subtitle.BottomFinishedPanel.prototype.enterDocument = function() {
    mirosubs.subtitle.BottomFinishedPanel.superClass_.enterDocument.call(this);
    this.getHandler().listen(this.addTranslationLink_, 'click',
                             this.addTranslationClicked_);
    this.getHandler().listen(this.askAFriendLink_, 'click',
                             this.askAFriendClicked_);
};
mirosubs.subtitle.BottomFinishedPanel.prototype.addTranslationClicked_ =
    function(event)
{
    this.dialog_.addTranslationsAndClose();
    event.preventDefault();
};
mirosubs.subtitle.BottomFinishedPanel.prototype.askAFriendClicked_ =
    function(event)
{
    alert("Sorry.  We're still testing the widget, so we haven't enabled sharing features.  Please just email your friend a link for now.");
    event.preventDefault();
};<commit_msg>bug 14034: implement "ask a friend to translate" button<commit_after>// Universal Subtitles, universalsubtitles.org
//
// Copyright (C) 2010 Participatory Culture Foundation
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program.  If not, see
// http://www.gnu.org/licenses/agpl-3.0.html.

goog.provide('mirosubs.subtitle.BottomFinishedPanel');

mirosubs.subtitle.BottomFinishedPanel = function(dialog) {
    goog.ui.Component.call(this);
    this.dialog_ = dialog;
};
goog.inherits(mirosubs.subtitle.BottomFinishedPanel, goog.ui.Component);

mirosubs.subtitle.BottomFinishedPanel.prototype.createDom = function() {
    mirosubs.subtitle.BottomFinishedPanel.superClass_.createDom.call(this);
    this.getElement().className = 'mirosubs-translating';
    var $d = goog.bind(this.getDomHelper().createDom, this.getDomHelper());
    var attrs = {'className': 'mirosubs-done', 'href':'#'};
    this.addTranslationLink_ = $d('a', attrs, 'Add a Translation Now');
    this.askAFriendLink_ = $d('a', attrs, 'Ask a Friend to Translate');
    this.getElement().appendChild(
        $d('div', 'mirosubs-buttons',
           this.addTranslationLink_,
           this.askAFriendLink_));
    this.getElement().appendChild(
        $d('p', null,
           ['Your subtitles are now ready to be translated -- by you ',
            'and by others.  The best way to get translation help is ',
            'to reach out to your friends or people in your community ',
            'or orgnization.'].join('')));
    this.getElement().appendChild(
        $d('p', null,
           ["Do you know someone who speaks a language that you’d like ",
            "to translate into?"].join('')));
};
mirosubs.subtitle.BottomFinishedPanel.prototype.enterDocument = function() {
    mirosubs.subtitle.BottomFinishedPanel.superClass_.enterDocument.call(this);
    this.getHandler().listen(this.addTranslationLink_, 'click',
                             this.addTranslationClicked_);
    this.getHandler().listen(this.askAFriendLink_, 'click',
                             this.askAFriendClicked_);
};
mirosubs.subtitle.BottomFinishedPanel.prototype.addTranslationClicked_ =
    function(event)
{
    this.dialog_.addTranslationsAndClose();
    event.preventDefault();
};

mirosubs.subtitle.BottomFinishedPanel.ASK_A_FRIEND_TEXT =
    "Hey-- I just created subtitles using universalsubtitles.org, and I was hoping " +
    "you'd be able to use your awesome language skills to translate them.  It's " +
    "easy, and it would be a huge help to me.";
mirosubs.subtitle.BottomFinishedPanel.prototype.askAFriendClicked_ =
    function(event)
{
    window.location.href = "/videos/email_friend?text=" +
        encodeURIComponent(mirosubs.subtitle.BottomFinishedPanel.ASK_A_FRIEND_TEXT);
    event.preventDefault();
};<|endoftext|>


<commit_before>angular.module('media_manager')
.factory('Collection', ['$resource', function($resource){
  var host = 'http://localhost:8000';
  return $resource(host + '/collections/:id',
    { id: '@id' }, {
      'saveImages': {
        method: 'POST',
        url: host + '/collections/:id/images',
        isArray: true
      }
    }
  );
}]);
<commit_msg>adding a put method to the Collection<commit_after>angular.module('media_manager')
.factory('Collection', ['$resource', function($resource){
  var host = 'http://localhost:8000';
  return $resource(host + '/collections/:id',
    { id: '@id' }, {
      'saveImages': {
        method: 'POST',
        url: host + '/collections/:id/images',
        isArray: true
      },
      'update': {
        method:'PUT'
      }
    }
  );
}]);
<|endoftext|>


<commit_before>/* global gtag */

import * as render from './render.js';
import {runners} from './tools.mjs';

// Render main HTML before anything else.
const container = document.querySelector('#tools');
render.renderToolCards(Object.entries(runners), container);

loadLogos();

let selectedTools = [];
const tools = document.querySelectorAll('.tool-container');
const overlay = document.querySelector('.overlay');
const input = document.querySelector('#url');
const arrow = document.querySelector('.search-arrow');

/**
 * Fades in the tool screenshots one by one.
 * @return {!Promise} Resolves when the images are loaded.
 */
async function loadLogos() {
  const logos = Array.from(document.querySelectorAll('.tool .tool-logo'));
  return logos.map(logo => {
    return new Promise(resolve => {
      logo.addEventListener('load', e => {
        e.target.classList.add('loaded');
        resolve(e.target);
      }, {once: true});
    });
  });
}

/**
 * Hides the complete check icons for each tool.
 */
function resetCompletedChecks() {
  const checks = document.querySelectorAll('.tool-check');
  Array.from(checks).forEach(check => {
    check.classList.remove('done');
    check.dataset.runningTime = 0;
  });
}

/**
 * Resets UI elements.
 */
function resetUI() {
  toggleInputOverlay(true);
  Array.from(tools).forEach(tool => tool.classList.remove('selected'));

  resetCompletedChecks();

  selectedTools = [];
}

/**
 * Toggles the input overlay.
 * @param {boolean=} clear When true, clears the input. Default is false.
 */
function toggleInputOverlay(clear = false) {
  overlay.classList.toggle('show');
  if (overlay.classList.contains('show')) {
    input.focus();
    render.renderToolRunCompleteIcons(selectedTools, document.querySelector('#tools-used'));
  }
  if (clear) {
    input.value = '';
  }
}

/**
 * Starts running the selected tools and streams results back as they come.
 * @param {!URL} url
 * @return {!Promise<string>} Resolves when all tool results are complete with
 *     the URL for the results PDF.
 */
function streamResults(url) {
  return new Promise((resolve, reject) => {
    const source = new EventSource(url.href);

    // Start running timers for each tool.
    const interval = setInterval(() => {
      const checks = Array.from(document.querySelectorAll('.tool-check[data-tool]'));
      checks.forEach(check => {
        if (!check.classList.contains('done')) {
          check.dataset.runningTime = parseInt(check.dataset.runningTime || 0) + 1;
        }
      });
      const allDone = checks.every(check => check.classList.contains('done'));
      if (allDone) {
        clearInterval(interval);
      }
    }, 1000);

    source.addEventListener('message', e => {
      try {
        const msg = JSON.parse(e.data.replace(/"(.*)"/, '$1'));
        const tool = runners[msg.tool];
        if (tool) {
          const check = document.querySelector(`.tool-check[data-tool="${msg.tool}"]`);
          check.classList.add('done');
        }
        if (msg.completed) {
          resetUI();
          source.close();
          resolve(msg.url);
        }
      } catch (err) {
        console.error('Malformed stream source msg', err);
        source.close();
        reject(err);
      }
    });

    // source.addEventListener('open', e => {
    //   // ga('send', 'event', 'Lighthouse', 'start run');
    // });

    source.addEventListener('error', e => {
      if (e.readyState === EventSource.CLOSED) {
        source.close();
        reject(e);
      }
    });
  });
}

/**
 * Runs the URL in the selected tools.
 * @param {string} url
 */
async function go(url) {
  url = url.trim();

  if (!url.match(/^https?:\/\//)) {
    url = `http://${url}`;
    input.value = url;
  }

  if (!url.length || !input.validity.valid) {
    alert('URL is not valid');
    return;
  } else if (!selectedTools.length) {
    alert('Please select a tool');
    return;
  }

  resetCompletedChecks();
  overlay.classList.add('running');
  arrow.classList.add('disabled');

  const runURL = new URL('/run', location);
  runURL.searchParams.set('url', url);
  runURL.searchParams.set('tools', selectedTools);

  gtag('event', 'start', {event_category: 'tool'});
  selectedTools.forEach(tool => {
    gtag('event', 'run', {
      event_category: 'tool',
      event_label: tool,
      value: 1,
    });
  });

  const pdfURL = await streamResults(runURL);
  window.open(pdfURL);

  gtag('event', 'complete', {event_category: 'tool'});

  arrow.classList.remove('disabled');
  overlay.classList.remove('running');
  input.value = '';
}

Array.from(tools).forEach(tool => {
  tool.addEventListener('click', e => {
    e.stopPropagation();

    if (tool.href && tool.dataset.primary === 'false') {
      return;
    }

    e.preventDefault();

    const idx = selectedTools.findIndex(t => tool.dataset.tool === t);
    if (idx != -1) {
      selectedTools.splice(idx, 1);
      tool.classList.remove('selected');
    } else {
      selectedTools.push(tool.dataset.tool);
      tool.classList.add('selected');
    }
  });
});

input.addEventListener('keydown', async e => {
  if (e.keyCode !== 13) { // Enter
    return;
  }
  await go(e.target.value);
});

arrow.addEventListener('click', async e => {
  e.stopPropagation();
  await go(input.value);
});

document.addEventListener('dblclick', e => {
  if (e.target !== input && overlay.contains(e.target) &&
      overlay.classList.contains('show') &&
      !overlay.classList.contains('running')) {
    resetUI();
  }
});

document.addEventListener('keydown', e => {
  if (e.keyCode === 27) { // ESC
    if (overlay.classList.contains('show') &&
        !overlay.classList.contains('running')) {
      resetUI();
    }
    return;
  }

  if (e.target === input) {
    return;
  }

  switch (e.keyCode) {
    case 32: // space
      if (!selectedTools.length) {
        alert('Please select a tool');
        return;
      }
      toggleInputOverlay();
      break;
    case 37: // left arrow
      // TODO: move to previous tool
      break;
    // case 32: // space
    case 39: // right arrow
      // TODO: move to next tool
      break;
  }
});
<commit_msg>Prevent re-running<commit_after>/* global gtag */

import * as render from './render.js';
import {runners} from './tools.mjs';

// Render main HTML before anything else.
const container = document.querySelector('#tools');
render.renderToolCards(Object.entries(runners), container);

loadLogos();

let selectedTools = [];
const tools = document.querySelectorAll('.tool-container');
const overlay = document.querySelector('.overlay');
const input = document.querySelector('#url');
const arrow = document.querySelector('.search-arrow');

/**
 * Fades in the tool screenshots one by one.
 * @return {!Promise} Resolves when the images are loaded.
 */
async function loadLogos() {
  const logos = Array.from(document.querySelectorAll('.tool .tool-logo'));
  return logos.map(logo => {
    return new Promise(resolve => {
      logo.addEventListener('load', e => {
        e.target.classList.add('loaded');
        resolve(e.target);
      }, {once: true});
    });
  });
}

/**
 * Hides the complete check icons for each tool.
 */
function resetCompletedChecks() {
  const checks = document.querySelectorAll('.tool-check');
  Array.from(checks).forEach(check => {
    check.classList.remove('done');
    check.dataset.runningTime = 0;
  });
}

/**
 * Resets UI elements.
 */
function resetUI() {
  toggleInputOverlay(true);
  Array.from(tools).forEach(tool => tool.classList.remove('selected'));

  resetCompletedChecks();

  selectedTools = [];
}

/**
 * Toggles the input overlay.
 * @param {boolean=} clear When true, clears the input. Default is false.
 */
function toggleInputOverlay(clear = false) {
  overlay.classList.toggle('show');
  if (overlay.classList.contains('show')) {
    input.focus();
    render.renderToolRunCompleteIcons(selectedTools, document.querySelector('#tools-used'));
  }
  if (clear) {
    input.value = '';
  }
}

/**
 * Starts running the selected tools and streams results back as they come.
 * @param {!URL} url
 * @return {!Promise<string>} Resolves when all tool results are complete with
 *     the URL for the results PDF.
 */
function streamResults(url) {
  return new Promise((resolve, reject) => {
    const source = new EventSource(url.href);

    // Start running timers for each tool.
    const interval = setInterval(() => {
      const checks = Array.from(document.querySelectorAll('.tool-check[data-tool]'));
      checks.forEach(check => {
        if (!check.classList.contains('done')) {
          check.dataset.runningTime = parseInt(check.dataset.runningTime || 0) + 1;
        }
      });
      const allDone = checks.every(check => check.classList.contains('done'));
      if (allDone) {
        clearInterval(interval);
      }
    }, 1000);

    source.addEventListener('message', e => {
      try {
        const msg = JSON.parse(e.data.replace(/"(.*)"/, '$1'));
        const tool = runners[msg.tool];
        if (tool) {
          const check = document.querySelector(`.tool-check[data-tool="${msg.tool}"]`);
          check.classList.add('done');
        }
        if (msg.completed) {
          resetUI();
          source.close();
          resolve(msg.url);
        }
      } catch (err) {
        console.error('Malformed stream source msg', err);
        source.close();
        reject(err);
      }
    });

    // source.addEventListener('open', e => {
    //   // ga('send', 'event', 'Lighthouse', 'start run');
    // });

    source.addEventListener('error', e => {
      if (e.readyState === EventSource.CLOSED) {
        source.close();
        reject(e);
      }
    });
  });
}

/**
 * Runs the URL in the selected tools.
 * @param {string} url
 */
async function go(url) {
  url = url.trim();

  if (!url.match(/^https?:\/\//)) {
    url = `http://${url}`;
    input.value = url;
  }

  if (!url.length || !input.validity.valid) {
    alert('URL is not valid');
    return;
  } else if (!selectedTools.length) {
    alert('Please select a tool');
    return;
  }

  resetCompletedChecks();
  overlay.classList.add('running');
  arrow.classList.add('disabled');

  const runURL = new URL('/run', location);
  runURL.searchParams.set('url', url);
  runURL.searchParams.set('tools', selectedTools);

  gtag('event', 'start', {event_category: 'tool'});
  selectedTools.forEach(tool => {
    gtag('event', 'run', {
      event_category: 'tool',
      event_label: tool,
      value: 1,
    });
  });

  const pdfURL = await streamResults(runURL);
  window.open(pdfURL);

  gtag('event', 'complete', {event_category: 'tool'});

  arrow.classList.remove('disabled');
  overlay.classList.remove('running');
  input.value = '';
}

Array.from(tools).forEach(tool => {
  tool.addEventListener('click', e => {
    e.stopPropagation();

    if (tool.href && tool.dataset.primary === 'false') {
      return;
    }

    e.preventDefault();

    const idx = selectedTools.findIndex(t => tool.dataset.tool === t);
    if (idx != -1) {
      selectedTools.splice(idx, 1);
      tool.classList.remove('selected');
    } else {
      selectedTools.push(tool.dataset.tool);
      tool.classList.add('selected');
    }
  });
});

input.addEventListener('keydown', async e => {
  if (e.keyCode !== 13 || overlay.classList.contains('running')) {
    return;
  }
  await go(e.target.value);
});

arrow.addEventListener('click', async e => {
  e.stopPropagation();
  await go(input.value);
});

document.addEventListener('dblclick', e => {
  if (e.target !== input && overlay.contains(e.target) &&
      overlay.classList.contains('show') &&
      !overlay.classList.contains('running')) {
    resetUI();
  }
});

document.addEventListener('keydown', e => {
  if (e.keyCode === 27) { // ESC
    if (overlay.classList.contains('show') &&
        !overlay.classList.contains('running')) {
      resetUI();
    }
    return;
  }

  if (e.target === input) {
    return;
  }

  switch (e.keyCode) {
    case 32: // space
      if (!selectedTools.length) {
        alert('Please select a tool');
        return;
      }
      toggleInputOverlay();
      break;
    case 37: // left arrow
      // TODO: move to previous tool
      break;
    // case 32: // space
    case 39: // right arrow
      // TODO: move to next tool
      break;
  }
});
<|endoftext|>


<commit_before>//
// Analytics for all things articles. This includes tests for share buttons
// and potentionally other alternative layout options or more.
//

if(location.pathname.match('/article/')){

  $(document.body).on('click', '.article-social a', function() {
    var articleId = $(this).closest('.article-container').data('id');
    analytics.track('Article Share', {
      article_id: articleId,
      context_type: sd.ARTICLE ? 'article_fixed' : 'magazine_fixed',
      service: $(this).attr('data-service')
    });

  }).on('click', '.article-share-fixed > a', function() {
    var articleId = $(this).closest('.article-container').data('id');
    analytics.track('Article Share', {
      article_id: articleId,
      context_type: 'article_sticky',
      service: $(this).attr('data-service')
    });

  }).on('click', '.article-section-toc-link a', function() {
    var articleId = $(this).closest('.article-container').data('id');
    analytics.track('Clicked article impression', {
      article_id: articleId,
      destination_path: null,
      impression_type: 'toc',
      context_type: 'article_fixed'
    });

  }).on('click', '.article-section-image-set', function() {
    var articleId = $(this).closest('.article-container').data('id');
    analytics.track('Clicked article impression', {
      article_id: articleId,
      destination_path: null,
      impression_type: 'image_set',
      context_type: 'article_fixed'
    });

  }).on('click', '.article-section-callout', function(){
    var articleId = $(this).closest('.article-container').data('id');
    // Only track callouts that are links
    if($(this)[0].href){
      analytics.track('Clicked article impression', {
        article_id: articleId,
        destination_path: $(this)[0].href.replace(/^.*\/\/[^\/]+/, ''),
        impression_type: 'article_callout',
        context_type: 'article_fixed'
      });
    }

  }).on('click', '.article-related-widget a', function(){
    var articleId = $(this).closest('.article-related-widget').data('id');
    analytics.track('Clicked article impression', {
      article_id: articleId,
      destination_path: $(this)[0].href.replace(/^.*\/\/[^\/]+/, ''),
      impression_type: 'related_article',
      context_type: 'article_fixed'
    });

  });

  // Hooks
  analyticsHooks.on('readmore', function(options) {
    analytics.track('Clicked Read More', {});
    analytics.page({path: location.pathname});
    if(window.PARSELY){
      window.PARSELY.beacon.trackPageView({
        url: location.href,
        urlref: sd.APP_URL + '/' + options.urlref,
        js: 1,
        action_name: 'infinite'
      });
    }
    if(window.Sailthru){
      Sailthru.track({
        domain: 'horizon.artsy.net',
        spider: true,
        track_url: true,
        url: sd.APP_URL + '/' + location.pathname,
        use_stored_tags: true
     });
    }
  });

  analyticsHooks.on('view:editorial-signup', function(options) {
    analytics.track('Article impression', {
      article_id: null,
      destination_path: null,
      impression_type: 'newsletter_signup',
      context_type: options.type,
    }, { integrations: { 'Mixpanel': false } } );
  });
}

// Applies to both /article/* and /articles
if(location.pathname.match('/article/') || location.pathname.match('/articles') || location.pathname.match('/gallery-insights')){
  analyticsHooks.on('submit:editorial-signup', function(options){
    analytics.track('Sign up for editorial email', {
      article_id: $(this).closest('.article-container').data('id'),
      context_type: options.type,
      user_email: options.email,
    });
  });

  analyticsHooks.on('submit:gi-signup', function(options){
    analytics.track('Sign up for gallery insights email', {
      article_id: $(this).closest('.article-container').data('id'),
      context_type: 'article_fixed',
      user_email: options.email
    });
  });

  analyticsHooks.on('dismiss:editorial-signup', function(options){
    analytics.track('Dismiss editorial signup', {
      context_type: context
    });
  });

}
<commit_msg>modal closes<commit_after>//
// Analytics for all things articles. This includes tests for share buttons
// and potentionally other alternative layout options or more.
//

if(location.pathname.match('/article/')){

  $(document.body).on('click', '.article-social a', function() {
    var articleId = $(this).closest('.article-container').data('id');
    analytics.track('Article Share', {
      article_id: articleId,
      context_type: sd.ARTICLE ? 'article_fixed' : 'magazine_fixed',
      service: $(this).attr('data-service')
    });

  }).on('click', '.article-share-fixed > a', function() {
    var articleId = $(this).closest('.article-container').data('id');
    analytics.track('Article Share', {
      article_id: articleId,
      context_type: 'article_sticky',
      service: $(this).attr('data-service')
    });

  }).on('click', '.article-section-toc-link a', function() {
    var articleId = $(this).closest('.article-container').data('id');
    analytics.track('Clicked article impression', {
      article_id: articleId,
      destination_path: null,
      impression_type: 'toc',
      context_type: 'article_fixed'
    });

  }).on('click', '.article-section-image-set', function() {
    var articleId = $(this).closest('.article-container').data('id');
    analytics.track('Clicked article impression', {
      article_id: articleId,
      destination_path: null,
      impression_type: 'image_set',
      context_type: 'article_fixed'
    });

  }).on('click', '.article-section-callout', function(){
    var articleId = $(this).closest('.article-container').data('id');
    // Only track callouts that are links
    if($(this)[0].href){
      analytics.track('Clicked article impression', {
        article_id: articleId,
        destination_path: $(this)[0].href.replace(/^.*\/\/[^\/]+/, ''),
        impression_type: 'article_callout',
        context_type: 'article_fixed'
      });
    }

  }).on('click', '.article-related-widget a', function(){
    var articleId = $(this).closest('.article-related-widget').data('id');
    analytics.track('Clicked article impression', {
      article_id: articleId,
      destination_path: $(this)[0].href.replace(/^.*\/\/[^\/]+/, ''),
      impression_type: 'related_article',
      context_type: 'article_fixed'
    });

  });

  // Hooks
  analyticsHooks.on('readmore', function(options) {
    analytics.track('Clicked Read More', {});
    analytics.page({path: location.pathname});
    if(window.PARSELY){
      window.PARSELY.beacon.trackPageView({
        url: location.href,
        urlref: sd.APP_URL + '/' + options.urlref,
        js: 1,
        action_name: 'infinite'
      });
    }
    if(window.Sailthru){
      Sailthru.track({
        domain: 'horizon.artsy.net',
        spider: true,
        track_url: true,
        url: sd.APP_URL + '/' + location.pathname,
        use_stored_tags: true
     });
    }
  });

  analyticsHooks.on('view:editorial-signup', function(options) {
    analytics.track('Article impression', {
      article_id: null,
      destination_path: null,
      impression_type: 'newsletter_signup',
      context_type: options.type,
    }, { integrations: { 'Mixpanel': false } } );
  });
}

// Applies to both /article/* and /articles
if(location.pathname.match('/article/') || location.pathname.match('/articles') || location.pathname.match('/gallery-insights')){
  analyticsHooks.on('submit:editorial-signup', function(options){
    analytics.track('Sign up for editorial email', {
      article_id: $(this).closest('.article-container').data('id'),
      context_type: options.type,
      user_email: options.email,
    });
  });

  analyticsHooks.on('submit:gi-signup', function(options){
    analytics.track('Sign up for gallery insights email', {
      article_id: $(this).closest('.article-container').data('id'),
      context_type: 'article_fixed',
      user_email: options.email
    });
  });

  analyticsHooks.on('dismiss:editorial-signup', function(options){
    analytics.track('Dismiss editorial signup', {
      context_type: options.type
    });
  });

}
<|endoftext|>


<commit_before>/*!
 * npm-info <https://github.com/doowb/npm-info>
 *
 * Copyright (c) 2016, Brian Woodward.
 * Licensed under the MIT License.
 */

'use strict';

var npm = require('../')();
var repo = npm.repo('micromatch');
repo.downloads(function(err, downloads) {
  if (err) return console.error(err);
  console.log(downloads.length + ' days of downloads have been pulled for micromatch');
  console.log();
  repo.total(function(err, total) {
    if (err) return console.error(err);
    console.log('total:', total);
    repo.last(30, function(err, total) {
      if (err) return console.error(err);
      console.log('last 30 days:', total);
      repo.last(7, function(err, total) {
        if (err) return console.error(err);
        console.log('last 7 days:', total);
        repo.last(1, function(err, total) {
          if (err) return console.error(err);
          console.log('last day:', total);
        });
      });
    });
  });
});
<commit_msg>update example using firebasebase store<commit_after>/*!
 * npm-info <https://github.com/doowb/npm-info>
 *
 * Copyright (c) 2016, Brian Woodward.
 * Licensed under the MIT License.
 */

'use strict';

// // default using memory store
// var npm = require('../')();

// // using data store
// var npm = require('../')({
//   store: require('../lib/stores/data')()
// });

// using firebase store
var Firebase = require('firebase');
var npm = require('../')({
  store: require('../lib/stores/firebase')(new Firebase('https://assemblebot.firebaseio.com/stats'))
});

var repo = npm.repo('micromatch');
repo.downloads(function(err, downloads) {
  if (err) return console.error(err);
  console.log(downloads.length + ' days of downloads have been pulled for micromatch');
  console.log();
  repo.total(function(err, total) {
    if (err) return console.error(err);
    console.log('total:', total);
    repo.last(30, function(err, total) {
      if (err) return console.error(err);
      console.log('last 30 days:', total);
      repo.last(7, function(err, total) {
        if (err) return console.error(err);
        console.log('last 7 days:', total);
        repo.last(1, function(err, total) {
          if (err) return console.error(err);
          console.log('last day:', total);
          process.exit();
        });
      });
    });
  });
});
<|endoftext|>


<commit_before>'use strict';

var utils = require('utils');

var MMSS = function() {
  this.config = {};
  this.music  = {};
};

MMSS.prototype = {
  constructor: MMSS,
  start: function(config) {
    this.config = config;

    this.build(function(err) {
      if (err) {
        console.error('Building file list failed..');
        process.exit(1);
      }
      // 遅延ロードじゃないとダメ
      var server = require('app/server').listen(config.PORT, function() {
        var port = server.address().port;
        console.log('App is running on port: %s', port);
      });
    });
  },
  build: function(fn) {
    console.log('Building file list start...');
    var that = this;
    utils.dir2obj(this.config.MUSIC_PATH, function(err, res) {
      if (err) { fn(err); }
      console.log('Building file list finish!');

      that.music = res;
      fn && fn(null);
    });
  },
  search: function(query, fn) {
    var foundAlbums = [];
    if (query === null) {
      return fn(null, foundAlbums);
    }

    var qRe = new RegExp(query, 'i');
    // まずはアーティスト名で探す
    var foundArtists = this.music.filter(function(artist) {
      return ('c' in artist) && qRe.test(artist.n);
    });

    if (foundArtists.length === 0) {
      return fn(null, foundAlbums);
    }

    foundArtists.forEach(function(artist) {
      artist.c.forEach(function(album) {
        foundAlbums.push({
          artist: artist.n,
          album:  album.n
        });
      });
    });

    fn(null, foundAlbums);
  },
  getArtistList: function(fn) {
    var artists = [];

    this.music.forEach(function(artist) {
      if ('c' in artist) {
        artists.push(artist.n);
      }
    });

    fn(null, artists);
  },
  getAlbumList: function(name, fn) {
    var albums = [];

    var artist = this.music.filter(function(artist) {
      return artist.n === name;
    })[0];

    artist.c.forEach(function(album) {
      // Disc1とか
      // if ('c' in album.c[0]) {
      //   album.c.forEach(function(album) {
      //     _album.concat(album.c);
      //   });
      // } else {
        albums.push(album);
      // }
    });

    fn(null, albums);
  },
  getTrack: function(name, fn) {
    var track = {
      path: this.config.MUSIC_PATH + '/' + name
    };
    fn(null, track);
  }
};

module.exports = (new MMSS());
<commit_msg>Disc1問題をとりあえず対応<commit_after>'use strict';

var utils = require('utils');

var MMSS = function() {
  this.config = {};
  this.music  = {};
};

MMSS.prototype = {
  constructor: MMSS,
  start: function(config) {
    this.config = config;

    this.build(function(err) {
      if (err) {
        console.error('Building file list failed..');
        process.exit(1);
      }
      // 遅延ロードじゃないとダメ
      var server = require('app/server').listen(config.PORT, function() {
        var port = server.address().port;
        console.log('App is running on port: %s', port);
      });
    });
  },
  build: function(fn) {
    console.log('Building file list start...');
    var that = this;
    utils.dir2obj(this.config.MUSIC_PATH, function(err, res) {
      if (err) { fn(err); }
      console.log('Building file list finish!');

      that.music = res;
      fn && fn(null);
    });
  },
  search: function(query, fn) {
    var foundAlbums = [];
    if (query === null) {
      return fn(null, foundAlbums);
    }

    var qRe = new RegExp(query, 'i');
    // まずはアーティスト名で探す
    var foundArtists = this.music.filter(function(artist) {
      return ('c' in artist) && qRe.test(artist.n);
    });

    if (foundArtists.length === 0) {
      return fn(null, foundAlbums);
    }

    foundArtists.forEach(function(artist) {
      artist.c.forEach(function(album) {
        foundAlbums.push({
          artist: artist.n,
          album:  album.n
        });
      });
    });

    fn(null, foundAlbums);
  },
  getArtistList: function(fn) {
    var artists = [];

    this.music.forEach(function(artist) {
      if ('c' in artist) {
        artists.push(artist.n);
      }
    });

    fn(null, artists);
  },
  getAlbumList: function(name, fn) {
    var albums = [];

    var artist = this.music.filter(function(artist) {
      return artist.n === name;
    })[0];

    artist.c.forEach(function(album) {
      // Disc1とか
      if ('c' in album.c[0]) {
        var tracks = [];
        album.c.forEach(function(disk) {
          tracks = tracks.concat(disk.c.map(function(track) { return { n: disk.n + '/' + track.n }; }));
        });
        album.c = tracks;
      }
      albums.push(album);
    });

    fn(null, albums);
  },
  getTrack: function(name, fn) {
    var track = {
      path: this.config.MUSIC_PATH + '/' + name
    };
    fn(null, track);
  }
};

module.exports = (new MMSS());
<|endoftext|>


<commit_before>

var app = angular.module("app", ['angular-loading-bar']);

app.controller("AppTest", function($scope, $http, $location, $anchorScroll){
  /**
  global vars
  **/
  app=this;
  $scope.hackerTop = "";
  $scope.lobTop = "";
  $scope.rTop = "";
  $scope.hackerNew = "";
  $scope.lobNew = "";
  $scope.rNew = "";
  $scope.content="first";

  /**
  For top posts
  **/
  $http.get('/ycomb').success(function(data) {
      $scope.hackerTop = data;
  });
  $http.get('/lobster').success(function(data) {
      $scope.lobTop = data;
  });
  $http.get('/rp').success(function(data) {
      $scope.rTop = data;
  });

  /**
  For new posts
  **/
  $http.get('/ynew').success(function(data) {
      $scope.hackerNew = data;
  });
  $http.get('/lnew').success(function(data) {
      $scope.lNew = data;
  });
  $http.get('/rnew').success(function(data) {
      $scope.rNew = data;
  });

  $scope.refresh = function(){
    $http.get('/ycomb').success(function(data) {
        $scope.hackerTop = data;
    });
    $http.get('/lobster').success(function(data) {
        $scope.lobTop = data;
    });
    $http.get('/rp').success(function(data) {
        $scope.rTop = data;
    });

    /**
    For new posts
    **/
    $http.get('/ynew').success(function(data) {
        $scope.hackerNew = data;
    });
    $http.get('/lnew').success(function(data) {
        $scope.lNew = data;
    });
    $http.get('/rnew').success(function(data) {
        $scope.rNew = data;
    });
  };

  $scope.goToTop = function(){
    $anchorScroll.yOffset = document.getElementById('container').offsetTop;
    $anchorScroll('container');
  };

  $scope.optionsCloseVisible = false;
  $scope.toggleOptionsMenu = function(){
    const options = document.getElementById('news-options');
    if (options.style.width) {
      options.style.width = '';
    } else {
      options.style.width = '100%';
    }
    $scope.optionsCloseVisible = !$scope.optionsCloseVisible;
  };

});
<commit_msg>Use arrow functions<commit_after>

var app = angular.module("app", ['angular-loading-bar']);

app.controller("AppTest", function($scope, $http, $location, $anchorScroll){
  /**
  global vars
  **/
  app=this;
  $scope.hackerTop = "";
  $scope.lobTop = "";
  $scope.rTop = "";
  $scope.hackerNew = "";
  $scope.lobNew = "";
  $scope.rNew = "";
  $scope.content="first";

  /**
  For top posts
  **/
  $http.get('/ycomb').success(function(data) {
      $scope.hackerTop = data;
  });
  $http.get('/lobster').success(function(data) {
      $scope.lobTop = data;
  });
  $http.get('/rp').success(function(data) {
      $scope.rTop = data;
  });

  /**
  For new posts
  **/
  $http.get('/ynew').success(function(data) {
      $scope.hackerNew = data;
  });
  $http.get('/lnew').success(function(data) {
      $scope.lNew = data;
  });
  $http.get('/rnew').success(function(data) {
      $scope.rNew = data;
  });

  $scope.refresh = function(){
    $http.get('/ycomb').success(function(data) {
        $scope.hackerTop = data;
    });
    $http.get('/lobster').success(function(data) {
        $scope.lobTop = data;
    });
    $http.get('/rp').success(function(data) {
        $scope.rTop = data;
    });

    /**
    For new posts
    **/
    $http.get('/ynew').success(function(data) {
        $scope.hackerNew = data;
    });
    $http.get('/lnew').success(function(data) {
        $scope.lNew = data;
    });
    $http.get('/rnew').success(function(data) {
        $scope.rNew = data;
    });
  };

  $scope.goToTop = () => {
    $anchorScroll.yOffset = document.getElementById('container').offsetTop;
    $anchorScroll('container');
  };

  $scope.optionsCloseVisible = false;
  $scope.toggleOptionsMenu = () => {
    const options = document.getElementById('news-options');
    if (options.style.width) {
      options.style.width = '';
    } else {
      options.style.width = '100%';
    }
    $scope.optionsCloseVisible = !$scope.optionsCloseVisible;
  };

});
<|endoftext|>


<commit_before>var common = require('../common'),
    config = require('../config'),
    events = require('events'),
    redis = require('redis'),
    util = require('util');

function Yakusoku() {
	events.EventEmitter.call(this);
}

util.inherits(Yakusoku, events.EventEmitter);
exports.Yakusoku = Yakusoku;
var Y = Yakusoku.prototype;

Y.connect = function () {
	if (!this.r) {
		this.r = redis.createClient();
		this.r.on('error', (function (err) {
			console.error(err);
			/*
			this.r.stream.on('error', function (err) {
				console.error('stream: ' + err);
			});
			*/
		}).bind(this));
		/* TEMP */
		this.tag = '3:moe';
	}
	return this.r;
};

Y.disconnect = function () {
	if (this.r)
		this.r.quit();
};

function is_empty(obj) {
	if (!obj)
		return false;
	for (var key in obj)
		if (obj.hasOwnProperty(key))
			return false;
	return true;
}

Y.insert_post = function (msg, body, ip, callback) {
	var r = this.connect();
	var self = this;
	/* Multi isn't needed here, yay. */
	if (msg.op) {
		r.exists('thread:' + msg.op, function (err, exists) {
			if (err)
				callback(err);
			else if (!exists)
				callback('Thread does not exist.');
			else
				self._insert(msg, body, ip, callback);
		});
	}
	else
		self._insert(msg, body, ip, callback);
};

Y._insert = function (msg, body, ip, callback) {
	var r = this.connect();
	var tag_key = 'tag:' + this.tag;
	r.incr(tag_key + ':ctr', function (err, num) {
		if (err)
			return callback(err);
		var view = {time: msg.time, ip: ip};
		var op = msg.op;
		if (msg.name)
			view.name = msg.name;
		if (msg.trip)
			view.trip = msg.trip;
		if (msg.email)
			view.email = msg.email;
		if (op)
			view.op = msg.op;
		if (msg.image)
			add_image_view(msg.image, view);

		var key = (op ? 'post:' : 'thread:') + num;
		var bump = !op || view.email != 'sage';
		var m = r.multi();
		if (bump)
			m.incr(tag_key + ':bumpctr');
		m.hmset(key, view);
		m.set(key + ':body', body);
		if (op)
			m.rpush('thread:' + op + ':posts', num);

		view.body = body;
		backlog_push(m, common.INSERT_POST, view);
		delete view.body;

		m.exec(function (err, results) {
			if (err)
				return callback(err);
			else if (!bump)
				return callback(null, num);
			r.zadd(tag_key + ':threads', results[0], num,
						function (err) {
				if (err)
					callback(err);
				else
					callback(null, num);
			});
		});
	});
};

function add_image_view(image, dest) {
	dest.imgMD5 = image.MD5;
	dest.imgext = image.ext;
	dest.imgsize = image.size;
	dest.imgtime = image.time;
}

Y.add_image = function (post_num, image, callback) {
	var r = this.connect();
	var key = 'post:' + post_num;
	r.exists(key, function (err, exists) {
		if (err)
			callback(err);
		else if (!exists)
			callback("Post does not exist.");
		else {
			var view = {};
			add_image_view(image, view);
			r.hmset(key, view, callback);
		}
	});
};

Y.append_post = function (num, is_reply, tail, limit, callback) {
	var r = this.connect();
	var key = (is_reply ? 'post:' : 'thread:') + num + ':body';
	/* Don't need to check .exists() thanks to client state */
	r.append(key, tail, function (err, new_len) {
		if (err)
			callback(err);
		else if (new_len > limit)
			trim_post(r, key, limit, callback, 0);
		else
			callback(null, new_len);
	});
};

function trim_post(r, key, limit, callback, tries) {
	r.watch(key);
	r.substr(key, 0, limit, function (err, body) {
		if (err || tries > 5) {
			console.error("Warning: Overlong post permitted");
			return callback(err);
		}
		r.multi().set(key, body).exec(function (err) {
			if (err)
				trim_post(r, key, limit, callback, tries+1);
			else
				callback(null, limit);
		});
	});
}

Y.finish_post = function (num, is_reply, callback) {
	var r = this.connect();
	var key = (is_reply ? 'post:' : 'thread:') + num;
	/* Don't need to check .exists() thanks to client state */
	r.get(key + ':body', function (err, body) {
		if (err)
			return callback(err);
		r.hmset(key, 'body', body, function (err) {
			if (err)
				callback(err);
			else
				r.del(key + ':body', callback);
		});
	});
};

function backlog_push(m, act) {
	var rec = [act];
	for (var i = 2; i < arguments.length; i++)
		rec.push(arguments[i]);
	m.rpush('backlog', JSON.stringify(rec));
}

Y.fetch_backlog = function (sync, watching, callback) {
	var r = this.connect();
	r.lrange('backlog', sync, -1, function (err, log) {
		if (err)
			return callback(err);
		// TODO: Do something with watching
		// Naive impl for now
		callback(null, sync + log.length, log);
	});
};

Y.get_sync_number = function (callback) {
	this.connect().llen('backlog', callback);
};

Y.thread_exists = function (num, callback) {
	this.connect().exists('thread:' + num, callback);
};

Y.get_tag = function () {
	var r = this.connect();
	var self = this;
	r.zrevrange('tag:' + this.tag + ':threads', 0, -1, function (err, ns) {
		if (err)
			return self.emit('error', err);
		self.emit('begin');
		var reader = new Reader(self);
		reader.on('error', self.emit.bind(self, 'error'));
		reader.on('thread', self.emit.bind(self, 'thread'));
		reader.on('post', self.emit.bind(self, 'post'));
		self._get_each_thread(reader, 0, ns);
	});
};

Y._get_each_thread = function (reader, ix, nums) {
	if (!nums || ix >= nums.length) {
		this.emit('end');
		return;
	}
	var next_please = this._get_each_thread.bind(this, reader, ix+1, nums);
	reader.once('end', next_please);
	reader.once('nomatch', next_please);
	reader.get_thread(nums[ix], false);
};

function Reader(yakusoku) {
	events.EventEmitter.call(this);
	this.y = yakusoku;
}

util.inherits(Reader, events.EventEmitter);
exports.Reader = Reader;

Reader.prototype.get_thread = function (num, redirect_ok) {
	var r = this.y.connect();
	var key = 'thread:' + num;
	var self = this;
	r.hgetall(key, function (err, pre_post) {
		if (err)
			return self.emit('error', err);
		if (is_empty(pre_post)) {
			if (!redirect_ok)
				return self.emit('nomatch');
			r.hget('post:' + num, 'op',
						function (err, op) {
				if (err)
					self.emit('error', err);
				else if (!op)
					self.emit('nomatch');
				else
					self.emit('redirect', op);
			});
			return;
		}
		self.emit('begin');
		pre_post.num = num;
		with_body(r, key, pre_post, function (err, post) {
			if (err)
				return self.emit('error', err);
			self.emit('thread', post);
			r.lrange(key + ':posts', 0, -1, function (err, nums) {
				if (err)
					return self.emit('error', err);
				self._get_each_reply(0, nums);
			});
		});
	});
};

Reader.prototype._get_each_reply = function (ix, nums) {
	if (!nums || ix >= nums.length)
		return this.emit('end');
	var r = this.y.connect();
	var num = nums[ix];
	var key = 'post:' + num;
	var next_please = this._get_each_reply.bind(this, ix + 1, nums);
	var self = this;
	r.hgetall(key, function (err, pre_post) {
		if (err)
			return self.emit('error', err);
		if (is_empty(pre_post))
			return next_please();
		pre_post.num = num;
		with_body(r, key, pre_post, function (err, post) {
			if (err)
				return self.emit('error', err);
			var has_next = ix + 1 < nums.length;
			self.emit('post', post, has_next);
			next_please();
		});
	});
};

function with_body(r, key, post, callback) {
	/* Convenience */
	post.time = parseInt(post.time);
	post.op = parseInt(post.op);

	if (post.body !== undefined)
		callback(null, post);
	else
		r.get(key + ':body', function (err, body) {
			if (err)
				return callback(err);
			if (body !== null) {
				post.body = body;
				post.editing = true;
				return callback(null, post);
			}
			// Race condition between finishing posts
			r.hget(key, 'body', function (err, body) {
				if (err)
					return callback(err);
				post.body = body;
				callback(null, post);
			});
		});
};
<commit_msg>Back to node.js stable<commit_after>var common = require('../common'),
    config = require('../config'),
    events = require('events'),
    redis = require('redis'),
    util = require('util');

function Yakusoku() {
	events.EventEmitter.call(this);
}

util.inherits(Yakusoku, events.EventEmitter);
exports.Yakusoku = Yakusoku;
var Y = Yakusoku.prototype;

Y.connect = function () {
	if (!this.r) {
		this.r = redis.createClient();
		this.r.on('error', (function (err) {
			console.error(err);
			/*
			this.r.stream.on('error', function (err) {
				console.error('stream: ' + err);
			});
			*/
		}).bind(this));
		/* TEMP */
		this.tag = '3:moe';
	}
	return this.r;
};

Y.disconnect = function () {
	if (this.r)
		this.r.quit();
};

function is_empty(obj) {
	if (!obj)
		return false;
	for (var key in obj)
		if (obj.hasOwnProperty(key))
			return false;
	return true;
}

Y.insert_post = function (msg, body, ip, callback) {
	var r = this.connect();
	var self = this;
	/* Multi isn't needed here, yay. */
	if (msg.op) {
		r.exists('thread:' + msg.op, function (err, exists) {
			if (err)
				callback(err);
			else if (!exists)
				callback('Thread does not exist.');
			else
				self._insert(msg, body, ip, callback);
		});
	}
	else
		self._insert(msg, body, ip, callback);
};

Y._insert = function (msg, body, ip, callback) {
	var r = this.connect();
	var tag_key = 'tag:' + this.tag;
	r.incr(tag_key + ':ctr', function (err, num) {
		if (err)
			return callback(err);
		var view = {time: msg.time, ip: ip};
		var op = msg.op;
		if (msg.name)
			view.name = msg.name;
		if (msg.trip)
			view.trip = msg.trip;
		if (msg.email)
			view.email = msg.email;
		if (op)
			view.op = msg.op;
		if (msg.image)
			add_image_view(msg.image, view);

		var key = (op ? 'post:' : 'thread:') + num;
		var bump = !op || view.email != 'sage';
		var m = r.multi();
		if (bump)
			m.incr(tag_key + ':bumpctr');
		m.hmset(key, view);
		m.set(key + ':body', body);
		if (op)
			m.rpush('thread:' + op + ':posts', num);

		view.body = body;
		backlog_push(m, common.INSERT_POST, view);
		delete view.body;

		m.exec(function (err, results) {
			if (err)
				return callback(err);
			else if (!bump)
				return callback(null, num);
			r.zadd(tag_key + ':threads', results[0], num,
						function (err) {
				if (err)
					callback(err);
				else
					callback(null, num);
			});
		});
	});
};

function add_image_view(image, dest) {
	dest.imgMD5 = image.MD5;
	dest.imgext = image.ext;
	dest.imgsize = image.size;
	dest.imgtime = image.time;
}

Y.add_image = function (post_num, image, callback) {
	var r = this.connect();
	var key = 'post:' + post_num;
	r.exists(key, function (err, exists) {
		if (err)
			callback(err);
		else if (!exists)
			callback("Post does not exist.");
		else {
			var view = {};
			add_image_view(image, view);
			r.hmset(key, view, callback);
		}
	});
};

Y.append_post = function (num, is_reply, tail, limit, callback) {
	var r = this.connect();
	var key = (is_reply ? 'post:' : 'thread:') + num + ':body';
	/* Don't need to check .exists() thanks to client state */
	r.append(key, tail, function (err, new_len) {
		if (err)
			callback(err);
		else if (new_len > limit)
			trim_post(r, key, limit, callback, 0);
		else
			callback(null, new_len);
	});
};

function trim_post(r, key, limit, callback, tries) {
	r.watch(key);
	r.substr(key, 0, limit, function (err, body) {
		if (err || tries > 5) {
			console.error("Warning: Overlong post permitted");
			return callback(err);
		}
		r.multi().set(key, body).exec(function (err) {
			if (err)
				trim_post(r, key, limit, callback, tries+1);
			else
				callback(null, limit);
		});
	});
}

Y.finish_post = function (num, is_reply, callback) {
	var r = this.connect();
	var key = (is_reply ? 'post:' : 'thread:') + num;
	/* Don't need to check .exists() thanks to client state */
	r.get(key + ':body', function (err, body) {
		if (err)
			return callback(err);
		r.hmset(key, 'body', body, function (err) {
			if (err)
				callback(err);
			else
				r.del(key + ':body', callback);
		});
	});
};

function backlog_push(m, act) {
	var rec = [act];
	for (var i = 2; i < arguments.length; i++)
		rec.push(arguments[i]);
	m.rpush('backlog', JSON.stringify(rec));
}

Y.fetch_backlog = function (sync, watching, callback) {
	var r = this.connect();
	r.lrange('backlog', sync, -1, function (err, log) {
		if (err)
			return callback(err);
		// TODO: Do something with watching
		// Naive impl for now
		callback(null, sync + log.length, log);
	});
};

Y.get_sync_number = function (callback) {
	this.connect().llen('backlog', callback);
};

Y.thread_exists = function (num, callback) {
	this.connect().exists('thread:' + num, callback);
};

Y.get_tag = function () {
	var r = this.connect();
	var self = this;
	r.zrevrange('tag:' + this.tag + ':threads', 0, -1, function (err, ns) {
		if (err)
			return self.emit('error', err);
		self.emit('begin');
		var reader = new Reader(self);
		reader.on('error', self.emit.bind(self, 'error'));
		reader.on('thread', self.emit.bind(self, 'thread'));
		reader.on('post', self.emit.bind(self, 'post'));
		self._get_each_thread(reader, 0, ns);
	});
};

Y._get_each_thread = function (reader, ix, nums) {
	if (!nums || ix >= nums.length) {
		this.emit('end');
		return;
	}
	var self = this;
	var next_please = function () {
		reader.removeListener('end', next_please);
		reader.removeListener('nomatch', next_please);
		self._get_each_thread(reader, ix+1, nums);
	};
	reader.on('end', next_please);
	reader.on('nomatch', next_please);
	reader.get_thread(nums[ix], false);
};

function Reader(yakusoku) {
	events.EventEmitter.call(this);
	this.y = yakusoku;
}

util.inherits(Reader, events.EventEmitter);
exports.Reader = Reader;

Reader.prototype.get_thread = function (num, redirect_ok) {
	var r = this.y.connect();
	var key = 'thread:' + num;
	var self = this;
	r.hgetall(key, function (err, pre_post) {
		if (err)
			return self.emit('error', err);
		if (is_empty(pre_post)) {
			if (!redirect_ok)
				return self.emit('nomatch');
			r.hget('post:' + num, 'op',
						function (err, op) {
				if (err)
					self.emit('error', err);
				else if (!op)
					self.emit('nomatch');
				else
					self.emit('redirect', op);
			});
			return;
		}
		self.emit('begin');
		pre_post.num = num;
		with_body(r, key, pre_post, function (err, post) {
			if (err)
				return self.emit('error', err);
			self.emit('thread', post);
			r.lrange(key + ':posts', 0, -1, function (err, nums) {
				if (err)
					return self.emit('error', err);
				self._get_each_reply(0, nums);
			});
		});
	});
};

Reader.prototype._get_each_reply = function (ix, nums) {
	if (!nums || ix >= nums.length)
		return this.emit('end');
	var r = this.y.connect();
	var num = nums[ix];
	var key = 'post:' + num;
	var next_please = this._get_each_reply.bind(this, ix + 1, nums);
	var self = this;
	r.hgetall(key, function (err, pre_post) {
		if (err)
			return self.emit('error', err);
		if (is_empty(pre_post))
			return next_please();
		pre_post.num = num;
		with_body(r, key, pre_post, function (err, post) {
			if (err)
				return self.emit('error', err);
			var has_next = ix + 1 < nums.length;
			self.emit('post', post, has_next);
			next_please();
		});
	});
};

function with_body(r, key, post, callback) {
	/* Convenience */
	post.time = parseInt(post.time);
	post.op = parseInt(post.op);

	if (post.body !== undefined)
		callback(null, post);
	else
		r.get(key + ':body', function (err, body) {
			if (err)
				return callback(err);
			if (body !== null) {
				post.body = body;
				post.editing = true;
				return callback(null, post);
			}
			// Race condition between finishing posts
			r.hget(key, 'body', function (err, body) {
				if (err)
					return callback(err);
				post.body = body;
				callback(null, post);
			});
		});
};
<|endoftext|>


<commit_before>// app.js

$(document).ready(function() {
  alert('bingo!');
});

<commit_msg>commentted alert demo in app.js<commit_after>// app.js

$(document).ready(function() {
  // alert('bingo!');
});

<|endoftext|>


<commit_before>/*
 * Default CKEditor preset and connector
 */

var ckeditorClasses = Array();
ckeditorClasses['editor'] = 'Full'; // Full editor
ckeditorClasses['medieditor'] = 'Medium'; // Medium editor
ckeditorClasses['minieditor'] = 'Basic'; // Mini editor

function ckeditorReplace() {
	var textareas = document.getElementsByTagName('textarea');
	for (var i = 0; i < textareas.length; i++) {
		if (ckeditorClasses[textareas[i].getAttribute('class')] !== undefined) {
			var textareasStyle = getComputedStyle(textareas[i], null) || textareas[i].currentStyle;
			CKEDITOR.replace(textareas[i], {height: parseInt(textareasStyle.height,10), width: parseInt(textareasStyle.width,10), toolbar: ckeditorClasses[textareas[i].getAttribute('class')]});
		}
	}
}

if (typeof jQuery == 'undefined') {
	if (window.addEventListener) {
		window.addEventListener('load', ckeditorReplace, false);
	} else if (window.attachEvent) {
		window.attachEvent('onload', ckeditorReplace);
	} else {
		window.onload = ckeditorReplace;
	}
} else {
	$(document).ready(ckeditorReplace);
	ajaxSuccessHandlers.push(ckeditorReplace);
}
<commit_msg>Correction of the sizes editor.<commit_after>/*
 * Default CKEditor preset and connector
 */

var ckeditorClasses = Array();
ckeditorClasses['editor'] = 'Full'; // Full editor
ckeditorClasses['medieditor'] = 'Medium'; // Medium editor
ckeditorClasses['minieditor'] = 'Basic'; // Mini editor

function ckeditorReplace() {
	var textareas = document.getElementsByTagName('textarea');
	for (var i = 0; i < textareas.length; i++) {
		if (ckeditorClasses[textareas[i].getAttribute('class')] !== undefined) {
			var textareasStyle = getComputedStyle(textareas[i], null) || textareas[i].currentStyle;
			CKEDITOR.replace(textareas[i], {height:parseInt(textareasStyle.height,10), width:parseInt(textareasStyle.width,10), toolbar: ckeditorClasses[textareas[i].getAttribute('class')]});
		}
	}
}

if (typeof jQuery == 'undefined') {
	if (window.addEventListener) {
		window.addEventListener('load', ckeditorReplace, false);
	} else if (window.attachEvent) {
		window.attachEvent('onload', ckeditorReplace);
	} else {
		window.onload = ckeditorReplace;
	}
} else {
	$(document).ready(ckeditorReplace);
	ajaxSuccessHandlers.push(ckeditorReplace);
}
<|endoftext|>


<commit_before>const VError = require("verror");
const ChannelQueue = require("@buttercup/channel-queue");
const AsyncEventEmitter = require("../events/AsyncEventEmitter.js");
const getUniqueID = require("../../tools/encoding.js").getUniqueID;
const createCredentials = require("../credentials.js");
const credentialsToSource = require("./marshalling.js").credentialsToSource;

const COLOUR_TEST = /^#([a-f0-9]{3}|[a-f0-9]{6})$/i;

const DefaultColour = "#000000";
const DefaultOrder = 1000;
/**
 * Archive source status
 * @enum ArchiveSourceStatus
 * @memberof ArchiveSource
 * @static
 */
const Status = {
    LOCKED: "locked",
    UNLOCKED: "unlocked",
    PENDING: "pending"
};

/**
 * Rehydrate a dehydrated archive source
 * @param {String} dehydratedString A dehydrated archive source
 * @returns {ArchiveSource} The rehydrated source
 * @memberof ArchiveSource
 * @static
 */
function rehydrate(dehydratedString) {
    const { name, id, sourceCredentials, archiveCredentials, type, colour, order } = JSON.parse(dehydratedString);
    const source = new ArchiveSource(name, sourceCredentials, archiveCredentials, id);
    source.type = type;
    if (colour) {
        source._colour = colour;
    }
    if (order >= 0) {
        source.order = order;
    }
    return source;
}

/**
 * Archive source class
 * @augments AsyncEventEmitter
 */
class ArchiveSource extends AsyncEventEmitter {
    /**
     * Constructor for an archive source
     * @param {String} name The name of the source
     * @param {String} sourceCredentials Encrypted archive source credentials
     * @param {String} archiveCredentials Encrypted archive credentials
     * @param {String=} id Optional source ID (Do not pass for new source)
     */
    constructor(name, sourceCredentials, archiveCredentials, id = getUniqueID()) {
        super();
        if (createCredentials.isSecureString(sourceCredentials) !== true) {
            throw new VError("Failed constructing archive source: Source credentials not in encrypted form");
        }
        if (createCredentials.isSecureString(archiveCredentials) !== true) {
            throw new VError("Failed constructing archive source: Archive credentials not in encrypted form");
        }
        this._queue = new ChannelQueue();
        this._name = name;
        this._id = id;
        this._status = Status.LOCKED;
        this._sourceCredentials = sourceCredentials;
        this._archiveCredentials = archiveCredentials;
        this._workspace = null;
        this._colour = DefaultColour;
        this.type = "";
        this.order = DefaultOrder;
    }

    /**
     * Source colour
     * @type {String}
     * @memberof ArchiveSource
     */
    get colour() {
        return this._colour;
    }

    /**
     * @typedef ArchiveSourceDescription
     * @property {String} name - The name of the source
     * @property {String} id - The source ID
     * @property {ArchiveSourceStatus} status - Status of the source
     * @property {String} type - The source type
     * @property {String} colour - Colour for the source
     * @property {Number} order - The order of the source
     */

    /**
     * Get the source description
     * @type {ArchiveSourceDescription}
     * @memberof ArchiveSource
     * @readonly
     */
    get description() {
        return {
            name: this.name,
            id: this.id,
            status: this.status,
            type: this.type,
            colour: this.colour,
            order: this.order
        };
    }

    /**
     * Source ID
     * @type {String}
     * @memberof ArchiveSource
     * @readonly
     */
    get id() {
        return this._id;
    }

    /**
     * Source name
     * @type {String}
     * @memberof ArchiveSource
     * @readonly
     */
    get name() {
        return this._name;
    }

    /**
     * Source status
     * @type {ArchiveSourceStatus}
     * @memberof ArchiveSource
     * @readonly
     */
    get status() {
        return this._status;
    }

    /**
     * Workspace instance for the source
     * Is null when the source is locked
     * @type {Workspace|null}
     * @memberof ArchiveSource
     * @readonly
     */
    get workspace() {
        return this._workspace;
    }

    set colour(newColour) {
        if (COLOUR_TEST.test(newColour) !== true) {
            throw new VError(`Failed setting colour: Invalid format (expected hex): ${newColour}`);
        }
        this._colour = newColour;
        this.emit("sourceColourUpdated", this.description);
    }

    /**
     * Dehydrate the source for storage
     * Returns a secure string with locked (encrypted) credentials, even when the
     *  source is in the UNLOCKED state. This method does NOT store the source -
     *  this must be done separately.
     * @returns {Promise.<String>} A promise that resolves with the dehydrated
     *  source information
     * @memberof ArchiveSource
     * @throws {VError} Rejects is source in PENDING state
     */
    dehydrate() {
        if (this.status === Status.PENDING) {
            return Promise.reject(new VError(`Failed dehydrating source: Source in pending state: ${this.id}`));
        }
        return this._enqueueStateChange(() => {
            return Promise.resolve()
                .then(() => {
                    const payload = {
                        id: this.id,
                        name: this.name,
                        type: this.type,
                        status: Status.LOCKED,
                        colour: this.colour,
                        order: this.order
                    };
                    if (this.status === Status.LOCKED) {
                        payload.sourceCredentials = this._sourceCredentials;
                        payload.archiveCredentials = this._archiveCredentials;
                        return payload;
                    }
                    return Promise.all([
                        this._sourceCredentials.toSecureString(this._archiveCredentials.password),
                        this._archiveCredentials.toSecureString(this._archiveCredentials.password)
                    ]).then(([encSourceCredentials, encArchiveCredentials]) => {
                        payload.sourceCredentials = encSourceCredentials;
                        payload.archiveCredentials = encArchiveCredentials;
                        return payload;
                    });
                })
                .then(payload => JSON.stringify(payload))
                .catch(err => {
                    throw new VError(err, `Failed dehydrating source: ${this.id}`);
                });
        });
    }

    /**
     * Lock the source
     * Encrypts the credentials and performs dehydration, placing the source into
     *  a LOCKED state. No saving is performed before locking.
     * @returns {Promise.<String>} A promise that resolves with dehydrated content
     * @throws {VError} Rejects if not in unlocked state
     * @fires ArchiveSource#sourceLocked
     * @memberof ArchiveSource
     */
    lock() {
        if (this.status !== Status.UNLOCKED) {
            return Promise.reject(
                new VError(`Failed locking source: Source in invalid state (${this.status}): ${this.id}`)
            );
        }
        this._status = Status.PENDING;
        return this._enqueueStateChange(() => {
            return Promise.all([
                this._sourceCredentials.toSecureString(this._archiveCredentials.password),
                this._archiveCredentials.toSecureString(this._archiveCredentials.password)
            ])
                .then(([encSourceCredentials, encArchiveCredentials]) => {
                    this._status = Status.LOCKED;
                    this._workspace = null;
                    this._sourceCredentials = encSourceCredentials;
                    this._archiveCredentials = encArchiveCredentials;
                })
                .catch(err => {
                    throw new VError(err, `Failed locking source: ${this.id}`);
                });
        }).then(() => {
            this.emit("sourceLocked", this.description);
            return this.dehydrate();
        });
    }

    /**
     * Unlock the source
     * @param {String} masterPassword The master password
     * @param {Boolean=} initialiseRemote Optionally initialise the remote (replaces
     *  remote archive) (defaults to false)
     * @memberof ArchiveSource
     * @throws {VError} Rejects if not in locked state
     * @throws {VError} Rejects if not able to create the source from the encrypted
     *  credentials
     * @fires ArchiveSource#sourceUnlocked
     */
    unlock(masterPassword, initialiseRemote = false) {
        if (this.status !== Status.LOCKED) {
            return Promise.reject(
                new VError(`Failed unlocking source: Source in invalid state (${this.status}): ${this.id}`)
            );
        }
        this._status = Status.PENDING;
        return this._enqueueStateChange(() => {
            return Promise.all([
                createCredentials.fromSecureString(this._sourceCredentials, masterPassword),
                createCredentials.fromSecureString(this._archiveCredentials, masterPassword)
            ])
                .then(([sourceCredentials, archiveCredentials] = []) => {
                    return credentialsToSource(sourceCredentials, archiveCredentials, initialiseRemote)
                        .then(sourceInfo => {
                            const { workspace, sourceCredentials, archiveCredentials } = sourceInfo;
                            this._workspace = workspace;
                            this._sourceCredentials = sourceCredentials;
                            this._archiveCredentials = archiveCredentials;
                            this._status = Status.UNLOCKED;
                            this.type = sourceCredentials.type;
                        })
                        .catch(err => {
                            throw new VError(err, "Failed mapping credentials to a source");
                        });
                })
                .then(() => {
                    this.emit("sourceUnlocked", this.description);
                })
                .catch(err => {
                    this._status = Status.LOCKED;
                    throw new VError(err, `Failed unlocking source: ${this.id}`);
                });
        });
    }

    /**
     * Update the password/credentials for the archive
     * The workspace is saved after changing. Responds with dehydrated details
     * which should be SAVED.
     * @param {String} masterPassword New master password
     * @returns {Promise.<String>} A promise that resolves with the dehydrated
     *  source details
     * @memberof ArchiveSource
     * @throws {VError} Rejects if source is not in unlocked state
     */
    updateArchiveCredentials(masterPassword) {
        if (this.status !== Status.UNLOCKED) {
            return Promise.reject(
                new VError(`Failed updating archive credentials: Source is not unlocked: ${this.id}`)
            );
        }
        return this._enqueueStateChange(() => {
            const credentials = createCredentials.fromPassword(masterPassword);
            // First update the credentials stored here
            this._archiveCredentials = credentials;
            // Then update the credentials in the workspace
            this.workspace.updatePrimaryCredentials(credentials);
            // Finally, dehydrate the source to save changes in the manager
            return (
                // Save the workspace to push the new password to file
                this.workspace
                    .save()
                    // Finally dehydrate
                    .then(() => this.dehydrate())
            );
        });
    }

    _enqueueStateChange(cb) {
        return this._queue.channel("state").enqueue(cb);
    }
}

ArchiveSource.DefaultColour = DefaultColour;
ArchiveSource.DefaultOrder = DefaultOrder;
ArchiveSource.Status = Status;

ArchiveSource.rehydrate = rehydrate;

module.exports = ArchiveSource;
<commit_msg>Remove dehydrate call from source<commit_after>const VError = require("verror");
const ChannelQueue = require("@buttercup/channel-queue");
const AsyncEventEmitter = require("../events/AsyncEventEmitter.js");
const getUniqueID = require("../../tools/encoding.js").getUniqueID;
const createCredentials = require("../credentials.js");
const credentialsToSource = require("./marshalling.js").credentialsToSource;

const COLOUR_TEST = /^#([a-f0-9]{3}|[a-f0-9]{6})$/i;

const DefaultColour = "#000000";
const DefaultOrder = 1000;
/**
 * Archive source status
 * @enum ArchiveSourceStatus
 * @memberof ArchiveSource
 * @static
 */
const Status = {
    LOCKED: "locked",
    UNLOCKED: "unlocked",
    PENDING: "pending"
};

/**
 * Rehydrate a dehydrated archive source
 * @param {String} dehydratedString A dehydrated archive source
 * @returns {ArchiveSource} The rehydrated source
 * @memberof ArchiveSource
 * @static
 */
function rehydrate(dehydratedString) {
    const { name, id, sourceCredentials, archiveCredentials, type, colour, order } = JSON.parse(dehydratedString);
    const source = new ArchiveSource(name, sourceCredentials, archiveCredentials, id);
    source.type = type;
    if (colour) {
        source._colour = colour;
    }
    if (order >= 0) {
        source.order = order;
    }
    return source;
}

/**
 * Archive source class
 * @augments AsyncEventEmitter
 */
class ArchiveSource extends AsyncEventEmitter {
    /**
     * Constructor for an archive source
     * @param {String} name The name of the source
     * @param {String} sourceCredentials Encrypted archive source credentials
     * @param {String} archiveCredentials Encrypted archive credentials
     * @param {String=} id Optional source ID (Do not pass for new source)
     */
    constructor(name, sourceCredentials, archiveCredentials, id = getUniqueID()) {
        super();
        if (createCredentials.isSecureString(sourceCredentials) !== true) {
            throw new VError("Failed constructing archive source: Source credentials not in encrypted form");
        }
        if (createCredentials.isSecureString(archiveCredentials) !== true) {
            throw new VError("Failed constructing archive source: Archive credentials not in encrypted form");
        }
        this._queue = new ChannelQueue();
        this._name = name;
        this._id = id;
        this._status = Status.LOCKED;
        this._sourceCredentials = sourceCredentials;
        this._archiveCredentials = archiveCredentials;
        this._workspace = null;
        this._colour = DefaultColour;
        this.type = "";
        this.order = DefaultOrder;
    }

    /**
     * Source colour
     * @type {String}
     * @memberof ArchiveSource
     */
    get colour() {
        return this._colour;
    }

    /**
     * @typedef ArchiveSourceDescription
     * @property {String} name - The name of the source
     * @property {String} id - The source ID
     * @property {ArchiveSourceStatus} status - Status of the source
     * @property {String} type - The source type
     * @property {String} colour - Colour for the source
     * @property {Number} order - The order of the source
     */

    /**
     * Get the source description
     * @type {ArchiveSourceDescription}
     * @memberof ArchiveSource
     * @readonly
     */
    get description() {
        return {
            name: this.name,
            id: this.id,
            status: this.status,
            type: this.type,
            colour: this.colour,
            order: this.order
        };
    }

    /**
     * Source ID
     * @type {String}
     * @memberof ArchiveSource
     * @readonly
     */
    get id() {
        return this._id;
    }

    /**
     * Source name
     * @type {String}
     * @memberof ArchiveSource
     * @readonly
     */
    get name() {
        return this._name;
    }

    /**
     * Source status
     * @type {ArchiveSourceStatus}
     * @memberof ArchiveSource
     * @readonly
     */
    get status() {
        return this._status;
    }

    /**
     * Workspace instance for the source
     * Is null when the source is locked
     * @type {Workspace|null}
     * @memberof ArchiveSource
     * @readonly
     */
    get workspace() {
        return this._workspace;
    }

    set colour(newColour) {
        if (COLOUR_TEST.test(newColour) !== true) {
            throw new VError(`Failed setting colour: Invalid format (expected hex): ${newColour}`);
        }
        this._colour = newColour;
        this.emit("sourceColourUpdated", this.description);
    }

    /**
     * Dehydrate the source for storage
     * Returns a secure string with locked (encrypted) credentials, even when the
     *  source is in the UNLOCKED state. This method does NOT store the source -
     *  this must be done separately.
     * @returns {Promise.<String>} A promise that resolves with the dehydrated
     *  source information
     * @memberof ArchiveSource
     * @throws {VError} Rejects is source in PENDING state
     */
    dehydrate() {
        if (this.status === Status.PENDING) {
            return Promise.reject(new VError(`Failed dehydrating source: Source in pending state: ${this.id}`));
        }
        return this._enqueueStateChange(() => {
            return Promise.resolve()
                .then(() => {
                    const payload = {
                        id: this.id,
                        name: this.name,
                        type: this.type,
                        status: Status.LOCKED,
                        colour: this.colour,
                        order: this.order
                    };
                    if (this.status === Status.LOCKED) {
                        payload.sourceCredentials = this._sourceCredentials;
                        payload.archiveCredentials = this._archiveCredentials;
                        return payload;
                    }
                    return Promise.all([
                        this._sourceCredentials.toSecureString(this._archiveCredentials.password),
                        this._archiveCredentials.toSecureString(this._archiveCredentials.password)
                    ]).then(([encSourceCredentials, encArchiveCredentials]) => {
                        payload.sourceCredentials = encSourceCredentials;
                        payload.archiveCredentials = encArchiveCredentials;
                        return payload;
                    });
                })
                .then(payload => JSON.stringify(payload))
                .catch(err => {
                    throw new VError(err, `Failed dehydrating source: ${this.id}`);
                });
        });
    }

    /**
     * Lock the source
     * Encrypts the credentials and performs dehydration, placing the source into
     *  a LOCKED state. No saving is performed before locking.
     * @returns {Promise.<String>} A promise that resolves with dehydrated content
     * @throws {VError} Rejects if not in unlocked state
     * @fires ArchiveSource#sourceLocked
     * @memberof ArchiveSource
     */
    lock() {
        if (this.status !== Status.UNLOCKED) {
            return Promise.reject(
                new VError(`Failed locking source: Source in invalid state (${this.status}): ${this.id}`)
            );
        }
        this._status = Status.PENDING;
        return this._enqueueStateChange(() => {
            return Promise.all([
                this._sourceCredentials.toSecureString(this._archiveCredentials.password),
                this._archiveCredentials.toSecureString(this._archiveCredentials.password)
            ])
                .then(([encSourceCredentials, encArchiveCredentials]) => {
                    this._status = Status.LOCKED;
                    this._workspace = null;
                    this._sourceCredentials = encSourceCredentials;
                    this._archiveCredentials = encArchiveCredentials;
                })
                .catch(err => {
                    throw new VError(err, `Failed locking source: ${this.id}`);
                });
        }).then(() => {
            this.emit("sourceLocked", this.description);
            return this.dehydrate();
        });
    }

    /**
     * Unlock the source
     * @param {String} masterPassword The master password
     * @param {Boolean=} initialiseRemote Optionally initialise the remote (replaces
     *  remote archive) (defaults to false)
     * @memberof ArchiveSource
     * @throws {VError} Rejects if not in locked state
     * @throws {VError} Rejects if not able to create the source from the encrypted
     *  credentials
     * @fires ArchiveSource#sourceUnlocked
     */
    unlock(masterPassword, initialiseRemote = false) {
        if (this.status !== Status.LOCKED) {
            return Promise.reject(
                new VError(`Failed unlocking source: Source in invalid state (${this.status}): ${this.id}`)
            );
        }
        this._status = Status.PENDING;
        return this._enqueueStateChange(() => {
            return Promise.all([
                createCredentials.fromSecureString(this._sourceCredentials, masterPassword),
                createCredentials.fromSecureString(this._archiveCredentials, masterPassword)
            ])
                .then(([sourceCredentials, archiveCredentials] = []) => {
                    return credentialsToSource(sourceCredentials, archiveCredentials, initialiseRemote)
                        .then(sourceInfo => {
                            const { workspace, sourceCredentials, archiveCredentials } = sourceInfo;
                            this._workspace = workspace;
                            this._sourceCredentials = sourceCredentials;
                            this._archiveCredentials = archiveCredentials;
                            this._status = Status.UNLOCKED;
                            this.type = sourceCredentials.type;
                        })
                        .catch(err => {
                            throw new VError(err, "Failed mapping credentials to a source");
                        });
                })
                .then(() => {
                    this.emit("sourceUnlocked", this.description);
                })
                .catch(err => {
                    this._status = Status.LOCKED;
                    throw new VError(err, `Failed unlocking source: ${this.id}`);
                });
        });
    }

    /**
     * Update the password/credentials for the archive
     * The workspace is saved after changing. Responds with dehydrated details
     * which should be SAVED.
     * @param {String} masterPassword New master password
     * @returns {Promise.<String>} A promise that resolves with the dehydrated
     *  source details
     * @memberof ArchiveSource
     * @throws {VError} Rejects if source is not in unlocked state
     */
    updateArchiveCredentials(masterPassword) {
        if (this.status !== Status.UNLOCKED) {
            return Promise.reject(
                new VError(`Failed updating archive credentials: Source is not unlocked: ${this.id}`)
            );
        }
        return this._enqueueStateChange(() => {
            const credentials = createCredentials.fromPassword(masterPassword);
            // First update the credentials stored here
            this._archiveCredentials = credentials;
            // Then update the credentials in the workspace
            this.workspace.updatePrimaryCredentials(credentials);
            // Save the workspace to push the new password to file
            return this.workspace.save();
        });
    }

    _enqueueStateChange(cb) {
        return this._queue.channel("state").enqueue(cb);
    }
}

ArchiveSource.DefaultColour = DefaultColour;
ArchiveSource.DefaultOrder = DefaultOrder;
ArchiveSource.Status = Status;

ArchiveSource.rehydrate = rehydrate;

module.exports = ArchiveSource;
<|endoftext|>


<commit_before>const socketio = require('socket.io');
const utils = require('./utils');
const eth = require('./eth');

const web3 = eth.web3;
const SpiceMembers = eth.contracts.SpiceMembers;
const SpiceHours = eth.contracts.SpiceHours;

const pendingTransactions = {};

function handleTransaction(io, name, tx) {
  if (!name) return; // No name, send nothing

  const timeout = 240000;
  const start = new Date().getTime();

  if (!tx.blockNumber) {
    io.emit(name + '/pending', JSON.stringify(tx));
    pendingTransactions[tx.hash] = tx;
  }

  function sendAfterReceipt() {
    web3.eth.getTransactionReceipt(tx.hash, (err, receipt) => {
      if (err) return io.emit('error', err.message);

      if (receipt) {
        delete pendingTransactions[tx.hash];
        web3.eth.getTransaction(tx.hash, (err, tx) => {
          if (err) return io.emit('error', err.message);
          io.emit(name + '/tx', JSON.stringify(tx));
          io.emit(name + '/receipt', JSON.stringify(receipt));
        });
      } else if (timeout > 0 && new Date().getTime() - start > timeout) {
        io.emit('error', `Transaction ${tx.hash} wasn't processed in ${timeout / 1000} seconds`);
      } else {
        setTimeout(sendAfterReceipt, 200);
      }
    });
  }

  sendAfterReceipt();
}

function attachTransactions(io, addresses) {
  const pendingFilter = web3.eth.filter('pending');
  pendingFilter.watch((err, txid) =>
    web3.eth.getTransaction(txid, (err, tx) => {
      handleTransaction(io, addresses[tx.to], tx);
    })
  );
  return pendingFilter;
}

function attachEvent(io, name, event, proc) {
  const eventFilter = event();
  eventFilter.watch((err, event) => {
    if (err) return io.emit('error', err.message);
    if (proc) event = proc(event);
    io.emit(name + '/event', JSON.stringify(event));
  });
  return eventFilter;
}

function processEvent(event) {
  const args = event.args;
  if (args) {
    if (args.info) args.info = utils.decryptInfo(args.info);
    args.description = utils.bytes32ToStr(args.description);
  }
  return event;
}

function attach(io) {
  const addresses = {
    [SpiceMembers.address]: 'members',
    [SpiceHours.address]: 'hours'
  };
  attachTransactions(io, addresses);

  const hours = SpiceHours.deployed();
  attachEvent(io, 'hours', hours.MarkHours, processEvent);
  attachEvent(io, 'hours', hours.ProcessPayroll, processEvent);
}

exports.pending = pendingTransactions;
exports.attach = attach;
<commit_msg>Modify eventapi transaction handling, should rewrite event handling.<commit_after>const socketio = require('socket.io');
const utils = require('./utils');
const eth = require('./eth');

const web3 = eth.web3;
const SpiceMembers = eth.contracts.SpiceMembers;
const SpiceHours = eth.contracts.SpiceHours;
const SpiceRates = eth.contracts.SpiceRates;

const pendingTransactions = {};

function handleTransaction(io, name, tx) {
  const timeout = 0;
  const start = new Date().getTime();

  if (!tx.blockNumber) {
    io.emit(name + '/pending', JSON.stringify(tx));
    pendingTransactions[tx.hash] = tx;
  }

  function sendAfterReceipt() {
    web3.eth.getTransactionReceipt(tx.hash, (err, receipt) => {
      if (err) return io.emit('error', err.message);

      if (receipt) {
        delete pendingTransactions[tx.hash];
        web3.eth.getTransaction(tx.hash, (err, tx) => {
          if (err) return io.emit('error', err.message);
          io.emit(name + '/tx', JSON.stringify(tx));
          io.emit(name + '/receipt', JSON.stringify(receipt));
        });
      } else if (timeout > 0 && new Date().getTime() - start > timeout) {
        io.emit('error', `Transaction ${tx.hash} wasn't processed in ${timeout / 1000} seconds`);
      } else {
        setTimeout(sendAfterReceipt, 200);
      }
    });
  }

  sendAfterReceipt();
}

function findContract(contracts, address) {
  return Object.keys(contracts)
    .map(name => [name, contracts[name]])
    .find(([name, contract]) => (address == contract.address));
}

function attachTransactions(io, contracts) {
  const pendingFilter = web3.eth.filter('pending');
  pendingFilter.watch((err, txid) =>
    web3.eth.getTransaction(txid, (err, tx) => {
      const contractInfo = findContract(contracts, tx.to);
      if (contractInfo) {
        handleTransaction(io, contractInfo[0], tx);
      }
    })
  );
}

function attachEvents(io, name, eventFilter, proc) {
  eventFilter.watch((err, event) => {
    if (err) return io.emit('error', err.message);
    if (proc) event = proc(event);
    io.emit(name + '/event', JSON.stringify(event));
  });
}

function processEvent(event) {
  const args = event.args;
  if (args) {
    if (args.info) args.info = utils.decryptInfo(args.info);
    args.description = utils.bytes32ToStr(args.description);
  }
  return event;
}

function attach(io) {
  const contracts = {
    members: SpiceMembers,
    hours: SpiceHours,
    rates: SpiceRates
  };
  attachTransactions(io, contracts);

  const hours = SpiceHours.deployed();
  attachEvents(io, 'hours', hours.allEvents(), processEvent);

  const rates = SpiceRates.deployed();
  attachEvents(io, 'rates', rates.allEvents(), processEvent);
}

exports.pending = pendingTransactions;
exports.attach = attach;
<|endoftext|>


<commit_before>// Use Bootstrap table
$("table").addClass("table table-hover").wrap("
");

// Remove valign="top" and align="right"
$("td, th").removeAttr("align").removeAttr("valign");

// Replace the first  into
$("td:first-child").changeElementType("");

// Move all rows in

$("table").prependIfNotExist("tbody", "
").append($("tr"));

// Move table header in

$("table").prependIfNotExist("thead", "
").append($("tr").first());

// Clear the icon column of the header
$("th").first().empty();

// Trim each cells inner html
$("td, th").each(function() {
	$(this).html($(this).html().trim());
});

// Get the search query string
const search = window.location.search;

// Parse autoindex request query arguments
// As mod_autoindex still use ";" as separators URLSearchParams cannot be used
// So let's create a two dimensional array with the arguments
const args_array = search.split(/^\?|;|&/).filter(Boolean).map(e => e.split("="));
// Then convert it into a dictionary
const args = Object.fromEntries(args_array);

// Add sorting arrows
const sort_element_by_arg = {
	"N": $("thead th").eq(-3),
	"M": $("thead th").eq(-2),
	"S": $("thead th").last(),
};

const sort_class_by_arg = {
	"A": "fa-fw fas fa-sort-down",
	"D": "fa-fw fas fa-sort-up",
};

if ("C" in args && "O" in args) {
	var icon = $("").addClass(sort_class_by_arg[args["O"]]);
	sort_element_by_arg[args["C"]].append(icon);
}

const icon_classe_by_type = {
	"default": "fa-fw far fa-file",
	"directory": "fa-fw fas fa-folder",
	"archive": "fa-fw far fa-file-archive",
	"audio": "fa-fw far fa-file-audio",
	"code": "fa-fw far fa-file-code",
	"excel": "fa-fw far fa-file-excel",
	"image": "fa-fw far fa-file-image",
	"pdf": "fa-fw far fa-file-pdf",
	"powerpoint": "fa-fw far fa-file-powerpoint",
	"text": "fa-fw far fa-file-alt",
	"video": "fa-fw far fa-file-video",
	"word": "fa-fw far fa-file-word",
};

const m_server = moment(server_date, moment.ISO_8601);

// Parse each row
$("tbody > tr").each(function(index) {
	var icon = $("th", this).first();
	var date = $("td", this).eq(-2);
	var size = $("td", this).last();
	var link = $("a", this).first();

	// Read the type from the  alt text
	var type = $("img", icon).attr("alt").replace(/[\[\] ]+/g, "");

	// Replace the icon
	if (type in icon_classe_by_type) {
		icon.html("");
	} else {
		icon.html("");
	}

	// Beautify date
	var m = moment(date.html(), "YYYY-MM-DD HH:mm");

	if (moment().diff(m, "days", true) < 1) {
		date.html(m.from(m_server));

	} else if (moment().diff(m, "weeks", true) < 1) {
		date.html(m.format("dddd LT"));

	} else {
		date.html(m.format("LLL"));
	}

	// Remove the parent row
	if (type === "parent") {
		$(this).remove();

	// Update directory style
	} else if (type === "directory") {
		link.attr("href", link.attr("href") + search);
		size.empty();
	}
});

// Get the path segments and remove empty fields
const path_segments = window.location.pathname.split("/").filter(Boolean);

// Create a name / url object for each path segments
var path_url = "/";
var path_urls = [{name: "", url: path_url + search}];
path_segments.forEach(function(segment) {
	path_url += segment + "/";
	path_urls.push({name: decodeURIComponent(segment), url: path_url + search});
});

// Create the breadcrumb elements for each previous objects
path_urls.forEach(function(url) {
	var a = $("").attr("href", url.url).html(url.name);
	var li = $("
").addClass("breadcrumb-item").append(a);
	$(".breadcrumb").append(li);
});

// Set the last breadcrumb item as active
$(".breadcrumb li").last().addClass("active").children("a").contents().unwrap();

// Show main
$("main").show();
<commit_msg>js: minor formatting<commit_after>// Use Bootstrap table
$("table").addClass("table table-hover").wrap("
");

// Remove valign="top" and align="right"
$("td, th").removeAttr("align").removeAttr("valign");

// Replace the first  into
$("td:first-child").changeElementType("");

// Move all rows in

$("table").prependIfNotExist("tbody", "
").append($("tr"));

// Move table header in

$("table").prependIfNotExist("thead", "
").append($("tr").first());

// Clear the icon column of the header
$("th").first().empty();

// Trim each cells inner html
$("td, th").each(function() {
	$(this).html($(this).html().trim());
});

// Get the search query string
const search = window.location.search;

// Parse autoindex request query arguments
// As mod_autoindex still use ";" as separators URLSearchParams cannot be used
// So let's create a two dimensional array with the arguments
const args_array = search.split(/^\?|;|&/).filter(Boolean).map(e => e.split("="));
// Then convert it into a dictionary
const args = Object.fromEntries(args_array);

// Add sorting arrows
const sort_element_by_arg = {
	"N": $("thead th").eq(-3),
	"M": $("thead th").eq(-2),
	"S": $("thead th").last(),
};

const sort_class_by_arg = {
	"A": "fa-fw fas fa-sort-down",
	"D": "fa-fw fas fa-sort-up",
};

if ("C" in args && "O" in args) {
	var icon = $("").addClass(sort_class_by_arg[args["O"]]);
	sort_element_by_arg[args["C"]].append(icon);
}

// Icon classes
const icon_class_by_type = {
	"default": "fa-fw far fa-file",
	"directory": "fa-fw fas fa-folder",
	"archive": "fa-fw far fa-file-archive",
	"audio": "fa-fw far fa-file-audio",
	"code": "fa-fw far fa-file-code",
	"excel": "fa-fw far fa-file-excel",
	"image": "fa-fw far fa-file-image",
	"pdf": "fa-fw far fa-file-pdf",
	"powerpoint": "fa-fw far fa-file-powerpoint",
	"text": "fa-fw far fa-file-alt",
	"video": "fa-fw far fa-file-video",
	"word": "fa-fw far fa-file-word",
};

// Server current date
const m_server = moment(server_date, moment.ISO_8601);

// Parse each row
$("tbody > tr").each(function(index) {
	var icon = $("th", this).first();
	var date = $("td", this).eq(-2);
	var size = $("td", this).last();
	var link = $("a", this).first();

	// Read the type from the  alt text
	var type = $("img", icon).attr("alt").replace(/[\[\] ]+/g, "");

	// Replace the icon
	if (type in icon_class_by_type) {
		icon.html("");
	} else {
		icon.html("");
	}

	// Beautify date
	var m = moment(date.html(), "YYYY-MM-DD HH:mm");

	if (moment().diff(m, "days", true) < 1) {
		date.html(m.from(m_server));

	} else if (moment().diff(m, "weeks", true) < 1) {
		date.html(m.format("dddd LT"));

	} else {
		date.html(m.format("LLL"));
	}

	// Remove the parent row
	if (type === "parent") {
		$(this).remove();

	// Update directory style
	} else if (type === "directory") {
		link.attr("href", link.attr("href") + search);
		size.empty();
	}
});

// Get the path segments and remove empty fields
const path_segments = window.location.pathname.split("/").filter(Boolean);

// Create a name / url object for each path segments
var path_url = "/";
var path_urls = [{name: "", url: path_url + search}];
path_segments.forEach(function(segment) {
	path_url += segment + "/";
	path_urls.push({name: decodeURIComponent(segment), url: path_url + search});
});

// Create the breadcrumb elements for each previous objects
path_urls.forEach(function(url) {
	var a = $("").attr("href", url.url).html(url.name);
	var li = $("
").addClass("breadcrumb-item").append(a);
	$(".breadcrumb").append(li);
});

// Set the last breadcrumb item as active
$(".breadcrumb li").last().addClass("active").children("a").contents().unwrap();

// Show main
$("main").show();
<|endoftext|>


<commit_before>var bookstore = $.import("de.linuxdozent.gittest.anonymous", "bookstore");
var speech;
var apiResponse = {
    "speech": "",
    "displayText":""
};

$.response.contentType = "application/json";
if($.request.method === $.net.http.POST) {
    var content = $.request.body.asString();
    var request = JSON.parse(content);
    if(request.result.action === "ListBooks") {
        var books = bookstore.readBooks();
        if(books.length > 0) {
            speech = "Wir führen: ";
            var arrayLength = books.length;
            for (var i = 0; i < arrayLength; i++) {
                speech += books[i].BookTitle + " ";
            }
        } else {
            speech = "Leider sind noch keine Bücher gelistet.";
        }
        apiResponse.speech = speech;
        apiResponse.displayText = speech;
        $.trace.error("Response: " + speech);
    }
    $.response.setBody(JSON.stringify(apiResponse));
}<commit_msg>Update linefeed<commit_after>var bookstore = $.import("de.linuxdozent.gittest.anonymous", "bookstore");
var speech;
var apiResponse = {
    "speech": "",
    "displayText":""
};

$.response.contentType = "application/json";
if($.request.method === $.net.http.POST) {
    var content = $.request.body.asString();
    var request = JSON.parse(content);
    if(request.result.action === "ListBooks") {
        var books = bookstore.readBooks();
        if(books.length > 0) {
            speech = "Wir führen: ";
            var arrayLength = books.length;
            for (var i = 0; i < arrayLength; i++) {
                speech += books[i].BookTitle + " ";
            }
        } else {
            speech = "Leider sind noch keine Bücher gelistet.";
        }
        apiResponse.speech = speech;
        apiResponse.displayText = speech;
        $.trace.error("Response: " + speech);
    }
    $.response.setBody(JSON.stringify(apiResponse));
}
<|endoftext|>


<commit_before>

  function something()
  {
  	var x = window.localStorage.getItem('bbb'); // x = hh['bbb']

  	x = x * 1 + 1; // x = x + 1

  	window.localStorage.setItem('bbb', x); // hh['bbb'] = x

  	alert(x);
  }

function add_to_cart(id)
{
	var key = 'product_' + id

	var x = window.localStorage.getItem(key);
	x = x * 1 + 1;
	window.localStorage.setItem(key, x);

	update_orders_input();
	update_orders_button();
}

function update_orders_input()
{
	var orders = cart_get_orders();
	$('#orders_input').val(orders);
}

function update_orders_button()
{
	var text = 'Cart (' + cart_get_number_of_items() + ')';
	$('#orders_button').val(text);
}

function cart_get_number_of_items()
{
	var cnt = 0;

	for(var i = 0; i < window.localStorage.length; i++)
	{
		var key = window.localStorage.key(i); // get key
		var value = window.localStorage.getItem(key); // get value, same as in Ruby: hh[key] = x

		if(key.indexOf('product_') == 0)
		{
			cnt = cnt + value * 1;
		}
	}

	return cnt;
}

function cart_get_orders()
{
	var orders = '';

	for(var i = 0; i < window.localStorage.length; i++)
	{
		var key = window.localStorage.key(i); // get key
		var value = window.localStorage.getItem(key); // get value, same as in Ruby: hh[key] = x

		if(key.indexOf('product_') == 0)
		{
			orders = orders + key + '=' + value + ',';
		}
	}

	return orders;
}

function cancel_order()
{
	alert('aaa');
	return false;
}
<commit_msg>Update cancel function<commit_after>

  function something()
  {
  	var x = window.localStorage.getItem('bbb'); // x = hh['bbb']

  	x = x * 1 + 1; // x = x + 1

  	window.localStorage.setItem('bbb', x); // hh['bbb'] = x

  	alert(x);
  }

function add_to_cart(id)
{
	var key = 'product_' + id

	var x = window.localStorage.getItem(key);
	x = x * 1 + 1;
	window.localStorage.setItem(key, x);

	update_orders_input();
	update_orders_button();
}

function update_orders_input()
{
	var orders = cart_get_orders();
	$('#orders_input').val(orders);
}

function update_orders_button()
{
	var text = 'Cart (' + cart_get_number_of_items() + ')';
	$('#orders_button').val(text);
}

function cart_get_number_of_items()
{
	var cnt = 0;

	for(var i = 0; i < window.localStorage.length; i++)
	{
		var key = window.localStorage.key(i); // get key
		var value = window.localStorage.getItem(key); // get value, same as in Ruby: hh[key] = x

		if(key.indexOf('product_') == 0)
		{
			cnt = cnt + value * 1;
		}
	}

	return cnt;
}

function cart_get_orders()
{
	var orders = '';

	for(var i = 0; i < window.localStorage.length; i++)
	{
		var key = window.localStorage.key(i); // get key
		var value = window.localStorage.getItem(key); // get value, same as in Ruby: hh[key] = x

		if(key.indexOf('product_') == 0)
		{
			orders = orders + key + '=' + value + ',';
		}
	}

	return orders;
}

function cancel_order()
{
	window.localStorage.clear();
	update_orders_input();
	update_orders_button();
	return false;
}
<|endoftext|>


<commit_before>import { html, render } from "lit";
import { defineElement as defineBentoAccordion } from "@bentoproject/accordion";

const template = ({ names }) => html`
  <bento-accordion animate>
    ${names.map((name) => html`

${name}

Content for ${name}

`
    )}
  </bento-accordion>
`;

async function getData() {
  // Fetch some remote data.
  // For this example, we will return some dummy data
  return Promise.resolve({names: ['Alice', 'Bob', 'Charlie', 'David']});
}

async function app() {
  // Define all imported Bento components when you start your app
  defineBentoAccordion();

  render(template(await getData()), document.body);
}

app();
<commit_msg>minor updates<commit_after>import { html, render } from "lit-html";
import { defineElement as defineBentoAccordion } from "@bentoproject/accordion";

const template = ({ names }) => html`
  <bento-accordion animate>
    ${names.map((name) => html`

${name}

Hello ${name}

`
    )}
  </bento-accordion>
`;

async function getData() {
  // Fetch some remote data. For this example, we will return some
  // dummy data wrapped in a promise.
  return Promise.resolve({names: ['Alice', 'Bob', 'Charlie', 'David']});
}

async function app() {
  // Define all imported Bento components when you start your app
  defineBentoAccordion();

  render(template(await getData()), document.body);
}

app();
<|endoftext|>