<commit_before>/*
 * fis
 * http://web.baidu.com/
 */

'use strict';

var fis = require('../fis-kernel.js');
var tar, remote;

exports.DEFAULT_REMOTE_REPOS = 'http://web.baidu.com/fis_repos';
exports.DEFAULT_CONF_FILE = 'fis-conf.json';

var FIS_CONF;

Object.defineProperty(exports, 'conf', {
    enumerable : true,
    set : function(value){
        if(fis.util.isFile(value)){
            FIS_CONF = fis.util.realpath(value);
            fis.config.merge(fis.util.readJSON(FIS_CONF));
        } else {
            fis.log.error('invalid fis-conf file [' + value + ']');
        }
    },
    get : function(){
        if(!FIS_CONF){
            fis.log.error('undefined fis-conf file');
        }
        return FIS_CONF;
    }
});

exports.init = function(root, conf, callback){

    if(fis.util.isDir(root)){
        //set root
        exports.setProjectRoot(root);

        if(typeof conf === 'function'){
            callback = conf;
            conf = fis.config.get('system.conf', exports.DEFAULT_CONF_FILE);
        }

        exports.conf = conf;

        //undownloaded dependencies counter
        var count = 0;

        //download remote
        remote = fis.config.get('system.repos', exports.DEFAULT_REMOTE_REPOS).replace(/\/$/, '');

        //traverse dependencies
        fis.util.map(fis.config.get('deps', {}), function(name, version){
            var dir = exports.getLibPath(name, version, name);
            var flag = dir + '/.inited';
            if(!fis.util.exists(flag)){
                count++;
                var url = remote + '/' + name + '/' + version + '.tar';
                process.stdout.write('download module [' + name + '@' + version + '] ... ');
                fis.util.download(url, function(err, file){
                    if(err){
                        process.stdout.write('fail\n');
                        fis.log.error( 'unable to download modules [' +
                            name + '@' + version + '] from [' + url + '], error [' + err + ']');
                    } else {
                        tar = tar || require('tar');
                        fis.util.fs
                            .createReadStream(file)
                            .pipe(tar.Extract({
                                path : dir
                            }))
                            .on('error', function(err){
                                process.stdout.write('fail\n');
                                fis.log.error(err);
                            })
                            .on('end', function(){
                                process.stdout.write('ok\n');
                                var str = [
                                    'name : ' + name + '@' + version,
                                    'from : ' + url,
                                    'time : ' + (new Date)
                                ].join('\n');
                                fis.util.write(flag, str);
                                if(--count == 0){
                                    callback();
                                }
                            });
                    }
                });
            }
        });
        if(count == 0){
            callback();
        }
    } else {
        fis.log.error('invalid project root dir [' + root + ']');
    }
};

exports.getSource = function(opt){
    var root = exports.getProjectPath(),
        dirs = [root];
    fis.util.map(fis.config.get('deps', {}), function(name, version){
        var dir = exports.getLibPath(name, version, name);
        if(fis.util.isDir(dir)){
            dir = fis.util.realpath(dir);
            dirs.push(dir);
        } else {
            fis.log.error('unable to find dependency [' + name + '@' + version + ']');
        }
    });
    var source = {};
    opt = opt || {};
    dirs.forEach(function(path){
        fis.util.find(path, opt.include, opt.exclude).forEach(function(file){
            if(file !== exports.conf){
                file = fis.file(file);
                source[file.subpath] = file;
            }
        });
    });
    return source;
}

//paths
var PROJECT_ROOT;
var LIB_ROOT;
var TEMP_ROOT;

function getPath(root, args){
    if(args && args.length > 0){
        args = root + '/' + Array.prototype.join.call(args, '/');
        return fis.util(args);
    } else {
        return root;
    }
}

function initDir(path, title){
    if(fis.util.exists(path)){
        if(!fis.util.isDir(path)){
            fis.log.error('unable to set path[' + path + '] as ' + title + ' directory.');
        }
    } else {
        fis.util.mkdir(path);
    }
    path = fis.util.realpath(path);
    if(path){
        return path;
    } else {
        fis.log.error('unable to create dir [' + path + '] for ' + title + ' directory.');
    }
}

exports.getProjectPath = function(){
    if(PROJECT_ROOT){
        return getPath(PROJECT_ROOT, arguments);
    } else {
        fis.log.error('undefined project root');
    }
};

exports.setProjectRoot = function(path){
    if(fis.util.isDir(path)){
        PROJECT_ROOT = fis.util.realpath(path);
    } else {
        fis.log.error('invalid project root path [' + path + ']');
    }
};

exports.setTempRoot = function(tmp){
    TEMP_ROOT = initDir(tmp);
};

exports.getTempPath = function(){
    if(!TEMP_ROOT){
        this.setTempRoot(__dirname + '/../.tmp');
    }
    return getPath(TEMP_ROOT, arguments);
};

exports.setLibRoot = function(path){
    LIB_ROOT = initDir(path);
};

exports.getLibPath = function(){
    if(!LIB_ROOT){
        exports.setLibRoot(exports.getTempPath('fis_modules'));
    }
    return getPath(LIB_ROOT, arguments);
};

exports.getCachePath = function(){
    return getPath(TEMP_ROOT + '/cache', arguments);
};<commit_msg>升级一下project库<commit_after>/*
 * fis
 * http://web.baidu.com/
 */

'use strict';

var fis = require('../fis-kernel.js');
var tar, remote;

exports.DEFAULT_REMOTE_REPOS = 'http://web.baidu.com/fis_repos';
exports.DEFAULT_CONF_FILE = 'fis-conf.json';

var FIS_CONF;

Object.defineProperty(exports, 'conf', {
    enumerable : true,
    set : function(value){
        if(fis.util.isFile(value)){
            FIS_CONF = fis.util.realpath(value);
            fis.config.merge(fis.util.readJSON(FIS_CONF));
        } else {
            fis.log.error('invalid fis-conf file [' + value + ']');
        }
    },
    get : function(){
        if(!FIS_CONF){
            fis.log.error('undefined fis-conf file');
        }
        return FIS_CONF;
    }
});

exports.init = function(root, conf, callback){

    if(fis.util.isDir(root)){
        //set root
        exports.setProjectRoot(root);

        if(typeof conf === 'function'){
            callback = conf;
            conf = fis.config.get('system.conf', exports.DEFAULT_CONF_FILE);
        }

        exports.conf = conf;

        //undownloaded dependencies counter
        var count = 0;

        //download remote
        remote = fis.config.get('system.repos', exports.DEFAULT_REMOTE_REPOS).replace(/\/$/, '');

        //traverse dependencies
        fis.util.map(fis.config.get('deps', {}), function(name, version){
            var dir = exports.getLibPath(name, version, name);
            var flag = dir + '/.inited';
            if(!fis.util.exists(flag)){
                count++;
                var url = remote + '/' + name + '/' + version + '.tar';
                process.stdout.write('download module [' + name + '@' + version + '] ... ');
                fis.util.download(url, function(err, file){
                    if(err){
                        process.stdout.write('fail\n');
                        fis.log.error( 'unable to download modules [' +
                            name + '@' + version + '] from [' + url + '], error [' + err + ']');
                    } else {
                        tar = tar || require('tar');
                        fis.util.fs
                            .createReadStream(file)
                            .pipe(tar.Extract({
                                path : dir
                            }))
                            .on('error', function(err){
                                process.stdout.write('fail\n');
                                fis.log.error(err);
                            })
                            .on('end', function(){
                                process.stdout.write('ok\n');
                                var str = [
                                    'name : ' + name + '@' + version,
                                    'from : ' + url,
                                    'time : ' + (new Date)
                                ].join('\n');
                                fis.util.write(flag, str);
                                if(--count == 0){
                                    callback();
                                }
                            });
                    }
                });
            }
        });
        if(count == 0){
            callback();
        }
    } else {
        fis.log.error('invalid project root dir [' + root + ']');
    }
};

exports.getSource = function(opt){
    var root = exports.getProjectPath(),
        dirs = [root];
    fis.util.map(fis.config.get('deps', {}), function(name, version){
        var dir = exports.getLibPath(name, version, name);
        if(fis.util.isDir(dir)){
            dir = fis.util.realpath(dir);
            dirs.push(dir);
        } else {
            fis.log.error('unable to find dependency [' + name + '@' + version + ']');
        }
    });
    var source = {};
    opt = opt || {};
    dirs.forEach(function(path){
        fis.util.find(path, opt.include, opt.exclude).forEach(function(file){
            if(file !== exports.conf){
                file = fis.file(file);
                source[file.subpath] = file;
            }
        });
    });
    return source;
};

//paths
var PROJECT_ROOT;
var LIB_ROOT;
var TEMP_ROOT;

function getPath(root, args){
    if(args && args.length > 0){
        args = root + '/' + Array.prototype.join.call(args, '/');
        return fis.util(args);
    } else {
        return root;
    }
}

function initDir(path, title){
    if(fis.util.exists(path)){
        if(!fis.util.isDir(path)){
            fis.log.error('unable to set path[' + path + '] as ' + title + ' directory.');
        }
    } else {
        fis.util.mkdir(path);
    }
    path = fis.util.realpath(path);
    if(path){
        return path;
    } else {
        fis.log.error('unable to create dir [' + path + '] for ' + title + ' directory.');
    }
}

exports.getProjectPath = function(){
    if(PROJECT_ROOT){
        return getPath(PROJECT_ROOT, arguments);
    } else {
        fis.log.error('undefined project root');
    }
};

exports.setProjectRoot = function(path){
    if(fis.util.isDir(path)){
        PROJECT_ROOT = fis.util.realpath(path);
    } else {
        fis.log.error('invalid project root path [' + path + ']');
    }
};

exports.setTempRoot = function(tmp){
    TEMP_ROOT = initDir(tmp);
};

exports.getTempPath = function(){
    if(!TEMP_ROOT){
        this.setTempRoot(__dirname + '/../.tmp');
    }
    return getPath(TEMP_ROOT, arguments);
};

exports.setLibRoot = function(path){
    LIB_ROOT = initDir(path);
};

exports.getLibPath = function(){
    if(!LIB_ROOT){
        exports.setLibRoot(exports.getTempPath('fis_modules'));
    }
    return getPath(LIB_ROOT, arguments);
};

exports.getCachePath = function(){
    return getPath(TEMP_ROOT + '/cache', arguments);
};<|endoftext|>


<commit_before>function FeedTouch() {

  // sanitise feed URL, ensure it has protocol and host, and escape special characters
  function _sanitise(page, feed) {

    var sanitised = feed;
    if (!sanitised.match(/^https?:\/\//)) {

      if (!feed.match(/^\//)) {
        sanitised = '/' + sanitised;
      }

      if (!page.match(/^https?:\/\//)) {
        page = 'http://' + page;
      }

      sanitised = page + sanitised;
    }

    return sanitised.replace(/#/, '%23').replace(/\?/, '%3F');
  }

  function home(url, max) {

    $('li#indicator').text('Loading feed...');
    $('li#indicator').show();

    var service = 'http://ajax.googleapis.com/ajax/services/feed/load?v=1.0&callback=?&num=' + max + '&q=' + encodeURIComponent(url);
    $.getJSON(service, function (data) {

      // it is a feed, display feed entries
      if (data.responseStatus === 200) {

        var feed = data.responseData.feed;
        document.title = feed.title + ' - FeedTouch';

        $('li#indicator').hide();
        $('h1#title').text(feed.title);

        feed.entries.forEach(function (entry) {
          $('li#indicator').before('
' + entry.title + '
');
        });
        $('ul#items').listview('refresh');

      // not a feed, attempt to discover feeds
      } else {

        $('li#indicator').text('Discovering feed...');
        $('li#indicator').show();

        $.getJSON('/s/' + url, function (data) {

          // feed discovered
          if (data && data.length > 0) {

            $('li#indicator').text('Discovered ' + data.length + ' feed' + ((data.length > 1) ? 's' : ''));

            if (data.length === 1) {
              window.location = '/' + _sanitise(url, data[0].url);
            } else {

              var title = url.replace(/https?:\/\//, '') + ' feeds';
              document.title = title + ' - FeedTouch';

              $('li#indicator').hide();
              $('h1#title').text(title);

              data.forEach(function (feed) {
                if (feed.title) {
                  $('li#indicator').before('
' + feed.title + '
');
                }
              });
              $('ul#items').listview('refresh');
            }

          // no feed
          } else {

            $('li#indicator').text(url.replace(/https?:\/\//, '') + ' does not have any feed');
            $('li#indicator').show();
            $('li#indicator').after('
View the page anyway?');
            $('ul#items').listview('refresh');
          }
        });
      }
    });
  }

  // load article, title will be displayed on the page prior to content retrieval
  function article(url, title) {
    $('div#content').html('Loading article...');

    var service = 'http://viewtext.org/api/text?url=' + encodeURIComponent(url) + '&callback=?';
    $.getJSON(service, function (data) {

      var heading = '
' + title + '
' + url + '
';
      if (data.content) {

        document.title = title + ' - FeedTouch';
        data.content = data.content.replace(/^\s*/, '').replace(/\s*$/, '');
        $('div#content').html(heading + data.content);

      } else {

        document.title = 'Error - FeedTouch';
        $('div#content').html(heading + 'Error unable to load article - ' + JSON.stringify(data));

      }
    });
  }

  return {
    home: home,
    article: article
  };
}<commit_msg>Fix URI component encoding.<commit_after>function FeedTouch() {

  // encode URI component with special characters handling
  function _encode(component) {
    return encodeURIComponent(component)
      .replace(/#/, '%23')
      .replace(/\?/, '%3F')
      .replace(/'/, '%E2%80%99');
  }

  // sanitise feed URL, ensure it has protocol and host
  function _sanitise(page, feed) {

    var sanitised = feed;
    if (!sanitised.match(/^https?:\/\//)) {

      if (!feed.match(/^\//)) {
        sanitised = '/' + sanitised;
      }

      if (!page.match(/^https?:\/\//)) {
        page = 'http://' + page;
      }

      sanitised = page + sanitised;
    }

    return _encode(sanitised);
  }

  function home(url, max) {

    $('li#indicator').text('Loading feed...');
    $('li#indicator').show();

    var service = 'http://ajax.googleapis.com/ajax/services/feed/load?v=1.0&callback=?&num=' + max + '&q=' + encodeURIComponent(url);
    $.getJSON(service, function (data) {

      // it is a feed, display feed entries
      if (data.responseStatus === 200) {

        var feed = data.responseData.feed;
        document.title = feed.title + ' - FeedTouch';

        $('li#indicator').hide();
        $('h1#title').text(feed.title);

        feed.entries.forEach(function (entry) {
          $('li#indicator').before('
' + entry.title + '
');
        });
        $('ul#items').listview('refresh');

      // not a feed, attempt to discover feeds
      } else {

        $('li#indicator').text('Discovering feed...');
        $('li#indicator').show();

        $.getJSON('/s/' + url, function (data) {

          // feed discovered
          if (data && data.length > 0) {

            $('li#indicator').text('Discovered ' + data.length + ' feed' + ((data.length > 1) ? 's' : ''));

            if (data.length === 1) {
              window.location = '/' + _sanitise(url, data[0].url);
            } else {

              var title = url.replace(/https?:\/\//, '') + ' feeds';
              document.title = title + ' - FeedTouch';

              $('li#indicator').hide();
              $('h1#title').text(title);

              data.forEach(function (feed) {
                if (feed.title) {
                  $('li#indicator').before('
' + feed.title + '
');
                }
              });
              $('ul#items').listview('refresh');
            }

          // no feed
          } else {

            $('li#indicator').text(url.replace(/https?:\/\//, '') + ' does not have any feed');
            $('li#indicator').show();
            $('li#indicator').after('
View the page anyway?');
            $('ul#items').listview('refresh');
          }
        });
      }
    });
  }

  // load article, title will be displayed on the page prior to content retrieval
  function article(url, title) {
    $('div#content').html('Loading article...');

    var service = 'http://viewtext.org/api/text?url=' + encodeURIComponent(url) + '&callback=?';
    $.getJSON(service, function (data) {

      var heading = '
' + title + '
' + url + '
';
      if (data.content) {

        document.title = title + ' - FeedTouch';
        data.content = data.content.replace(/^\s*/, '').replace(/\s*$/, '');
        $('div#content').html(heading + data.content);

      } else {

        document.title = 'Error - FeedTouch';
        $('div#content').html(heading + 'Error unable to load article - ' + JSON.stringify(data));

      }
    });
  }

  return {
    home: home,
    article: article
  };
}<|endoftext|>


<commit_before>import $ from 'jquery'
import _ from 'underscore'
import View from './base'
import renderTemplate from '../utils/render-template'
import dataStore from '../data-store'
import navigationBar from '../navigation-bar'
import statusBar from '../status-bar'
import metadataStore from '../utils/metadata-store'
import MarkdownRenderer from '../utils/markdown'
import moment from 'moment'
import postProvider from '../providers/post'
import contestProvider from '../providers/contest'
import identityProvider from '../providers/identity'
import 'jquery.form'
import 'parsley'

class NewsView extends View {
  constructor () {
    super(/^\/news$/)
    this.$main = null

    this.onCreatePost = null
    this.onUpdatePost = null
    this.onDeletePost = null
  }

  getTitle () {
    return `${metadataStore.getMetadata('event-title')} :: News`
  }

  renderPosts () {
    let posts = postProvider.getPosts()
    let $section = this.$main.find('section')

    if (posts.length === 0) {
      $section.empty()
      $section.html($('

').addClass('lead').text('No posts have been published yet.'))
    } else {
      $section.empty()
      let md = new MarkdownRenderer()
      let sortedPosts = _.sortBy(posts, 'createdAt').reverse()
      let manageable = identityProvider.getIdentity().isSupervisor()
      for (let post of sortedPosts) {
        let options = {
          id: post.id,
          title: post.title,
          description: md.render(post.description),
          updatedAt: moment(post.updatedAt).format('lll'),
          manageable: manageable
        }

        $section.append($(renderTemplate('post-partial', options)))
      }
    }
  }

  initDeletePostModal () {
    let $modal = $('#delete-post-modal')
    $modal.modal({ show: false })

    let $modalBody = $modal.find('.modal-body p.confirmation')
    let $submitError = $modal.find('.submit-error > p')
    let $submitButton = $modal.find('button[data-action="complete-delete-post"]')

    $modal.on('show.bs.modal', (e) => {
      let postId = parseInt($(e.relatedTarget).data('post-id'), 10)
      $modal.data('post-id', postId)
      let post = _.findWhere(postProvider.getPosts(), { id: postId })
      $modalBody.html(renderTemplate('delete-post-confirmation', { title: post.title }))
      $submitError.text('')
    })

    $submitButton.on('click', (e) => {
      let postId = $modal.data('post-id')
      $
        .when(postProvider.deletePost(postId, identityProvider.getIdentity().token))
        .done(() => {
          $modal.modal('hide')
          if (!dataStore.connectedRealtime()) {
            window.location.reload()
          }
        })
        .fail((err) => {
          $submitError.text(err)
        })
    })
  }

  initCreatePostModal () {
    let $createPostModal = $('#create-post-modal')
    $createPostModal.modal({ show: false })

    let $createPostSubmitError = $createPostModal.find('.submit-error > p')
    let $createPostSubmitButton = $createPostModal.find('button[data-action="complete-create-post"]')
    let $createPostForm = $createPostModal.find('form')
    $createPostForm.parsley()

    $createPostSubmitButton.on('click', (e) => {
      $createPostForm.trigger('submit')
    })

    let $createPostTablist = $('#create-post-tablist')
    let $createPostTabData = $createPostTablist.find('a[href="#create-post-data"]')
    let $createPostTabPreview = $createPostTablist.find('a[href="#create-post-preview"]')

    let $createPostTitle = $('#create-post-title')
    let $createPostDescription = $('#create-post-description')

    let $createPostPreview = $('#create-post-preview')

    $createPostTabData.tab()
    $createPostTabPreview.tab()

    $createPostTabPreview.on('show.bs.tab', (e) => {
      let md = new MarkdownRenderer()
      let options = {
        title: $createPostTitle.val(),
        description: md.render($createPostDescription.val()),
        updatedAt: moment(new Date()).format('lll')
      }

      $createPostPreview.html(renderTemplate('post-simplified-partial', options))
    })

    $createPostModal.on('show.bs.modal', (e) => {
      $createPostTabData.tab('show')
      $createPostTitle.val('')
      $createPostDescription.val('')
      $createPostSubmitError.text('')
      $createPostForm.parsley().reset()
    })

    $createPostModal.on('shown.bs.modal', (e) => {
      $createPostTitle.focus()
    })

    $createPostForm.on('submit', (e) => {
      e.preventDefault()
      $createPostForm.ajaxSubmit({
        beforeSubmit: () => {
          $createPostSubmitError.text('')
          $createPostSubmitButton.prop('disabled', true)
        },
        clearForm: true,
        dataType: 'json',
        headers: {
          'X-CSRF-Token': identityProvider.getIdentity().token
        },
        success: (responseText, textStatus, jqXHR) => {
          $createPostModal.modal('hide')
          if (!dataStore.connectedRealtime()) {
            window.location.reload()
          }
        },
        error: (jqXHR, textStatus, errorThrown) => {
          if (jqXHR.responseJSON) {
            $createPostSubmitError.text(jqXHR.responseJSON)
          } else {
            $createPostSubmitError.text('Unknown error. Please try again later.')
          }
        },
        complete: () => {
          $createPostSubmitButton.prop('disabled', false)
        }
      })
    })
  }

  initEditPostModal () {
    let $editPostModal = $('#edit-post-modal')
    $editPostModal.modal({ show: false })

    let $editPostSubmitError = $editPostModal.find('.submit-error > p')
    let $editPostSubmitButton = $editPostModal.find('button[data-action="complete-edit-post"]')
    let $editPostForm = $editPostModal.find('form')
    $editPostForm.parsley()

    $editPostSubmitButton.on('click', (e) => {
      $editPostForm.trigger('submit')
    })

    let $editPostTablist = $('#edit-post-tablist')
    let $editPostTabData = $editPostTablist.find('a[href="#edit-post-data"]')
    let $editPostTabPreview = $editPostTablist.find('a[href="#edit-post-preview"]')

    let $editPostTitle = $('#edit-post-title')
    let $editPostDescription = $('#edit-post-description')

    let $editPostPreview = $('#edit-post-preview')

    $editPostTabData.tab()
    $editPostTabPreview.tab()

    $editPostTabPreview.on('show.bs.tab', (e) => {
      let md = new MarkdownRenderer()
      let options = {
        title: $editPostTitle.val(),
        description: md.render($editPostDescription.val()),
        updatedAt: moment(new Date()).format('lll')
      }

      $editPostPreview.html(renderTemplate('post-simplified-partial', options))
    })

    $editPostModal.on('show.bs.modal', (e) => {
      $editPostTabData.tab('show')
      let postId = parseInt($(e.relatedTarget).data('post-id'), 10)
      let post = _.findWhere(postProvider.getPosts(), { id: postId })

      $editPostForm.attr('action', `/api/post/${postId}/update`)
      $editPostTitle.val(post.title)
      $editPostDescription.val(post.description)
      $editPostSubmitError.text('')
      $editPostForm.parsley().reset()
    })

    $editPostModal.on('shown.bs.modal', (e) => {
      $editPostTitle.focus()
    })

    $editPostForm.on('submit', (e) => {
      e.preventDefault()
      $editPostForm.ajaxSubmit({
        beforeSubmit: () => {
          $editPostSubmitError.text('')
          $editPostSubmitButton.prop('disabled', true)
        },
        clearForm: true,
        dataType: 'json',
        headers: {
          'X-CSRF-Token': identityProvider.getIdentity().token
        },
        success: (responseText, textStatus, jqXHR) => {
          $editPostModal.modal('hide')
          if (!dataStore.connectedRealtime()) {
            window.location.reload()
          }
        },
        error: (jqXHR, textStatus, errorThrown) => {
          if (jqXHR.responseJSON) {
            $editPostSubmitError.text(jqXHR.responseJSON)
          } else {
            $editPostSubmitError.text('Unknown error. Please try again later.')
          }
        },
        complete: () => {
          $editPostSubmitButton.prop('disabled', false)
        }
      })
    })
  }

  present () {
    this.$main = $('#main')
    this.$main.html(renderTemplate('loading-view'))

    $
      .when(identityProvider.fetchIdentity())
      .done((identity) => {
        let promise = null
        if (identity.isTeam()) {
          promise = $.when(contestProvider.fetchContest(), postProvider.fetchPosts(), contestProvider.fetchTeamScores())
        } else {
          promise = $.when(contestProvider.fetchContest(), postProvider.fetchPosts())
        }

        promise
          .done((contest) => {
            identityProvider.subscribe()
            if (dataStore.supportsRealtime()) {
              dataStore.connectRealtime()
            }

            navigationBar.present({ active: 'news' })
            statusBar.present()

            this.$main.html(renderTemplate('news-view', { identity: identity }))

            if (identity.isSupervisor()) {
              this.initCreatePostModal()
              this.initDeletePostModal()
              this.initEditPostModal()
            }

            this.renderPosts()

            this.onCreatePost = (post) => {
              this.renderPosts()
              return false
            }

            this.onUpdatePost = (post) => {
              this.renderPosts()
              return false
            }

            this.onDeletePost = (postId) => {
              this.renderPosts()
              return false
            }

            postProvider.subscribe()
            postProvider.on('createPost', this.onCreatePost)
            postProvider.on('updatePost', this.onUpdatePost)
            postProvider.on('deletePost', this.onDeletePost)
          })
          .fail((err) => {
            console.error(err)
            navigationBar.present()
            this.$main.html(renderTemplate('internal-error-view'))
          })
      })
      .fail((err) => {
        console.error(err)
        navigationBar.present()
        this.$main.html(renderTemplate('internal-error-view'))
      })
  }

  dismiss () {
    identityProvider.unsubscribe()

    if (this.onCreatePost) {
      postProvider.off('createPost', this.onCreatePost)
      this.onCreatePost = null
    }

    if (this.onDeletePost) {
      postProvider.off('deletePost', this.onDeletePost)
      this.onDeletePost = null
    }

    if (this.onUpdatePost) {
      postProvider.off('updatePost', this.onUpdatePost)
      this.onUpdatePost = null
    }

    postProvider.unsubscribe()

    this.$main.empty()
    this.$main = null
    navigationBar.dismiss()
    statusBar.dismiss()

    if (dataStore.supportsRealtime()) {
      dataStore.disconnectRealtime()
    }
  }
}

export default new NewsView()
<commit_msg>Refactor news view<commit_after>import $ from 'jquery'
import _ from 'underscore'
import View from './base'
import renderTemplate from '../utils/render-template'
import dataStore from '../data-store'
import navigationBar from '../navigation-bar'
import statusBar from '../status-bar'
import metadataStore from '../utils/metadata-store'
import MarkdownRenderer from '../utils/markdown'
import moment from 'moment'
import postProvider from '../providers/post'
import contestProvider from '../providers/contest'
import identityProvider from '../providers/identity'
import 'jquery.form'
import 'parsley'

class NewsView extends View {
  constructor () {
    super(/^\/news$/)
    this.$main = null

    this.onCreatePost = null
    this.onUpdatePost = null
    this.onDeletePost = null
  }

  getTitle () {
    return `${metadataStore.getMetadata('event-title')} :: News`
  }

  renderPosts () {
    let posts = postProvider.getPosts()
    let $section = this.$main.find('section')

    if (posts.length === 0) {
      $section.empty()
      $section.html($('

').addClass('lead').text('No posts have been published yet.'))
    } else {
      $section.empty()
      let md = new MarkdownRenderer()
      let sortedPosts = _.sortBy(posts, 'createdAt').reverse()
      let manageable = identityProvider.getIdentity().isSupervisor()
      for (let post of sortedPosts) {
        let options = {
          id: post.id,
          title: post.title,
          description: md.render(post.description),
          updatedAt: moment(post.updatedAt).format('lll'),
          manageable: manageable
        }

        $section.append($(renderTemplate('post-partial', options)))
      }
    }
  }

  initDeletePostModal () {
    let $modal = $('#delete-post-modal')
    $modal.modal({ show: false })

    let $modalBody = $modal.find('.modal-body p.confirmation')
    let $submitError = $modal.find('.submit-error > p')
    let $submitButton = $modal.find('button[data-action="complete-delete-post"]')

    $modal.on('show.bs.modal', (e) => {
      let postId = parseInt($(e.relatedTarget).data('post-id'), 10)
      $modal.data('post-id', postId)
      let post = _.findWhere(postProvider.getPosts(), { id: postId })
      $modalBody.html(renderTemplate('delete-post-confirmation', { title: post.title }))
      $submitError.text('')
    })

    $submitButton.on('click', (e) => {
      let postId = $modal.data('post-id')
      $
        .when(postProvider.deletePost(postId, identityProvider.getIdentity().token))
        .done(() => {
          $modal.modal('hide')
          if (!dataStore.connectedRealtime()) {
            window.location.reload()
          }
        })
        .fail((err) => {
          $submitError.text(err)
        })
    })
  }

  initCreatePostModal () {
    let $modal = $('#create-post-modal')
    $modal.modal({ show: false })

    let $submitError = $modal.find('.submit-error > p')
    let $submitButton = $modal.find('button[data-action="complete-create-post"]')
    let $form = $modal.find('form')
    $form.parsley()

    $submitButton.on('click', (e) => {
      $form.trigger('submit')
    })

    let $tabList = $('#create-post-tablist')
    let $tabData = $tabList.find('a[href="#create-post-data"]')
    let $tabPreview = $tabList.find('a[href="#create-post-preview"]')

    let $postTitle = $('#create-post-title')
    let $postDescription = $('#create-post-description')

    let $postPreview = $('#create-post-preview')

    $tabData.tab()
    $tabPreview.tab()

    $tabPreview.on('show.bs.tab', (e) => {
      let md = new MarkdownRenderer()
      let options = {
        title: $postTitle.val(),
        description: md.render($postDescription.val()),
        updatedAt: moment(new Date()).format('lll')
      }

      $postPreview.html(renderTemplate('post-simplified-partial', options))
    })

    $modal.on('show.bs.modal', (e) => {
      $tabData.tab('show')
      $postTitle.val('')
      $postDescription.val('')
      $submitError.text('')
      $form.parsley().reset()
    })

    $modal.on('shown.bs.modal', (e) => {
      $postTitle.focus()
    })

    $form.on('submit', (e) => {
      e.preventDefault()
      $form.ajaxSubmit({
        beforeSubmit: () => {
          $submitError.text('')
          $submitButton.prop('disabled', true)
        },
        clearForm: true,
        dataType: 'json',
        headers: {
          'X-CSRF-Token': identityProvider.getIdentity().token
        },
        success: (responseText, textStatus, jqXHR) => {
          $modal.modal('hide')
          if (!dataStore.connectedRealtime()) {
            window.location.reload()
          }
        },
        error: (jqXHR, textStatus, errorThrown) => {
          if (jqXHR.responseJSON) {
            $submitError.text(jqXHR.responseJSON)
          } else {
            $submitError.text('Unknown error. Please try again later.')
          }
        },
        complete: () => {
          $submitButton.prop('disabled', false)
        }
      })
    })
  }

  initEditPostModal () {
    let $modal = $('#edit-post-modal')
    $modal.modal({ show: false })

    let $submitError = $modal.find('.submit-error > p')
    let $submitButton = $modal.find('button[data-action="complete-edit-post"]')
    let $form = $modal.find('form')
    $form.parsley()

    $submitButton.on('click', (e) => {
      $form.trigger('submit')
    })

    let $tabList = $('#edit-post-tablist')
    let $tabData = $tabList.find('a[href="#edit-post-data"]')
    let $tabPreview = $tabList.find('a[href="#edit-post-preview"]')

    let $postTitle = $('#edit-post-title')
    let $postDescription = $('#edit-post-description')

    let $postPreview = $('#edit-post-preview')

    $tabData.tab()
    $tabPreview.tab()

    $tabPreview.on('show.bs.tab', (e) => {
      let md = new MarkdownRenderer()
      let options = {
        title: $postTitle.val(),
        description: md.render($postDescription.val()),
        updatedAt: moment(new Date()).format('lll')
      }

      $postPreview.html(renderTemplate('post-simplified-partial', options))
    })

    $modal.on('show.bs.modal', (e) => {
      $tabData.tab('show')
      let postId = parseInt($(e.relatedTarget).data('post-id'), 10)
      let post = _.findWhere(postProvider.getPosts(), { id: postId })

      $form.attr('action', `/api/post/${postId}/update`)
      $postTitle.val(post.title)
      $postDescription.val(post.description)
      $submitError.text('')
      $form.parsley().reset()
    })

    $modal.on('shown.bs.modal', (e) => {
      $postTitle.focus()
    })

    $form.on('submit', (e) => {
      e.preventDefault()
      $form.ajaxSubmit({
        beforeSubmit: () => {
          $submitError.text('')
          $submitButton.prop('disabled', true)
        },
        clearForm: true,
        dataType: 'json',
        headers: {
          'X-CSRF-Token': identityProvider.getIdentity().token
        },
        success: (responseText, textStatus, jqXHR) => {
          $modal.modal('hide')
          if (!dataStore.connectedRealtime()) {
            window.location.reload()
          }
        },
        error: (jqXHR, textStatus, errorThrown) => {
          if (jqXHR.responseJSON) {
            $submitError.text(jqXHR.responseJSON)
          } else {
            $submitError.text('Unknown error. Please try again later.')
          }
        },
        complete: () => {
          $submitButton.prop('disabled', false)
        }
      })
    })
  }

  present () {
    this.$main = $('#main')
    this.$main.html(renderTemplate('loading-view'))

    $
      .when(identityProvider.fetchIdentity())
      .done((identity) => {
        let promise = null
        if (identity.isTeam()) {
          promise = $.when(contestProvider.fetchContest(), postProvider.fetchPosts(), contestProvider.fetchTeamScores())
        } else {
          promise = $.when(contestProvider.fetchContest(), postProvider.fetchPosts())
        }

        promise
          .done((contest) => {
            identityProvider.subscribe()
            if (dataStore.supportsRealtime()) {
              dataStore.connectRealtime()
            }

            navigationBar.present({ active: 'news' })
            statusBar.present()

            this.$main.html(renderTemplate('news-view', { identity: identity }))

            if (identity.isSupervisor()) {
              this.initCreatePostModal()
              this.initDeletePostModal()
              this.initEditPostModal()
            }

            this.renderPosts()

            this.onCreatePost = (post) => {
              this.renderPosts()
              return false
            }

            this.onUpdatePost = (post) => {
              this.renderPosts()
              return false
            }

            this.onDeletePost = (postId) => {
              this.renderPosts()
              return false
            }

            postProvider.subscribe()
            postProvider.on('createPost', this.onCreatePost)
            postProvider.on('updatePost', this.onUpdatePost)
            postProvider.on('deletePost', this.onDeletePost)
          })
          .fail((err) => {
            console.error(err)
            navigationBar.present()
            this.$main.html(renderTemplate('internal-error-view'))
          })
      })
      .fail((err) => {
        console.error(err)
        navigationBar.present()
        this.$main.html(renderTemplate('internal-error-view'))
      })
  }

  dismiss () {
    identityProvider.unsubscribe()

    if (this.onCreatePost) {
      postProvider.off('createPost', this.onCreatePost)
      this.onCreatePost = null
    }

    if (this.onDeletePost) {
      postProvider.off('deletePost', this.onDeletePost)
      this.onDeletePost = null
    }

    if (this.onUpdatePost) {
      postProvider.off('updatePost', this.onUpdatePost)
      this.onUpdatePost = null
    }

    postProvider.unsubscribe()

    this.$main.empty()
    this.$main = null
    navigationBar.dismiss()
    statusBar.dismiss()

    if (dataStore.supportsRealtime()) {
      dataStore.disconnectRealtime()
    }
  }
}

export default new NewsView()
<|endoftext|>


<commit_before>const BinarySocket        = require('./socket');
const BinarySocketGeneral = require('./socket_general');
const Header              = require('./header');
const localize            = require('../../_common/localize').localize;
const getElementById      = require('../../_common/common_functions').getElementById;

/*
 * Monitors the network status and initialises the WebSocket connection
 * 1. online : check the WS status (init/send: blink after timeout, open/message: online)
 * 2. offline: it is offline
 */
const NetworkMonitor = (() => {
    const status_config = {
        online  : { class: 'online',  tooltip: localize('Online') },
        offline : { class: 'offline', tooltip: localize('Offline') },
        blinking: { class: 'pulser',  tooltip: localize('Connecting') },
    };
    const pendings = {};
    const pending_keys = {
        ws_init   : 'ws_init',
        ws_request: 'ws_request',
    };
    const pending_timeouts = {
        [pending_keys.ws_init]   : 5000,
        [pending_keys.ws_request]: 10000,
    };

    let ws_config,
        el_status,
        network_status;

    const init = () => {
        ws_config = $.extend({ wsEvent }, BinarySocketGeneral.initOptions());
        el_status = getElementById('network_status');

        if ('onLine' in navigator) {
            window.addEventListener('online',  setStatus);
            window.addEventListener('offline', setStatus);
        } else { // if not supported, default to online and fallback to WS checks
            navigator.onLine = true;
        }

        if (isOnline()) {
            BinarySocket.init(ws_config);
        }

        setStatus(isOnline() ? 'online' : 'offline');
    };

    const isOnline = () => navigator.onLine;

    const wsReconnect = () => {
        if (isOnline() && BinarySocket.hasReadyState(2, 3)) { // CLOSING or CLOSED
            BinarySocket.init(ws_config);
            clearPendings();
        }
    };

    const setStatus = (status) => {
        if (!isOnline() || status === pending_keys.ws_init) {
            network_status = 'offline';
            Header.displayNotification(localize('Connection error: Please check your internet connection.'), true, 'CONNECTION_ERROR');
        } else if (status === pending_keys.ws_request || network_status === 'offline') {
            network_status = 'blinking';
            wsReconnect();
            Header.hideNotification('CONNECTION_ERROR');
        } else {
            network_status = 'online';
            Header.hideNotification('CONNECTION_ERROR');
        }
        el_status.setAttribute('class',        `no-underline ${status_config[network_status].class}`);
        el_status.setAttribute('data-balloon', `${localize('Network status')}: ${status_config[network_status].tooltip}`);
    };

    const ws_events_map = {
        init   : () => setPending(pending_keys.ws_init),
        open   : () => clearPendings(pending_keys.ws_init),
        send   : () => setPending(pending_keys.ws_request),
        message: () => clearPendings(),
        close  : () => wsReconnect(),
    };

    const wsEvent = (event) => {
        if (typeof ws_events_map[event] === 'function') {
            ws_events_map[event]();
        }
    };

    const setPending = (key) => {
        if (pendings[key]) {
            clearPendings(key);
        }
        pendings[key] = setTimeout(() => { setStatus(key); }, pending_timeouts[key]);
    };

    const clearPendings = (key) => {
        const clear = (k) => {
            clearTimeout(pendings[k]);
            pendings[k] = undefined;
        };

        if (key) {
            clear(key);
        } else {
            Object.keys(pendings).forEach(clear);
        }
        setStatus('online');
    };

    return {
        init,
        wsEvent,
    };
})();

module.exports = NetworkMonitor;
<commit_msg>Re-arrange imports<commit_after>const Header              = require('./header');
const BinarySocket        = require('./socket');
const BinarySocketGeneral = require('./socket_general');
const getElementById      = require('../../_common/common_functions').getElementById;
const localize            = require('../../_common/localize').localize;

/*
 * Monitors the network status and initialises the WebSocket connection
 * 1. online : check the WS status (init/send: blink after timeout, open/message: online)
 * 2. offline: it is offline
 */
const NetworkMonitor = (() => {
    const status_config = {
        online  : { class: 'online',  tooltip: localize('Online') },
        offline : { class: 'offline', tooltip: localize('Offline') },
        blinking: { class: 'pulser',  tooltip: localize('Connecting') },
    };
    const pendings = {};
    const pending_keys = {
        ws_init   : 'ws_init',
        ws_request: 'ws_request',
    };
    const pending_timeouts = {
        [pending_keys.ws_init]   : 5000,
        [pending_keys.ws_request]: 10000,
    };

    let ws_config,
        el_status,
        network_status;

    const init = () => {
        ws_config = $.extend({ wsEvent }, BinarySocketGeneral.initOptions());
        el_status = getElementById('network_status');

        if ('onLine' in navigator) {
            window.addEventListener('online',  setStatus);
            window.addEventListener('offline', setStatus);
        } else { // if not supported, default to online and fallback to WS checks
            navigator.onLine = true;
        }

        if (isOnline()) {
            BinarySocket.init(ws_config);
        }

        setStatus(isOnline() ? 'online' : 'offline');
    };

    const isOnline = () => navigator.onLine;

    const wsReconnect = () => {
        if (isOnline() && BinarySocket.hasReadyState(2, 3)) { // CLOSING or CLOSED
            BinarySocket.init(ws_config);
            clearPendings();
        }
    };

    const setStatus = (status) => {
        if (!isOnline() || status === pending_keys.ws_init) {
            network_status = 'offline';
            Header.displayNotification(localize('Connection error: Please check your internet connection.'), true, 'CONNECTION_ERROR');
        } else if (status === pending_keys.ws_request || network_status === 'offline') {
            network_status = 'blinking';
            wsReconnect();
            Header.hideNotification('CONNECTION_ERROR');
        } else {
            network_status = 'online';
            Header.hideNotification('CONNECTION_ERROR');
        }
        el_status.setAttribute('class',        `no-underline ${status_config[network_status].class}`);
        el_status.setAttribute('data-balloon', `${localize('Network status')}: ${status_config[network_status].tooltip}`);
    };

    const ws_events_map = {
        init   : () => setPending(pending_keys.ws_init),
        open   : () => clearPendings(pending_keys.ws_init),
        send   : () => setPending(pending_keys.ws_request),
        message: () => clearPendings(),
        close  : () => wsReconnect(),
    };

    const wsEvent = (event) => {
        if (typeof ws_events_map[event] === 'function') {
            ws_events_map[event]();
        }
    };

    const setPending = (key) => {
        if (pendings[key]) {
            clearPendings(key);
        }
        pendings[key] = setTimeout(() => { setStatus(key); }, pending_timeouts[key]);
    };

    const clearPendings = (key) => {
        const clear = (k) => {
            clearTimeout(pendings[k]);
            pendings[k] = undefined;
        };

        if (key) {
            clear(key);
        } else {
            Object.keys(pendings).forEach(clear);
        }
        setStatus('online');
    };

    return {
        init,
        wsEvent,
    };
})();

module.exports = NetworkMonitor;
<|endoftext|>


<commit_before>/*jshint esnext: true */
var _ = require('underscore'),
  async = require('async'),
  bag = require('bagofholding'),
  nano = require('nano'),
  util = require('util');

/**
 * class Db
 *
 * CouchDB errors are camouflaged as results to allow multiple operations to resume
 * even though there is an error encountered on at least one of the operations
 * (e.g. database delete should not cause an error regardless whether the database already exists or not).
 * Non-CouchDB errors (e.g. connection error) will still be passed via standard callback error.
 *
 * @param {String} url: CouchDB instance URL in format http(s)://user:pass@host:port
 * @param {Object} opts: optional
 * - nano: for test purpose only, temporary until I find a way to mock required function module using Sinon.js, resort to dependency injection for now
 */
function Db(url, opts) {
  var couchOpts = { url: url },
    envProxy = bag.http.proxy(url);

  if (envProxy) {
    couchOpts.request_defaults = { proxy: envProxy };
  }

  this.couch = (opts && opts.nano ? opts.nano : nano)(couchOpts);
}

/**
 * Create inexisting databases.
 *
 * @param {Array} dbNames: name of databases
 * @param {Function} cb: standard cb(err, result) callback
 */
Db.prototype.createDatabases = function (dbNames, cb) {
  var self = this,
    tasks = [];
  dbNames.forEach(function (dbName) {
    function task(cb) {
      self.couch.db.create(dbName, self._handle(cb, {
        dbName: dbName,
        message: 'created'
      }));
    }
    tasks.push(task);
  });
  async.parallel(tasks, cb);
};

/**
 * Delete existing databases.
 *
 * @param {Array} dbNames: name of databases
 * @param {Function} cb: standard cb(err, result) callback
 */
Db.prototype.removeDatabases = function (dbNames, cb) {
  var self = this,
    tasks = [];
  dbNames.forEach(function (dbName) {
    function task(cb) {
      self.couch.db.destroy(dbName, self._handle(cb, {
        dbName: dbName,
        message: 'deleted'
      }));
    }
    tasks.push(task);
  });
  async.parallel(tasks, cb);
};

/**
 * Delete unknown databases (those not configured in setup file).
 * NOTE: in CouchDB v1.2.0, _users database deletion does not trigger any error, but the _users database can't be deleted
 *
 * @param {Array} dbNames: name of databases
 * @param {Function} cb: standard cb(err, result) callback
 */
Db.prototype.cleanDatabases = function (dbNames, cb) {
  var self = this;
  this.couch.db.list(function (err, result) {
    if (err) {
      cb(err);
    } else {
      var dbNamesToClean = [];
      result.forEach(function (dbName) {
        if (dbNames.indexOf(dbName) === -1) {
          dbNamesToClean.push(dbName);
        }
      });
      self.removeDatabases(dbNamesToClean, cb);
    }
  });
};

/**
 * Create inexisting documents, leave existing documents untouched as conflict error.
 *
 * @param {Object} dbSetup: database setup, keys are database names, values are documents
 * @param {Function} cb: standard cb(err, result) callback
 */
Db.prototype.createDocuments = function (dbSetup, cb) {
  var self = this,
    tasks = [];
  _.keys(dbSetup).forEach(function (dbName) {
    dbSetup[dbName].forEach(function (doc) {
      function task(cb) {
        self.couch.use(dbName).insert(doc, self._handle(cb, {
          dbName: dbName,
          docId: doc._id,
          message: 'created'
        }));
      }
      tasks.push(task);
    });
  });
  async.parallel(tasks, cb);
};

/**
 * Create inexisting documents, update existing documents.
 *
 * @param {Object} dbSetup: database setup, keys are database names, values are documents
 * @param {Function} cb: standard cb(err, result) callback
 */
Db.prototype.saveDocuments = function (dbSetup, cb) {
  const CONFLICT = 409;
  var self = this,
    tasks = [];
  _.keys(dbSetup).forEach(function (dbName) {
    dbSetup[dbName].forEach(function (doc) {
      function task(cb) {

        // try to create documents, but with conflict error handling
        function _errorCb(err, result) {

          // if there is a conflict error, retrieve the document's revision first
          function _successCb(err, result) {

            // use the revision to update the existing documents
            // NOTE: it is not impossible that the revision of this document
            // is modified by other operations right before updating, Couchpenter
            // will only try once to avoid any possibility of retrying infinitely.
            doc._rev = result._rev;
            self.couch.use(dbName).insert(doc, self._handle(cb, {
              dbName: dbName,
              docId: doc._id,
              message: 'updated'
            }));
          }

          self.couch.use(dbName).get(doc._id, self._handle(cb, {
            dbName: dbName,
            docId: doc._id,
            successCb: _successCb
          }));
        }

        self.couch.use(dbName).insert(doc, self._handle(cb, {
          dbName: dbName,
          docId: doc._id,
          message: 'created',
          errorCb: _errorCb,
          errorCode: CONFLICT
        }));
      }
      tasks.push(task);
    });
  });
  async.parallel(tasks, cb);
};

/**
 * Delete existing documents.
 *
 * @param {Object} dbSetup: database setup, keys are database names, values are documents
 * @param {Function} cb: standard cb(err, result) callback
 */
Db.prototype.removeDocuments = function (dbSetup, cb) {
  var self = this,
    tasks = [];
  _.keys(dbSetup).forEach(function (dbName) {
    dbSetup[dbName].forEach(function (doc) {
      function task(cb) {

        // retrieve the document revision and use it to delete the document
        function _successCb(err, result) {

          self.couch.use(dbName).destroy(result._id, result._rev, self._handle(cb, {
            dbName: dbName,
            docId: doc._id,
            message: 'deleted'
          }));
        }

        self.couch.use(dbName).get(doc._id, self._handle(cb, {
          dbName: dbName,
          docId: doc._id,
          successCb: _successCb
        }));
      }
      tasks.push(task);
    });
  });
  async.parallel(tasks, cb);
};

/**
 * Warm views.
 *
 * @param {Object} dbSetup: database setup, keys are database names, values are documents
 * @param {Function} cb: standard cb(err, result) callback
 */
Db.prototype.warmViews = function (dbSetup, cb) {
  var self = this,
  tasks = [];
  _.keys(dbSetup).forEach(function (dbName) {
    dbSetup[dbName].forEach(function (doc) {
      if (doc._id && doc._id.indexOf('_design/') === 0 && doc.views) {
        function task(cb) {
          cb = self._handle(cb, { dbName: dbName, docId: doc._id,
                                  message: 'warmed ' + _.keys(doc.views).length + ' views' });
          async.each(_.keys(doc.views), function (viewName, cb) {
            self.couch.use(dbName).view(doc._id.replace('_design/', ''), viewName, { limit: 1 }, cb);
          }, cb);
        }
        tasks.push(task);
      }
    });
  });
  async.parallel(tasks, cb);
};

Db.prototype._handle = function (cb, opts) {
  var self = this;

  // construct result object, the existence of error field indicates whether
  // the result is a success or not
  function _result(err, dbName, docId, message) {
    var result = {
      id: util.format('%s%s%s', dbName, (docId) ? '/' : '', docId || ''),
      message: message || util.format('%s (%s)', err.error, err.status_code)
    };
    if (err) {
      result.error = err;
    }
    return result;
  }

  return function (err, result) {
    if (err) {
      // special status code, delegate to error callback
      if (opts.errorCb && err.status_code === opts.errorCode) {
        opts.errorCb(err, result);
      // CouchDB error is camouflaged as result (status_code field is set by nano)
      } else if (err.status_code) {
        cb(null, _result(err, opts.dbName, opts.docId));
      // standard error (e.g. connection refused) is passed as standard error
      } else {
        cb(err);
      }
    } else {
      // delegate to success callback
      if (opts.successCb) {
        opts.successCb(err, result);
      // standard success
      } else {
        cb(null, _result(null, opts.dbName, opts.docId, opts.message));
      }
    }
  };
};

module.exports = Db;
<commit_msg>Added mechanism for "warming" CouchDB views: Fixed some lint errors.<commit_after>/*jshint esnext: true */
var _ = require('underscore'),
  async = require('async'),
  bag = require('bagofholding'),
  nano = require('nano'),
  util = require('util');

/**
 * class Db
 *
 * CouchDB errors are camouflaged as results to allow multiple operations to resume
 * even though there is an error encountered on at least one of the operations
 * (e.g. database delete should not cause an error regardless whether the database already exists or not).
 * Non-CouchDB errors (e.g. connection error) will still be passed via standard callback error.
 *
 * @param {String} url: CouchDB instance URL in format http(s)://user:pass@host:port
 * @param {Object} opts: optional
 * - nano: for test purpose only, temporary until I find a way to mock required function module using Sinon.js, resort to dependency injection for now
 */
function Db(url, opts) {
  var couchOpts = { url: url },
    envProxy = bag.http.proxy(url);

  if (envProxy) {
    couchOpts.request_defaults = { proxy: envProxy };
  }

  this.couch = (opts && opts.nano ? opts.nano : nano)(couchOpts);
}

/**
 * Create inexisting databases.
 *
 * @param {Array} dbNames: name of databases
 * @param {Function} cb: standard cb(err, result) callback
 */
Db.prototype.createDatabases = function (dbNames, cb) {
  var self = this,
    tasks = [];
  dbNames.forEach(function (dbName) {
    function task(cb) {
      self.couch.db.create(dbName, self._handle(cb, {
        dbName: dbName,
        message: 'created'
      }));
    }
    tasks.push(task);
  });
  async.parallel(tasks, cb);
};

/**
 * Delete existing databases.
 *
 * @param {Array} dbNames: name of databases
 * @param {Function} cb: standard cb(err, result) callback
 */
Db.prototype.removeDatabases = function (dbNames, cb) {
  var self = this,
    tasks = [];
  dbNames.forEach(function (dbName) {
    function task(cb) {
      self.couch.db.destroy(dbName, self._handle(cb, {
        dbName: dbName,
        message: 'deleted'
      }));
    }
    tasks.push(task);
  });
  async.parallel(tasks, cb);
};

/**
 * Delete unknown databases (those not configured in setup file).
 * NOTE: in CouchDB v1.2.0, _users database deletion does not trigger any error, but the _users database can't be deleted
 *
 * @param {Array} dbNames: name of databases
 * @param {Function} cb: standard cb(err, result) callback
 */
Db.prototype.cleanDatabases = function (dbNames, cb) {
  var self = this;
  this.couch.db.list(function (err, result) {
    if (err) {
      cb(err);
    } else {
      var dbNamesToClean = [];
      result.forEach(function (dbName) {
        if (dbNames.indexOf(dbName) === -1) {
          dbNamesToClean.push(dbName);
        }
      });
      self.removeDatabases(dbNamesToClean, cb);
    }
  });
};

/**
 * Create inexisting documents, leave existing documents untouched as conflict error.
 *
 * @param {Object} dbSetup: database setup, keys are database names, values are documents
 * @param {Function} cb: standard cb(err, result) callback
 */
Db.prototype.createDocuments = function (dbSetup, cb) {
  var self = this,
    tasks = [];
  _.keys(dbSetup).forEach(function (dbName) {
    dbSetup[dbName].forEach(function (doc) {
      function task(cb) {
        self.couch.use(dbName).insert(doc, self._handle(cb, {
          dbName: dbName,
          docId: doc._id,
          message: 'created'
        }));
      }
      tasks.push(task);
    });
  });
  async.parallel(tasks, cb);
};

/**
 * Create inexisting documents, update existing documents.
 *
 * @param {Object} dbSetup: database setup, keys are database names, values are documents
 * @param {Function} cb: standard cb(err, result) callback
 */
Db.prototype.saveDocuments = function (dbSetup, cb) {
  const CONFLICT = 409;
  var self = this,
    tasks = [];
  _.keys(dbSetup).forEach(function (dbName) {
    dbSetup[dbName].forEach(function (doc) {
      function task(cb) {

        // try to create documents, but with conflict error handling
        function _errorCb(err, result) {

          // if there is a conflict error, retrieve the document's revision first
          function _successCb(err, result) {

            // use the revision to update the existing documents
            // NOTE: it is not impossible that the revision of this document
            // is modified by other operations right before updating, Couchpenter
            // will only try once to avoid any possibility of retrying infinitely.
            doc._rev = result._rev;
            self.couch.use(dbName).insert(doc, self._handle(cb, {
              dbName: dbName,
              docId: doc._id,
              message: 'updated'
            }));
          }

          self.couch.use(dbName).get(doc._id, self._handle(cb, {
            dbName: dbName,
            docId: doc._id,
            successCb: _successCb
          }));
        }

        self.couch.use(dbName).insert(doc, self._handle(cb, {
          dbName: dbName,
          docId: doc._id,
          message: 'created',
          errorCb: _errorCb,
          errorCode: CONFLICT
        }));
      }
      tasks.push(task);
    });
  });
  async.parallel(tasks, cb);
};

/**
 * Delete existing documents.
 *
 * @param {Object} dbSetup: database setup, keys are database names, values are documents
 * @param {Function} cb: standard cb(err, result) callback
 */
Db.prototype.removeDocuments = function (dbSetup, cb) {
  var self = this,
    tasks = [];
  _.keys(dbSetup).forEach(function (dbName) {
    dbSetup[dbName].forEach(function (doc) {
      function task(cb) {

        // retrieve the document revision and use it to delete the document
        function _successCb(err, result) {

          self.couch.use(dbName).destroy(result._id, result._rev, self._handle(cb, {
            dbName: dbName,
            docId: doc._id,
            message: 'deleted'
          }));
        }

        self.couch.use(dbName).get(doc._id, self._handle(cb, {
          dbName: dbName,
          docId: doc._id,
          successCb: _successCb
        }));
      }
      tasks.push(task);
    });
  });
  async.parallel(tasks, cb);
};

/**
 * Warm views.
 *
 * @param {Object} dbSetup: database setup, keys are database names, values are documents
 * @param {Function} cb: standard cb(err, result) callback
 */
Db.prototype.warmViews = function (dbSetup, cb) {
  var self = this,
  tasks = [];
  _.keys(dbSetup).forEach(function (dbName) {
    dbSetup[dbName].forEach(function (doc) {
      if (doc._id && doc._id.match(/^_design\//) && doc.views) {
        tasks.push(function(cb) {
          cb = self._handle(cb, { dbName: dbName, docId: doc._id,
                                  message: 'warmed ' + _.keys(doc.views).length + ' views' });
          async.each(_.keys(doc.views), function (viewName, cb) {
            self.couch.use(dbName).view(doc._id.replace('_design/', ''), viewName, { limit: 0 }, cb);
          }, cb);
        });
      }
    });
  });
  async.parallel(tasks, cb);
};

Db.prototype._handle = function (cb, opts) {
  var self = this;

  // construct result object, the existence of error field indicates whether
  // the result is a success or not
  function _result(err, dbName, docId, message) {
    var result = {
      id: util.format('%s%s%s', dbName, (docId) ? '/' : '', docId || ''),
      message: message || util.format('%s (%s)', err.error, err.status_code)
    };
    if (err) {
      result.error = err;
    }
    return result;
  }

  return function (err, result) {
    if (err) {
      // special status code, delegate to error callback
      if (opts.errorCb && err.status_code === opts.errorCode) {
        opts.errorCb(err, result);
      // CouchDB error is camouflaged as result (status_code field is set by nano)
      } else if (err.status_code) {
        cb(null, _result(err, opts.dbName, opts.docId));
      // standard error (e.g. connection refused) is passed as standard error
      } else {
        cb(err);
      }
    } else {
      // delegate to success callback
      if (opts.successCb) {
        opts.successCb(err, result);
      // standard success
      } else {
        cb(null, _result(null, opts.dbName, opts.docId, opts.message));
      }
    }
  };
};

module.exports = Db;
<|endoftext|>


<commit_before>/*!
 * Copyright 2014 Apereo Foundation (AF) Licensed under the
 * Educational Community License, Version 2.0 (the "License"); you may
 * not use this file except in compliance with the License. You may
 * obtain a copy of the License at
 *
 *     http://opensource.org/licenses/ECL-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an "AS IS"
 * BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
 * or implied. See the License for the specific language governing
 * permissions and limitations under the License.
 */

var OAE = require('oae-util/lib/oae');
var OaeServer = require('oae-util/lib/server');
var log = require('oae-logger').logger('mediacore-rest');

var MediaCoreAPI = require('./api');

// Ensure that the MediaCore encoding callback endpoing bypasses CSRF validation. It has its own authenticity handling.
OaeServer.addSafePathPrefix('/api/mediacore/encodingCallback');

/**
 * @REST getMediacoreEmbedContentId
 *
 * Get the MediaCore embed url from a content id
 *
 * @Server      tenant
 * @Method      GET
 * @Path        /mediacore/embed/{contentId}
 * @PathParam   {string}                contentId           The contentId of the content to play in the MediaCore player
 * @Return      {MediacoreEmbedInfo}                        Returns object with MediaCore information
 */
OAE.tenantRouter.on('get', '/api/mediacore/embed/:contentId', function(req, res) {
    MediaCoreAPI.getEmbedInfo(req.ctx, req.params.contentId, function(err, embedInfo) {
        if (err) {
            return res.send(err.code, err.msg);
        }

        return res.send(200, embedInfo);
    });
});

/**
 * @REST postMediacoreEncodingCallback
 *
 * Update the thumbnails from MediaCore
 *
 * @Server      tenant
 * @Method      POST
 * @Path        /mediacore/encodingCallback
 * @BodyParam   {MediacoreId}   body             The MediaCore item id for which to update the thumbnail
 */
OAE.tenantRouter.on('post', '/api/mediacore/encodingCallback', function(req, res) {
    MediaCoreAPI.updateThumbnails(req.body.media_id, function(err) {
        if (err) {
            return res.send(err.code, err.msg);
        }

        return res.send(200);
    });
});
<commit_msg>note private API endpoint<commit_after>/*!
 * Copyright 2014 Apereo Foundation (AF) Licensed under the
 * Educational Community License, Version 2.0 (the "License"); you may
 * not use this file except in compliance with the License. You may
 * obtain a copy of the License at
 *
 *     http://opensource.org/licenses/ECL-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an "AS IS"
 * BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
 * or implied. See the License for the specific language governing
 * permissions and limitations under the License.
 */

var OAE = require('oae-util/lib/oae');
var OaeServer = require('oae-util/lib/server');
var log = require('oae-logger').logger('mediacore-rest');

var MediaCoreAPI = require('./api');

// Ensure that the MediaCore encoding callback endpoing bypasses CSRF validation. It has its own authenticity handling.
OaeServer.addSafePathPrefix('/api/mediacore/encodingCallback');

/**
 * @REST getMediacoreEmbedContentId
 *
 * Get the MediaCore embed url from a content id
 *
 * @Server      tenant
 * @Method      GET
 * @Path        /mediacore/embed/{contentId}
 * @PathParam   {string}                contentId           The contentId of the content to play in the MediaCore player
 * @Return      {MediacoreEmbedInfo}                        Returns object with MediaCore information
 */
OAE.tenantRouter.on('get', '/api/mediacore/embed/:contentId', function(req, res) {
    MediaCoreAPI.getEmbedInfo(req.ctx, req.params.contentId, function(err, embedInfo) {
        if (err) {
            return res.send(err.code, err.msg);
        }

        return res.send(200, embedInfo);
    });
});

/**
 * @REST postMediacoreEncodingCallback
 *
 * Update the thumbnails from MediaCore
 *
 * @Api         private
 * @Server      tenant
 * @Method      POST
 * @Path        /mediacore/encodingCallback
 * @BodyParam   {MediacoreId}   body             The MediaCore item id for which to update the thumbnail
 */
OAE.tenantRouter.on('post', '/api/mediacore/encodingCallback', function(req, res) {
    MediaCoreAPI.updateThumbnails(req.body.media_id, function(err) {
        if (err) {
            return res.send(err.code, err.msg);
        }

        return res.send(200);
    });
});
<|endoftext|>


<commit_before>function initialize() {
  var socket = io.connect();
  var encounterIds = {};
  var latlng = new google.maps.LatLng(35.643757,139.826647);
  var zoom = 18;
  var client = 'bulbasaur';
  var circles = {
    'bulbasaur': new google.maps.Circle({
      strokeColor: '#00FF00',
      strokeOpacity: 0.8,
      strokeWeight: 2,
      fillColor: '#00FF00',
      fillOpacity: 0.35,
      radius: 50
    }),
    'charmander': new google.maps.Circle({
      strokeColor: '#FF0000',
      strokeOpacity: 0.8,
      strokeWeight: 2,
      fillColor: '#FF0000',
      fillOpacity: 0.35,
      radius: 50
    }),
    'squirtle': new google.maps.Circle({
      strokeColor: '#0000FF',
      strokeOpacity: 0.8,
      strokeWeight: 2,
      fillColor: '#0000FF',
      fillOpacity: 0.35,
      radius: 50
    }),
  };

  if (localStorage) {
    var lat = parseFloat(localStorage.getItem('lat'));
    var lng = parseFloat(localStorage.getItem('lng'));
    var _zoom = parseInt(localStorage.getItem('zoom'));
    var _client = localStorage.getItem('client');
    if (lat && lng) {
      console.log('latlng');
      latlng = new google.maps.LatLng(lat,lng);
      console.log(latlng);
    }
    if (_zoom) {
      zoom = _zoom;
    }
    if (_client) {
      client = _client;
    }
  }
  var opts = {
    zoom: zoom,
    center: latlng,
    mapTypeId: google.maps.MapTypeId.ROADMAP
  };
  var map = new google.maps.Map(document.getElementById("mypoke"), opts);
  map.addListener('center_changed', function() {
    //console.log(map.getCenter());
    if(localStorage) {
      var center = map.getCenter();
      localStorage.setItem('lat', center.lat().toString());
      localStorage.setItem('lng', center.lng().toString());
    }
  });
  map.addListener('zoom_changed', function() {
    if(localStorage) {
      localStorage.setItem('zoom', map.getZoom().toString());
    }
  });
  var clientActive = function(active) {
    ['charmander', 'squirtle', 'bulbasaur'].forEach(function(value){
      if (value != active) {
        $('#' + value).removeClass('active');
      }
    });
    $('#' + active).addClass('active');
    client = active;
    localStorage.setItem('client', client);
    socket.emit('client', active);
  };
  clientActive(client);

  $('#search').click(function() {
    if ($('#search').hasClass('blink')) {
      socket.emit('search', null);
    } else {
      var center = map.getCenter();
      socket.emit('search', { latitude: center.lat(), longitude: center.lng() });
    }
  });

  socket.on('searching', function(status) {
    var _client = status['client'];
    console.log('searching: ' + _client);
    var status = status['status'];
    var circle = circles[_client];
    if (client == _client) {
      if (status == 'stop') {
        $('#search').removeClass('blink');
      } else {
        $('#search').addClass('blink');
      }
    }
    if (status == 'stop') {
      circle.setMap(null);
    } else {
      circle.setCenter({lat: status.latitude, lng: status.longitude});
      circle.setMap(map);
    }
  });

  $('#current').click(function() {
    if (navigator.geolocation) {
      $('#current').addClass('active');
      setTimeout(function() {$('#current').removeClass('active');}, 1000);
      navigator.geolocation.getCurrentPosition(
        function(position) {
          latlng = new google.maps.LatLng(position.coords.latitude,position.coords.longitude);
          map.setCenter(latlng);
        }
      );
    }
  });
  $('#bulbasaur').click(function() {
    clientActive('charmander');
  });
  $('#charmander').click(function() {
    clientActive('squirtle');
  });
  $('#squirtle').click(function() {
    clientActive('bulbasaur');
  });

  setInterval(function() {
    $.each(encounterIds, function(eid, pokemon) {
      if (Date.now() > pokemon.raw.expiration_time) {
        console.log("pokemon was deleted by cleaner")
        pokemon.marker.setMap(null);
        delete encounterIds[eid];
      }
    });
  }, 5000);

  // fetch first pokemons
  socket.emit('search', 'cache');
  setInterval(function() {
    socket.emit('search', 'cache');
  }, 5000);

  socket.on('pokemons', function(pokes) {
    $.each(pokes, function(eid, rawPokemon) {
      var pokemon = pokemons[rawPokemon.pokemon_id.toString()];
      pokemon.raw = rawPokemon;
      if (!encounterIds[eid]) {
        var latlng = new google.maps.LatLng(rawPokemon.lat ,rawPokemon.lng);
        var image = {
          url : pokemon.img,
          scaledSize : new google.maps.Size(32, 32)
        }
        var mopts = {
          position: latlng,
          map: map,
          icon: image,
          title: pokemon.ja
        };
        var marker = new google.maps.Marker(mopts);
        pokemon.marker = marker;
        var vanishDate = new Date(rawPokemon.expiration_time);
        var content = "Name: " + pokemon.ja + "
" + "Vanished: " + vanishDate.toLocaleString();
        var infowindow = new google.maps.InfoWindow({
          content: content
        });
        marker.addListener('click', function() {
          var map = infowindow.getMap();
          if (map !== null && typeof map !== "undefined") {
            infowindow.close();
          } else {
            infowindow.open(map, marker);
          }
        });
        console.log(rawPokemon.expiration_time - Date.now())
        setTimeout(function() {
          marker.setMap(null);
        }, rawPokemon.expiration_time - Date.now());
        encounterIds[eid] = pokemon;
      }
    })
  });

}
<commit_msg>Improve draw timing<commit_after>function initialize() {
  var socket = io.connect();
  var encounterIds = {};
  var latlng = new google.maps.LatLng(35.643757,139.826647);
  var zoom = 18;
  var client = 'bulbasaur';
  var clients = {
    'bulbasaur': {
      'status': 'stop'
    },
    'charmander': {
      'status': 'stop'
    },
    'squirtle': {
      'status': 'stop'
    }
  }
  var circles = {
    'bulbasaur': new google.maps.Circle({
      strokeColor: '#00FF00',
      strokeOpacity: 0.8,
      strokeWeight: 2,
      fillColor: '#00FF00',
      fillOpacity: 0.35,
      radius: 50
    }),
    'charmander': new google.maps.Circle({
      strokeColor: '#FF0000',
      strokeOpacity: 0.8,
      strokeWeight: 2,
      fillColor: '#FF0000',
      fillOpacity: 0.35,
      radius: 50
    }),
    'squirtle': new google.maps.Circle({
      strokeColor: '#0000FF',
      strokeOpacity: 0.8,
      strokeWeight: 2,
      fillColor: '#0000FF',
      fillOpacity: 0.35,
      radius: 50
    }),
  };

  if (localStorage) {
    var lat = parseFloat(localStorage.getItem('lat'));
    var lng = parseFloat(localStorage.getItem('lng'));
    var _zoom = parseInt(localStorage.getItem('zoom'));
    var _client = localStorage.getItem('client');
    if (lat && lng) {
      console.log('latlng');
      latlng = new google.maps.LatLng(lat,lng);
      console.log(latlng);
    }
    if (_zoom) {
      zoom = _zoom;
    }
    if (_client) {
      client = _client;
    }
  }
  var opts = {
    zoom: zoom,
    center: latlng,
    mapTypeId: google.maps.MapTypeId.ROADMAP
  };
  var map = new google.maps.Map(document.getElementById("mypoke"), opts);
  map.addListener('center_changed', function() {
    //console.log(map.getCenter());
    if(localStorage) {
      var center = map.getCenter();
      localStorage.setItem('lat', center.lat().toString());
      localStorage.setItem('lng', center.lng().toString());
    }
  });
  map.addListener('zoom_changed', function() {
    if(localStorage) {
      localStorage.setItem('zoom', map.getZoom().toString());
    }
  });

  var drawSearchButton = function() {
    if (clients[client]['status'] == 'stop') {
      $('#search').removeClass('blink');
    } else {
      $('#search').addClass('blink');
    }
  };
  var clientActive = function(active) {
    ['charmander', 'squirtle', 'bulbasaur'].forEach(function(value){
      if (value != active) {
        $('#' + value).removeClass('active');
      }
    });
    $('#' + active).addClass('active');
    client = active;
    localStorage.setItem('client', client);
    socket.emit('client', active);
    drawSearchButton();
  };
  clientActive(client);

  $('#search').click(function() {
    if (clients[client]['status'] != 'stop') {
      $('#search').removeClass('blink');
      socket.emit('search', null);
    } else {
      $('#search').addClass('blink');
      var center = map.getCenter();
      socket.emit('search', { latitude: center.lat(), longitude: center.lng() });
    }
  });

  socket.on('searching', function(status) {
    var _client = status['client'];
    console.log('searching: ' + _client);
    status = status['status'];
    clients[_client]['status'] = status;
    drawSearchButton();
    var circle = circles[_client];
    if (status == 'stop') {
      circle.setMap(null);
    } else {
      circle.setCenter({lat: status.latitude, lng: status.longitude});
      circle.setMap(map);
    }
  });

  $('#current').click(function() {
    if (navigator.geolocation) {
      $('#current').addClass('active');
      setTimeout(function() {$('#current').removeClass('active');}, 1000);
      navigator.geolocation.getCurrentPosition(
        function(position) {
          latlng = new google.maps.LatLng(position.coords.latitude,position.coords.longitude);
          map.setCenter(latlng);
        }
      );
    }
  });
  $('#bulbasaur').click(function() {
    clientActive('charmander');
  });
  $('#charmander').click(function() {
    clientActive('squirtle');
  });
  $('#squirtle').click(function() {
    clientActive('bulbasaur');
  });

  setInterval(function() {
    $.each(encounterIds, function(eid, pokemon) {
      if (Date.now() > pokemon.raw.expiration_time) {
        console.log("pokemon was deleted by cleaner")
        pokemon.marker.setMap(null);
        delete encounterIds[eid];
      }
    });
  }, 5000);

  // fetch first pokemons
  socket.emit('search', 'cache');
  setInterval(function() {
    socket.emit('search', 'cache');
  }, 5000);

  socket.on('pokemons', function(pokes) {
    $.each(pokes, function(eid, rawPokemon) {
      var pokemon = pokemons[rawPokemon.pokemon_id.toString()];
      pokemon.raw = rawPokemon;
      if (!encounterIds[eid]) {
        var latlng = new google.maps.LatLng(rawPokemon.lat ,rawPokemon.lng);
        var image = {
          url : pokemon.img,
          scaledSize : new google.maps.Size(32, 32)
        }
        var mopts = {
          position: latlng,
          map: map,
          icon: image,
          title: pokemon.ja
        };
        var marker = new google.maps.Marker(mopts);
        pokemon.marker = marker;
        var vanishDate = new Date(rawPokemon.expiration_time);
        var content = "Name: " + pokemon.ja + "
" + "Vanished: " + vanishDate.toLocaleString();
        var infowindow = new google.maps.InfoWindow({
          content: content
        });
        marker.addListener('click', function() {
          var map = infowindow.getMap();
          if (map !== null && typeof map !== "undefined") {
            infowindow.close();
          } else {
            infowindow.open(map, marker);
          }
        });
        console.log(rawPokemon.expiration_time - Date.now())
        setTimeout(function() {
          marker.setMap(null);
        }, rawPokemon.expiration_time - Date.now());
        encounterIds[eid] = pokemon;
      }
    })
  });

}
<|endoftext|>