<commit_before>'use strict'

var botQueryHandler = require('./storage/replyQuery');
var tg = require('./telegram/telegramAPI');
var replyQuery = require('./storage/replyQuery');
var lfm = require('./lfmController')

/*
  queryData.a - artist
  queryData.o - object (track/album)
  queryData.m - method
  short because of Telegram 64 bytes limit
*/

botQueryHandler.execute = function (query, queryData) {
    botQueryHandler[queryData.m](query, queryData, botQueryHandler._answer);
};

botQueryHandler.youtube = function (query, queryData, answer) {
    var replyData  = replyQuery.get(queryData.a, queryData.o, 'tracks');
    if (!replyData) {
        botQueryHandler._outdatedButton(query);
        return answer(query);
    };
    lfm.getYouTubeLink(replyData.artist, replyData.object, function (response) {
        tg.sendTextMessage(response, query.message.chat.id);
        return answer(query);
    });
};

botQueryHandler.prevPage = function (query, queryData) {
    var artist = replyQuery.getArtist(queryData.a);
    if (!artist) {
        botQueryHandler._outdatedButton(query);
        return answer(query);
    };
    if (queryData.page == '1') {
        tg.sendTextMessage('You are on page 1', query.message.chat.id);
        return answer(query);
    };
    lfm.getTopTracks(artist, +queryData.page - 1, function (response, replyMarkup) {
        botQueryHandler._editMessage(query, response, replyMarkup);
        answer(query);
    });
};

botQueryHandler.nextPage = function (query, queryData) {
    var artist = replyQuery.getArtist(queryData.a);
    if (!artist) {
        botQueryHandler._outdatedButton(query);
        return answer(query);
    };
    lfm.getTopTracks(artist, +queryData.page + 1, function (response, replyMarkup) {
        botQueryHandler._editMessage(query, response, replyMarkup);
        answer(query);
    });
};

botQueryHandler._outdatedButton = function (query) {

    tg.sendTextMessage('Sorry, this button is outdated', query.message.chat.id);
};

botQueryHandler._editMessage = function (query, response, replyMarkup) {
    tg.editMessageText(
        query.message.chat.id,
        query.message.message_id,
        null,
        response,
        'HTML',
        1,
        replyMarkup
    );
};

botQueryHandler._answer = function (query) {
     tg.answerCallbackQuery(query.id);
};

module.exports = botQueryHandler;
<commit_msg>Fix answering replyQuerys<commit_after>'use strict'

var botQueryHandler = require('./storage/replyQuery');
var tg = require('./telegram/telegramAPI');
var replyQuery = require('./storage/replyQuery');
var lfm = require('./lfmController')

/*
  queryData.a - artist
  queryData.o - object (track/album)
  queryData.m - method
  short because of Telegram 64 bytes limit
*/

botQueryHandler.execute = function (query, queryData) {
    botQueryHandler[queryData.m](query, queryData, botQueryHandler._answer);
};

botQueryHandler.youtube = function (query, queryData, answer) {
    var replyData  = replyQuery.get(queryData.a, queryData.o, 'tracks');
    if (!replyData) {
        botQueryHandler._outdatedButton(query);
        return answer(query);
    };
    lfm.getYouTubeLink(replyData.artist, replyData.object, function (response) {
        tg.sendTextMessage(response, query.message.chat.id);
        return answer(query);
    });
};

botQueryHandler.prevPage = function (query, queryData, answer) {
    var artist = replyQuery.getArtist(queryData.a);
    if (!artist) {
        botQueryHandler._outdatedButton(query);
        return answer(query);
    };
    if (queryData.page == '1') {
        tg.sendTextMessage('You are on page 1', query.message.chat.id);
        return answer(query);
    };
    lfm.getTopTracks(artist, +queryData.page - 1, function (response, replyMarkup) {
        botQueryHandler._editMessage(query, response, replyMarkup);
        answer(query);
    });
};

botQueryHandler.nextPage = function (query, queryData, answer) {
    var artist = replyQuery.getArtist(queryData.a);
    if (!artist) {
        botQueryHandler._outdatedButton(query);
        return answer(query);
    };
    lfm.getTopTracks(artist, +queryData.page + 1, function (response, replyMarkup) {
        botQueryHandler._editMessage(query, response, replyMarkup);
        answer(query);
    });
};

botQueryHandler._outdatedButton = function (query) {

    tg.sendTextMessage('Sorry, this button is outdated', query.message.chat.id);
};

botQueryHandler._editMessage = function (query, response, replyMarkup) {
    tg.editMessageText(
        query.message.chat.id,
        query.message.message_id,
        null,
        response,
        'HTML',
        1,
        replyMarkup
    );
};

botQueryHandler._answer = function (query) {
     tg.answerCallbackQuery(query.id);
};

module.exports = botQueryHandler;
<|endoftext|>


<commit_before>var injector = require( 'injector' )
  , env = null;

module.exports = function() {
    if ( env === null ) {
        var path = require( 'path' )
          , appRoot = path.resolve( __dirname + '/../..')
          , config = require( appRoot + '/config' )
          , packageJson = require( appRoot + '/package.json' )
          , express = require( 'express' )
          , app = express()
          , moduleLdr;

        injector.instance( 'express', express );
        injector.instance( 'app', app );
        injector.instance( 'config', config );
        injector.instance( 'packageJson', packageJson );

        moduleLdr = require( './moduleLoader' ).getInstance( env );

        // Add our moduleLoader to the injector
        injector.instance( 'moduleLoader', moduleLdr );

        env = {
            config: config,
            express: express,
            app: app,
            moduleLoader: moduleLdr,
            webPort: process.env.NODE_WWW_PORT || config.webPort || 8080,
            packageJson: packageJson
        };

        injector.instance( 'env', env );

        // Define all core classes on the injector
        var classes = require( 'classes' );

        injector.instance( 'Class', classes.Class );
        injector.instance( 'Model', classes.Model );
        injector.instance( 'Service', classes.Service );
        injector.instance( 'Controller', classes.Controller );
        injector.instance( 'Module', classes.Module );
    }

    return env;
}<commit_msg>feat(injector-instances): Added Promise, Exceptions and async to injector<commit_after>var injector = require( 'injector' )
  , env = null;

module.exports = function() {
    if ( env === null ) {
        var path = require( 'path' )
          , appRoot = path.resolve( __dirname + '/../..')
          , config = require( appRoot + '/config' )
          , packageJson = require( appRoot + '/package.json' )
          , express = require( 'express' )
          , app = express()
          , moduleLdr;

        injector.instance( 'express', express );
        injector.instance( 'app', app );
        injector.instance( 'config', config );
        injector.instance( 'packageJson', packageJson );
        injector.instance( 'Promise', require( 'bluebird') );
        injector.instance( 'Exceptions', require( 'exceptions' ) );
        injector.instance( 'async', require( 'async' ) );

        moduleLdr = require( './moduleLoader' ).getInstance( env );

        // Add our moduleLoader to the injector
        injector.instance( 'moduleLoader', moduleLdr );

        env = {
            config: config,
            express: express,
            app: app,
            moduleLoader: moduleLdr,
            webPort: process.env.NODE_WWW_PORT || config.webPort || 8080,
            packageJson: packageJson
        };

        injector.instance( 'env', env );

        // Define all core classes on the injector
        var classes = require( 'classes' );

        injector.instance( 'Class', classes.Class );
        injector.instance( 'Model', classes.Model );
        injector.instance( 'Service', classes.Service );
        injector.instance( 'Controller', classes.Controller );
        injector.instance( 'Module', classes.Module );
    }

    return env;
}<|endoftext|>


<commit_before>import { h, Component } from 'preact';
import style from './style';

export default class Track extends Component {
  state = {
    playing: false
  };

  sanitizeQuery(query) {
    query = query.toLowerCase(); // to lower case
    query = query.replace(/(dj set)/g, ''); // remove dj set info from name
    query = query.replace(/(official)/g, ''); // remove official info from name
    query = query.replace(/\./g, ''); // remove .s
    return query;
  }

  findArtist(query) {
    const url = new URL('https://api.spotify.com/v1/search');
    query = this.sanitizeQuery(query);
    url.searchParams.append('q', query);
    url.searchParams.append('type', 'artist');
    url.searchParams.append('limit', '1');

    return new Promise((resolve, reject) => {
      return fetch(url)
        .then(response => response.json())
        .then(data => {
          if (data.artists.total <= 0) {
            return reject(`Spotify found no artists with query: "${query}".`);
          }

          const items = data.artists.items;

          const item = items.find(item => item.name.toLowerCase() === query);

          if (!item) {
            return reject(`No artist called "${query}".`);
          }

          return resolve(item.id);
        })
        .catch(reject);
    });
  }

  getTopTrack(id) {
    const url = new URL(`https://api.spotify.com/v1/artists/${id}/top-tracks`);
    url.searchParams.append('country', 'GB');
    url.searchParams.append('limit', '1');

    return fetch(url).then(response => response.json());
  }

  getTopTrackInfo(data) {
    return new Promise((resolve, reject) => {
      const { name } = this.props;

      // return nothing if no data sent to template
      if (data.tracks.length <= 0) {
        reject('No tracks for this artist');
      }

      const tracks = data.tracks;

      let track = tracks.find(track => track.artists[0].name.toLowerCase() === name.toLowerCase());

      if (!track) {
        track = tracks[0];
      }

      if (track.album.images.length > 0) {
        track.image = track.album.images[1].url;
      }

      resolve(track);
    });
  }

  getMusic() {
    const { name } = this.props;

    this.findArtist(name)
      .then(this.getTopTrack)
      .then(this.getTopTrackInfo.bind(this))
      .then(track => this.setState({ track }));
  }

	// gets called when this route is navigated to
  componentDidMount() {
    this.getMusic();
  }

	// gets called just before navigating away from the route
  componentWillUnmount() {
    if (this.state.audio) {
      this.stopAudio();
    }
  }

  playAudio () {
    this.state.audio.play();
    this.setState({ playing: true });
  }

  pauseAudio () {
    this.state.audio.pause();
    this.setState({ playing: false });
  }

  stopAudio () {
    this.state.audio.pause();
    this.setState({ playing: false, audio: null });
  }

  handleClick() {
    const { playing, track } = this.state;
    let { audio } = this.state;

    if (!track) {
      return false;
    }

    if (!audio) {
      audio = new Audio(track.preview_url);
      audio.addEventListener('paused', this.pauseAudio);
      audio.addEventListener('ended', this.stopAudio);

      this.setState({ audio });
    }

    if (playing) {
      this.pauseAudio();
    } else {
      this.playAudio();
    }
  }

  render() {
    const { track, playing } = this.state;

    if (!track || !track.artists || track.artists.length <= 0) {
      return false;
    }

    const trackArtists = track.artists.map(artist => artist.name).join(', ');

    return (

        <div class={`${style.cover} ${playing ? style.coverPlaying : ''}`}
             style={{ backgroundImage: `url(${track ? track.image : ''})` }}
             onClick={this.handleClick.bind(this)}
             />

           {track && track.name}
           {track && trackArtists}
           {track && track.album.name}

		);
  }
}
<commit_msg>Use basic string for Spotify API requests<commit_after>import { h, Component } from 'preact';
import style from './style';

export default class Track extends Component {
  state = {
    playing: false
  };

  sanitizeQuery(query) {
    query = query.toLowerCase(); // to lower case
    query = query.replace(/(dj set)/g, ''); // remove dj set info from name
    query = query.replace(/(official)/g, ''); // remove official info from name
    query = query.replace(/\./g, ''); // remove .s
    return query;
  }

  findArtist(query) {
    query = this.sanitizeQuery(query);
    const url = 'https://api.spotify.com/v1/search' +
                `?q=${query}` +
                '&type=artist' +
                '&limit=1';

                console.log(url);

    return new Promise((resolve, reject) => {
      return fetch(url)
        .then(response => response.json())
        .then(data => {
          if (data.error) {
            return reject(data.error.message);
          }

          if (data.artists.total <= 0) {
            return reject(`Spotify found no artists with query: "${query}".`);
          }

          const items = data.artists.items;

          const item = items.find(item => item.name.toLowerCase() === query);

          if (!item) {
            return reject(`No artist called "${query}".`);
          }

          return resolve(item.id);
        })
        .catch(reject);
    });
  }

  getTopTrack(id) {
    const url = `https://api.spotify.com/v1/artists/${id}/top-tracks` +
                '?country=GB' +
                '&limit=1';

    return fetch(url).then(response => response.json());
  }

  getTopTrackInfo(data) {
    return new Promise((resolve, reject) => {
      const { name } = this.props;
        if (data.error) {
          return reject(data.error.message);
        }

      // return nothing if no data sent to template
      if (data.tracks.length <= 0) {
        reject('No tracks for this artist');
      }

      const tracks = data.tracks;

      let track = tracks.find(track => track.artists[0].name.toLowerCase() === name.toLowerCase());

      if (!track) {
        track = tracks[0];
      }

      if (track.album.images.length > 0) {
        track.image = track.album.images[1].url;
      }

      resolve(track);
    });
  }

  getMusic() {
    const { name } = this.props;

    this.findArtist(name)
      .then(this.getTopTrack)
      .then(this.getTopTrackInfo.bind(this))
      .then(track => this.setState({ track }));
  }

	// gets called when this route is navigated to
  componentDidMount() {
    this.getMusic();
  }

	// gets called just before navigating away from the route
  componentWillUnmount() {
    if (this.state.audio) {
      this.stopAudio();
    }
  }

  playAudio () {
    this.state.audio.play();
    this.setState({ playing: true });
  }

  pauseAudio () {
    this.state.audio.pause();
    this.setState({ playing: false });
  }

  stopAudio () {
    this.state.audio.pause();
    this.setState({ playing: false, audio: null });
  }

  handleClick() {
    const { playing, track } = this.state;
    let { audio } = this.state;

    if (!track) {
      return false;
    }

    if (!audio) {
      audio = new Audio(track.preview_url);
      audio.addEventListener('paused', this.pauseAudio);
      audio.addEventListener('ended', this.stopAudio);

      this.setState({ audio });
    }

    if (playing) {
      this.pauseAudio();
    } else {
      this.playAudio();
    }
  }

  render() {
    const { track, playing } = this.state;

    if (!track || !track.artists || track.artists.length <= 0) {
      return false;
    }

    const trackArtists = track.artists.map(artist => artist.name).join(', ');

    return (

        <div class={`${style.cover} ${playing ? style.coverPlaying : ''}`}
             style={{ backgroundImage: `url(${track ? track.image : ''})` }}
             onClick={this.handleClick.bind(this)}
             />

           {track && track.name}
           {track && trackArtists}
           {track && track.album.name}

		);
  }
}
<|endoftext|>


<commit_before>/**
 * cookies metrics
 */
'use strict';

exports.version = '0.2';

exports.module = function(phantomas) {
	// monitor cookies in HTTP headers
	var cookiesSent = 0,
		cookiesRecv = 0,
		cookiesDomains = {};

	phantomas.on('send', function(entry, res) {
		res.headers.forEach(function(header) {
			switch (header.name) {
				case 'Cookie':
					cookiesSent += header.value.length;
					cookiesDomains[entry.domain] = true;
					break;
			}
		});
	});

	phantomas.on('recv', function(entry, res) {
		res.headers.forEach(function(header) {
			switch (header.name) {
				case 'Set-Cookie':
					cookiesRecv += header.value.length;
					cookiesDomains[entry.domain] = true;
					break;
			}
		});
	});

	// domain cookies (accessible by the browser)
	phantomas.on('report', function() {
		/* global document: true, window: true */
		phantomas.setMetric('cookiesSent', cookiesSent); // @desc length of cookies sent in HTTP requests @unreliable
		phantomas.setMetric('cookiesRecv', cookiesRecv); // @desc length of cookies received in HTTP responses @unreliable

		// domains with cookies
		var domainsWithCookies = 0;
		for (var domain in cookiesDomains) {
			domainsWithCookies++;
		}
		phantomas.setMetric('domainsWithCookies', domainsWithCookies); // @desc number of domains with cookies set

		phantomas.setMetricEvaluate('documentCookiesLength', function() { // @desc length of document.cookie
			try {
				return document.cookie.length;
			}
			catch(ex) {
				window.__phantomas.log('documentCookiesLength: not set because ' + ex + '!');
				return 0;
			}
		});

		phantomas.setMetricEvaluate('documentCookiesCount', function() { //@desc number of cookies in document.cookie
			try {
				return document.cookie.split(';').length;
			}
			catch(ex) {
				window.__phantomas.log('documentCookiesCount: not set because ' + ex + '!');
				return 0;
			}
		});
	});
};
<commit_msg>Cookies: add offenders for domainsWithCookies<commit_after>/**
 * cookies metrics
 */
'use strict';

exports.version = '0.3';

exports.module = function(phantomas) {
	// monitor cookies in HTTP headers
	var Collection = require('../../lib/collection'),
		cookiesDomains = new Collection();

	phantomas.setMetric('cookiesSent'); // @desc length of cookies sent in HTTP requests @unreliable
	phantomas.setMetric('cookiesRecv'); // @desc length of cookies received in HTTP responses
	phantomas.setMetric('domainsWithCookies'); // @desc number of domains with cookies set
	phantomas.setMetric('documentCookiesLength'); // @desc length of document.cookie
	phantomas.setMetric('documentCookiesCount'); //@desc number of cookies in document.cookie

	phantomas.on('send', function(entry, res) {
		res.headers.forEach(function(header) {
			switch (header.name) {
				case 'Cookie':
					phantomas.incrMetric('cookiesSent', header.value.length);
					cookiesDomains.push(entry.domain);

					phantomas.log('Cookie: sent "%s" (for %s)', header.value, entry.domain);
					break;
			}
		});
	});

	phantomas.on('recv', function(entry, res) {
		res.headers.forEach(function(header) {
			switch (header.name) {
				case 'Set-Cookie':
					var cookies = (header.value || '').split('\n');

					cookies.forEach(function(cookie) {
						phantomas.incrMetric('cookiesRecv', cookie.length);
						cookiesDomains.push(entry.domain);

						phantomas.log('Cookie: received "%s" (for %s)', cookie, entry.domain);
					});
					break;
			}
		});
	});

	// domain cookies (accessible by the browser)
	phantomas.on('report', function() {
		/* global document: true, window: true */

		// domains with cookies
		cookiesDomains.forEach(function(domain, cnt) {
			phantomas.incrMetric('domainsWithCookies');
			phantomas.addOffender('domainsWithCookies', '%s: %d cookie(s)', domain, cnt);
		});

		phantomas.setMetricEvaluate('documentCookiesLength', function() {
			try {
				return document.cookie.length;
			}
			catch(ex) {
				window.__phantomas.log('documentCookiesLength: not set because ' + ex + '!');
				return 0;
			}
		});

		phantomas.setMetricEvaluate('documentCookiesCount', function() {
			try {
				return document.cookie.split(';').length;
			}
			catch(ex) {
				window.__phantomas.log('documentCookiesCount: not set because ' + ex + '!');
				return 0;
			}
		});
	});
};
<|endoftext|>


<commit_before>//default core extensions for the default Application Frame modules - copyright by TitanNano / Jovan Ggerodetti - http://www.titannano.de

$('escape').wrapper(function(){

    "use strict";

/* --- eventManagement --- */
    $$.EventManager= function(){
        var listeners= [];
        this.addEventListener= function(type, listener, useCapture){
            listeners.push({
                type : type,
                listener : listener,
                useCapture : useCapture
            });
            };
        this.dispatchEvent= function(event){
            listeners.forEach(function(item){
                if(item.type === event.type){
                    item.listener(event);
                    }
                });
            };
        };

/* --- advanced Prototyping --- */
    $$.Prototype= function(types){
        var prototype= {};
        types.forEach(function(item){
            var x= item.prototype || item;
            for(var i in x){
                if(x.__lookupGetter__(i) || x.__lookupSetter__(i)){
                    prototype.__defineGetter__(i, x.__lookupGetter__(i));
                    prototype.__defineSetter__(i, x.__lookupSetter__(i));
                }else{
                    prototype[i]= x[i];
                }
            }
        });
        return prototype;
    };

/* --- forEach extension --- */
var forEach= function(callback){
    for(var i= 0; i<this.length; i++){
        callback(this[i], i);
        }
    };

// if your required list type isn't here just add it
if($$.Array && !$$.Array.prototype.forEach) $$.Array.prototype.forEach= forEach;
if($$.NodeList && !$$.NodeList.prototype.forEach) $$.NodeList.prototype.forEach= forEach;
if($$.navigator && $$.navigator.isTouch && !$$.TouchList.prototype.forEach) $$.TouchList.prototype.forEach= forEach;

/* --- array last extension --- */
var ArrayLast= function(){
	return this[this.length -1];
};

if($$.Array && !$$.Array.prototype.last) $$.Array.prototype.last= ArrayLast;

/* --- DOM Node extensions --- */
if($$.Node){
    $$.Node.prototype.transition= function(add, remove){
        var node= this;
        return new $$.Promise(function(setValue){
//          set event listener
            node.addEventListener('transitionend', function x(e){
                this.removeEventListener('transitionend', x);
                setValue(this, e);
            });
//          set css classes
            if(add && add instanceof Array)
                add.forEach(function(item){
                    node.classList.add(item);
                });
            else if(add)
                node.classList.add(add);

            if(remove && remove instanceof Array)
                remove.forEach(function(item){
                    node.classList.add(item);
                });
            else if(remove)
                node.classList.remove(remove);
        });
    };
}

});<commit_msg>new core extensions<commit_after>//default core extensions for the default Application Frame modules - copyright by TitanNano / Jovan Gerodetti - http://www.titannano.de

$('wrap')(function(){

"use strict";

// if your required list type isn't here just add it
if($$.NodeList && !$$.NodeList.prototype.forEach) $$.NodeList.prototype.forEach= $$.Array.prototype.forEach;
if($$.TouchList && !$$.TouchList.prototype.forEach) $$.TouchList.prototype.forEach= $$.Array.prototype.forEach;

/* --- last extension --- */
var ArrayLast= function(){
	return this[this.length -1];
};

if($$.Array && !$$.Array.prototype.last) $$.Array.prototype.last= ArrayLast;

});
<|endoftext|>


<commit_before>import React from 'react';
import R from 'ramda';
import md5 from 'spark-md5';
import binaryXHR from 'binary-xhr';
import fetch from 'isomorphic-fetch';
import {Link, IndexLink} from 'react-router';
import {sortBadgeHashtags} from '../badge_logic/badge_cruncher.js';

export default React.createClass({
  getInitialState: function () {
    return {
      userName: '',
      userId: 0,
      userPic: '',
      userTagline: '',
      badges: ''
    };
  },
  arrayBufferToBase64: function (buffer) {
    var binary = '';
    var bytes = new Uint8Array(buffer);
    var len = bytes.byteLength;
    for (var i = 0; i < len; i++) {
      binary += String.fromCharCode(bytes[i]);
    }
    return window.btoa(binary);
  },
  setGravatar: function (imageUrl, localGenericUrl, component) {
    // Gravatar users who have not set an image may have one of
    // several generic image versions. They are visually identical
    // but resolve to different checksums. This list may grow as
    // more are discovered.
    var genericHashes = [
      '9a2ccc34a976d9e034142f01b205d8e8',
      '1189394e6979d5a03c75384b58b79b7c'
    ];
    binaryXHR(imageUrl, function (err, image) {
      if (!err) {
        // Hash image
        var imageStr64 = component.arrayBufferToBase64(image);
        var imageHash = md5.hash(imageStr64);
        // Compare hashes and set state to either the user's custom
        // Gravatar image or the local generic image
        var newImageUrl = R.contains(imageHash, genericHashes) ? localGenericUrl : imageUrl;
        component.setState({userPic: newImageUrl});
      }
    });
  },
  setUserPic: function (userId) {
    var component = this;
    return fetch('http://api.openstreetmap.org/api/0.6/user/' + userId)
    .then(function (res) {
      if (res.status >= 200 && res.status < 300) {
        return res.text();
      } else {
        throw new Error("Couldn't retrieve data", res.statusText);
      }
    })
    .then(function (xmlString) {
      var localGenericUrl = 'assets/graphics/osm-user-blank.jpg';
      var url = '';
      var urls = [];
      // Check for img href in user profile
      var urlBegin = xmlString.split(''));
        urls = url.split('&d=');
        if (urls.length < 2) {
          // If one img href, use the 'large' version to set
          // state (it is the user's custom OSM profile pic)
          url = url.replace('/original/', '/large/');
          component.setState({userPic: url});
        } else {
          // If >one img href, pic is from Gravatar, so check if
          // user has custom or generic pic and set state to local
          // generic or 128px version of their pic accordingly.
          url = urls[0].replace('?s=256', '?s=128');
          component.setGravatar(url, localGenericUrl, component);
        }
      }
    });
  },
  componentWillReceiveProps: function (nextProps) {
    if (nextProps) {
      var userId = nextProps.user.id;
      var userTagline = this.userTagline(nextProps.user.badges.length);
      var latestBadge = sortBadgeHashtags(nextProps.user);
      var userName = nextProps.user.name;
      var userNameCap = nextProps.user.name.charAt(0).toUpperCase() + nextProps.user.name.slice(1);
      var badgeChecker = false;
      var latestBadgeName = '';
      var latestBadgeLevel = '';

      if (latestBadge.length !== 0) {
        latestBadgeName = latestBadge[0].name;
        latestBadgeLevel = latestBadge[0].level;
        badgeChecker = true;
      }

      this.setState({
        userName: userName,
        userNameCap: userNameCap,
        userId: userId,
        userTagline: userTagline,
        userBadge: latestBadgeName,
        badgeLevel: latestBadgeLevel,
        badgeCheck: badgeChecker
      });
      this.setUserPic(userId);
    }
  },
  userTagline: function (badgeCount) {
    if (badgeCount < 1) {
      return 'Beginner Mapper';
    }
    if (badgeCount >= 1 && badgeCount < 8) {
      return 'Novice Mapper';
    }
    if (badgeCount >= 8 && badgeCount < 16) {
      return 'Pro Mapper';
    }
    if (badgeCount >= 16 && badgeCount < 30) {
      return 'Super Mapper';
    }
    return 'Map Addict';
  },
  render: function () {
    var twittermsg = '';
    if (this.state.badgeCheck) {
      var badgeName = this.state.userBadge;
      var badgeLevel = this.state.badgeLevel;
      twittermsg = this.state.userNameCap + ' earned the ' + badgeName + ' badge (lv.' + badgeLevel + ') on MissingMaps!';
    } else {
      twittermsg = this.state.userNameCap + ' contributed to MissingMaps! Checkout their progress at ';
    }
    let message = 'https://twitter.com/intent/tweet?text=' + encodeURIComponent(twittermsg + ' ' + window.location);
    var osmlink = 'http://www.openstreetmap.org/user/' + this.state.userName;
    return (

              ← Back to search

                  Share

              {this.state.userName}

{this.state.userTagline}   |   OSM Profile

              <IndexLink to={`/${this.state.userId}`} activeClassName="activeLink">Overview</IndexLink>
              <Link to={`/${this.state.userId}/badges`} activeClassName="activeLink">Badges</Link>

    );
  }
});
<commit_msg>adding MissingMaps hashtag. closes #126<commit_after>import React from 'react';
import R from 'ramda';
import md5 from 'spark-md5';
import binaryXHR from 'binary-xhr';
import fetch from 'isomorphic-fetch';
import {Link, IndexLink} from 'react-router';
import {sortBadgeHashtags} from '../badge_logic/badge_cruncher.js';

export default React.createClass({
  getInitialState: function () {
    return {
      userName: '',
      userId: 0,
      userPic: '',
      userTagline: '',
      badges: ''
    };
  },
  arrayBufferToBase64: function (buffer) {
    var binary = '';
    var bytes = new Uint8Array(buffer);
    var len = bytes.byteLength;
    for (var i = 0; i < len; i++) {
      binary += String.fromCharCode(bytes[i]);
    }
    return window.btoa(binary);
  },
  setGravatar: function (imageUrl, localGenericUrl, component) {
    // Gravatar users who have not set an image may have one of
    // several generic image versions. They are visually identical
    // but resolve to different checksums. This list may grow as
    // more are discovered.
    var genericHashes = [
      '9a2ccc34a976d9e034142f01b205d8e8',
      '1189394e6979d5a03c75384b58b79b7c'
    ];
    binaryXHR(imageUrl, function (err, image) {
      if (!err) {
        // Hash image
        var imageStr64 = component.arrayBufferToBase64(image);
        var imageHash = md5.hash(imageStr64);
        // Compare hashes and set state to either the user's custom
        // Gravatar image or the local generic image
        var newImageUrl = R.contains(imageHash, genericHashes) ? localGenericUrl : imageUrl;
        component.setState({userPic: newImageUrl});
      }
    });
  },
  setUserPic: function (userId) {
    var component = this;
    return fetch('http://api.openstreetmap.org/api/0.6/user/' + userId)
    .then(function (res) {
      if (res.status >= 200 && res.status < 300) {
        return res.text();
      } else {
        throw new Error("Couldn't retrieve data", res.statusText);
      }
    })
    .then(function (xmlString) {
      var localGenericUrl = 'assets/graphics/osm-user-blank.jpg';
      var url = '';
      var urls = [];
      // Check for img href in user profile
      var urlBegin = xmlString.split(''));
        urls = url.split('&d=');
        if (urls.length < 2) {
          // If one img href, use the 'large' version to set
          // state (it is the user's custom OSM profile pic)
          url = url.replace('/original/', '/large/');
          component.setState({userPic: url});
        } else {
          // If >one img href, pic is from Gravatar, so check if
          // user has custom or generic pic and set state to local
          // generic or 128px version of their pic accordingly.
          url = urls[0].replace('?s=256', '?s=128');
          component.setGravatar(url, localGenericUrl, component);
        }
      }
    });
  },
  componentWillReceiveProps: function (nextProps) {
    if (nextProps) {
      var userId = nextProps.user.id;
      var userTagline = this.userTagline(nextProps.user.badges.length);
      var latestBadge = sortBadgeHashtags(nextProps.user);
      var userName = nextProps.user.name;
      var userNameCap = nextProps.user.name.charAt(0).toUpperCase() + nextProps.user.name.slice(1);
      var badgeChecker = false;
      var latestBadgeName = '';
      var latestBadgeLevel = '';

      if (latestBadge.length !== 0) {
        latestBadgeName = latestBadge[0].name;
        latestBadgeLevel = latestBadge[0].level;
        badgeChecker = true;
      }

      this.setState({
        userName: userName,
        userNameCap: userNameCap,
        userId: userId,
        userTagline: userTagline,
        userBadge: latestBadgeName,
        badgeLevel: latestBadgeLevel,
        badgeCheck: badgeChecker
      });
      this.setUserPic(userId);
    }
  },
  userTagline: function (badgeCount) {
    if (badgeCount < 1) {
      return 'Beginner Mapper';
    }
    if (badgeCount >= 1 && badgeCount < 8) {
      return 'Novice Mapper';
    }
    if (badgeCount >= 8 && badgeCount < 16) {
      return 'Pro Mapper';
    }
    if (badgeCount >= 16 && badgeCount < 30) {
      return 'Super Mapper';
    }
    return 'Map Addict';
  },
  render: function () {
    var twittermsg = '';
    if (this.state.badgeCheck) {
      var badgeName = this.state.userBadge;
      var badgeLevel = this.state.badgeLevel;
      twittermsg = this.state.userNameCap + ' earned the ' + badgeName + ' badge (lv.' + badgeLevel + ') on #MissingMaps';
    } else {
      twittermsg = this.state.userNameCap + ' contributed to #MissingMaps! Checkout their progress at ';
    }
    let message = 'https://twitter.com/intent/tweet?text=' + encodeURIComponent(twittermsg + ' ' + window.location);
    var osmlink = 'http://www.openstreetmap.org/user/' + this.state.userName;
    return (

              ← Back to search

                  Share

              {this.state.userName}

{this.state.userTagline}   |   OSM Profile

              <IndexLink to={`/${this.state.userId}`} activeClassName="activeLink">Overview</IndexLink>
              <Link to={`/${this.state.userId}/badges`} activeClassName="activeLink">Badges</Link>

    );
  }
});
<|endoftext|>


<commit_before>export default (petition, me) => ({
  initialValues: {
    // For TrustSupportForm
    user: me || {},
    // For TrustPublishForm
    owner: petition.owner || {}
  },
  mobileConfirmed: (me && me.mobile_trusted) || false,
  petition: petition || {},
  me: me || {},
  trustedFields: {
    // For TrustSupportForm
    'user.email': (me && me.email_trusted) || false,
    'user.mobile': (me && me.mobile_trusted) || false,
    // For TrustPublishForm
    'owner.email': (me && me.email_trusted) || false,
    'owner.mobile': (me && me.mobile_trusted) || false
  }
});
<commit_msg>Fix trust logic<commit_after>import petitionOwned from 'selectors/petitionOwned';

export default (petition, me) => ({
  initialValues: {
    // For TrustSupportForm
    user: me || {},
    // For TrustPublishForm
    owner: petitionOwned(petition) ? petition.owner : me || {}
  },
  mobileConfirmed: (me && me.mobile_trusted) || false,
  petition: petition || {},
  me: me || {},
  trustedFields: {
    // For TrustSupportForm
    'user.email': (me && me.email_trusted) || false,
    'user.mobile': (me && me.mobile_trusted) || false,
    // For TrustPublishForm
    'owner.email': (me && me.email_trusted) || false,
    'owner.mobile': (me && me.mobile_trusted) || false
  }
});
<|endoftext|>


<commit_before>/*
 This file is a part of libertysoil.org website
 Copyright (C) 2016  Loki Education (Social Enterprise)

 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/>.
 */
import React, { PropTypes } from 'react';
import ga from 'react-google-analytics';

import Button from './button';
import TagIcon from './tag-icon';
import MoreButton from './more-button';
import { TAG_HASHTAG, TAG_LOCATION, TAG_SCHOOL } from '../consts/tags';
import ClickOutsideComponentDecorator from '../decorators/ClickOutsideComponentDecorator';
import AddTagModal from './add-tag-modal';

@ClickOutsideComponentDecorator
export default class CreatePost extends React.Component {
  static displayName = 'CreatePost';
  static propTypes = {
    actions: PropTypes.shape({
      resetCreatePostForm: PropTypes.func,
      updateCreatePostForm: PropTypes.func
    }),
    allSchools: PropTypes.arrayOf(PropTypes.shape({
      name: PropTypes.string
    })),
    defaultText: PropTypes.string,
    geotags: PropTypes.arrayOf(PropTypes.shape({
      id: PropTypes.string,
      name: PropTypes.string
    })),
    hashtags: PropTypes.arrayOf(PropTypes.shape({
      name: PropTypes.string
    })),
    schools: PropTypes.arrayOf(PropTypes.shape({
      name: PropTypes.string
    })),
    triggers: PropTypes.shape({
      createPost: PropTypes.func.isRequired,
      loadUserRecentTags: PropTypes.func.isRequired,
      checkSchoolExists: PropTypes.func.isRequired,
      checkGeotagExists: PropTypes.func.isRequired
    }),
    userRecentTags: PropTypes.shape({
      geotags: PropTypes.array.isRequired,
      schools: PropTypes.array.isRequired,
      hashtags: PropTypes.array.isRequired
    }).isRequired
  };

  constructor(props) {
    super(props);

    this.state = {
      isSubmitting: false,
      expanded: false,
      addTagModalType: null
    };
  }

  _stopPropagation = (e) => {
    if (e) {
      e.stopPropagation();
      e.nativeEvent.stopImmediatePropagation();
    }
  };

  onClickOutside = () => {
    const form = this.form;

    if (!form.text.value.trim().length) {
      this.setState({
        expanded: false
      });
    }
  };

  _handleSubmit = async (event) => {
    event.preventDefault();
    if (this.state.isSubmitting) {
      return;
    }

    const form = this.form;

    if (!form.text.value.trim().length) {
      return;
    }

    this.setState({ isSubmitting: true });
    const data = {
      text: form.text.value,
      hashtags: this.props.hashtags.map(hashtag => hashtag.name),
      schools: this.props.schools.map(school => school.name),
      geotags: this.props.geotags.map(geotag => geotag.id),
      minor_update: form.minor_update.checked
    };

    this.props.actions.resetCreatePostForm();

    await this.props.triggers.createPost('short_text', data);
    ga('send', 'event', 'Post', 'Done', data.hashtags.join(','));
    await this.props.triggers.loadUserRecentTags();

    form.text.value = '';
    this._addTagModal.reset();
    this.setState({ isSubmitting: false });
  };

  _handleFocus = () => {
    this.setState({
      expanded: true
    });
  };

  _handleClickOnMore = () => {
    this.setState({
      expanded: !this.state.expanded
    });
  };

  _handleKeydown = (e) => {
    const ENTER = 13;

    if (e.ctrlKey || e.metaKey) {
      if (e.keyCode === ENTER) {
        const submit = new Event('submit');
        this.form.dispatchEvent(submit);
      }
    }
  };

  _showAddHashtagModal = (e) => {
    this._stopPropagation(e);

    this.setState({
      addTagModalType: TAG_HASHTAG
    });
  };

  _showAddSchoolModal = (e) => {
    this._stopPropagation(e);

    this.setState({
      addTagModalType: TAG_SCHOOL
    });
  };

  _showAddGeotagModal = (e) => {
    this._stopPropagation(e);

    this.setState({
      addTagModalType: TAG_LOCATION
    });
  };

  _closeAddTagModal = (e) => {
    this._stopPropagation(e);

    this.setState({
      addTagModalType: null
    });
  };

  _changeAddTagModal = (newType) => {
    this.setState({
      addTagModalType: newType
    });
  };

  /**
   * @param {Object} tags - {hashtags: [], schools: [], geotags: [], ...}
   */
  _addTags = (tags) => {
    this.props.actions.updateCreatePostForm(tags);

    this._closeAddTagModal();
  };

  render() {
    const {
      addTagModalType,
      expanded
    } = this.state;

    return (

        <form ref={c => this.form = c} onKeyDown={this._handleKeydown} onSubmit={this._handleSubmit}>

                  {!expanded &&

                  }
                  <textarea
                    className="input input-block create_post__text_input"
                    defaultValue={this.props.defaultText}
                    name="text"
                    placeholder="Make a contribution to education change"
                    rows={(this.state.expanded) ? 10 : 1}
                    onFocus={this._handleFocus}
                  />

                <MoreButton expanded={expanded} onClick={this._handleClickOnMore} />
                {expanded &&

                    <TagIcon className="create_post__tag_button" type={TAG_SCHOOL} onClick={this._showAddSchoolModal} />
                    <TagIcon className="create_post__tag_button" type={TAG_LOCATION} onClick={this._showAddGeotagModal}  />
                    {/*<TagIcon className="create_post__tag_button" type={TAG_EVENT} />*/}
                    {/*<TagIcon className="create_post__tag_button" type={TAG_MENTION} />*/}
                    <TagIcon className="create_post__tag_button" type={TAG_HASHTAG} onClick={this._showAddHashtagModal} />

                }

            {expanded &&

                  <Button
                    className="button button-red"
                    waiting={this.state.isSubmitting}
                    title="Publish"
                    type="submit"
                  />

                  {/*<button className="button button-wide button-transparent" type="button">Go full screen</button>*/}

                    Minor update (?)
                    <input name="minor_update" type="checkbox" />

            }

        </form>
        <AddTagModal
          allSchools={this.props.allSchools}
          geotags={this.props.geotags}
          hashtags={this.props.hashtags}
          ref={(c) => this._addTagModal = c}
          schools={this.props.schools}
          triggers={this.props.triggers}
          type={addTagModalType}
          userRecentTags={this.props.userRecentTags}
          onClose={this._closeAddTagModal}
          onSave={this._addTags}
          onTypeChange={this._changeAddTagModal}
        />

    );
  }
}
<commit_msg>Disable Publish button if text field has 0 non-space length<commit_after>/*
 This file is a part of libertysoil.org website
 Copyright (C) 2016  Loki Education (Social Enterprise)

 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/>.
 */
import React, { PropTypes } from 'react';
import ga from 'react-google-analytics';

import Button from './button';
import TagIcon from './tag-icon';
import MoreButton from './more-button';
import { TAG_HASHTAG, TAG_LOCATION, TAG_SCHOOL } from '../consts/tags';
import ClickOutsideComponentDecorator from '../decorators/ClickOutsideComponentDecorator';
import AddTagModal from './add-tag-modal';

@ClickOutsideComponentDecorator
export default class CreatePost extends React.Component {
  static displayName = 'CreatePost';
  static propTypes = {
    actions: PropTypes.shape({
      resetCreatePostForm: PropTypes.func,
      updateCreatePostForm: PropTypes.func
    }),
    allSchools: PropTypes.arrayOf(PropTypes.shape({
      name: PropTypes.string
    })),
    defaultText: PropTypes.string,
    geotags: PropTypes.arrayOf(PropTypes.shape({
      id: PropTypes.string,
      name: PropTypes.string
    })),
    hashtags: PropTypes.arrayOf(PropTypes.shape({
      name: PropTypes.string
    })),
    schools: PropTypes.arrayOf(PropTypes.shape({
      name: PropTypes.string
    })),
    triggers: PropTypes.shape({
      createPost: PropTypes.func.isRequired,
      loadUserRecentTags: PropTypes.func.isRequired,
      checkSchoolExists: PropTypes.func.isRequired,
      checkGeotagExists: PropTypes.func.isRequired
    }),
    userRecentTags: PropTypes.shape({
      geotags: PropTypes.array.isRequired,
      schools: PropTypes.array.isRequired,
      hashtags: PropTypes.array.isRequired
    }).isRequired
  };

  constructor(props) {
    super(props);

    this.state = {
      isSubmitting: false,
      hasText: false,
      expanded: false,
      addTagModalType: null
    };
  }

  _stopPropagation = (e) => {
    if (e) {
      e.stopPropagation();
      e.nativeEvent.stopImmediatePropagation();
    }
  };

  onClickOutside = () => {
    const form = this.form;

    if (!form.text.value.trim().length) {
      this.setState({
        expanded: false
      });
    }
  };

  _handleSubmit = async (event) => {
    event.preventDefault();
    if (this.state.isSubmitting) {
      return;
    }

    if (!this.state.hasText) {
      return;
    }

    this.setState({ isSubmitting: true });

    const form = this.form;
    const data = {
      text: form.text.value,
      hashtags: this.props.hashtags.map(hashtag => hashtag.name),
      schools: this.props.schools.map(school => school.name),
      geotags: this.props.geotags.map(geotag => geotag.id),
      minor_update: form.minor_update.checked
    };

    this.props.actions.resetCreatePostForm();

    await this.props.triggers.createPost('short_text', data);
    ga('send', 'event', 'Post', 'Done', data.hashtags.join(','));
    await this.props.triggers.loadUserRecentTags();

    form.text.value = '';
    this._addTagModal.reset();
    this.setState({ isSubmitting: false, hasText: false });
  };

  _handleTextChange = (event) => {
    let hasText = false;
    if (event.target.value.trim()) {
      hasText = true;
    }

    this.setState({ hasText });
  };

  _handleFocus = () => {
    this.setState({
      expanded: true
    });
  };

  _handleClickOnMore = () => {
    this.setState({
      expanded: !this.state.expanded
    });
  };

  _handleKeydown = (e) => {
    const ENTER = 13;

    if (e.ctrlKey || e.metaKey) {
      if (e.keyCode === ENTER) {
        const submit = new Event('submit');
        this.form.dispatchEvent(submit);
      }
    }
  };

  _showAddHashtagModal = (e) => {
    this._stopPropagation(e);

    this.setState({
      addTagModalType: TAG_HASHTAG
    });
  };

  _showAddSchoolModal = (e) => {
    this._stopPropagation(e);

    this.setState({
      addTagModalType: TAG_SCHOOL
    });
  };

  _showAddGeotagModal = (e) => {
    this._stopPropagation(e);

    this.setState({
      addTagModalType: TAG_LOCATION
    });
  };

  _closeAddTagModal = (e) => {
    this._stopPropagation(e);

    this.setState({
      addTagModalType: null
    });
  };

  _changeAddTagModal = (newType) => {
    this.setState({
      addTagModalType: newType
    });
  };

  /**
   * @param {Object} tags - {hashtags: [], schools: [], geotags: [], ...}
   */
  _addTags = (tags) => {
    this.props.actions.updateCreatePostForm(tags);

    this._closeAddTagModal();
  };

  render() {
    const {
      addTagModalType,
      expanded
    } = this.state;

    return (

        <form ref={c => this.form = c} onKeyDown={this._handleKeydown} onSubmit={this._handleSubmit}>

                  {!expanded &&

                  }
                  <textarea
                    className="input input-block create_post__text_input"
                    defaultValue={this.props.defaultText}
                    name="text"
                    placeholder="Make a contribution to education change"
                    rows={(this.state.expanded) ? 10 : 1}
                    onChange={this._handleTextChange}
                    onFocus={this._handleFocus}
                  />

                <MoreButton expanded={expanded} onClick={this._handleClickOnMore} />
                {expanded &&

                    <TagIcon className="create_post__tag_button" type={TAG_SCHOOL} onClick={this._showAddSchoolModal} />
                    <TagIcon className="create_post__tag_button" type={TAG_LOCATION} onClick={this._showAddGeotagModal}  />
                    {/*<TagIcon className="create_post__tag_button" type={TAG_EVENT} />*/}
                    {/*<TagIcon className="create_post__tag_button" type={TAG_MENTION} />*/}
                    <TagIcon className="create_post__tag_button" type={TAG_HASHTAG} onClick={this._showAddHashtagModal} />

                }

            {expanded &&

                  <Button
                    className="button button-red"
                    disabled={!this.state.hasText}
                    title="Publish"
                    type="submit"
                    waiting={this.state.isSubmitting}
                  />

                  {/*<button className="button button-wide button-transparent" type="button">Go full screen</button>*/}

                    Minor update (?)
                    <input name="minor_update" type="checkbox" />

            }

        </form>
        <AddTagModal
          allSchools={this.props.allSchools}
          geotags={this.props.geotags}
          hashtags={this.props.hashtags}
          ref={(c) => this._addTagModal = c}
          schools={this.props.schools}
          triggers={this.props.triggers}
          type={addTagModalType}
          userRecentTags={this.props.userRecentTags}
          onClose={this._closeAddTagModal}
          onSave={this._addTags}
          onTypeChange={this._changeAddTagModal}
        />

    );
  }
}
<|endoftext|>


<commit_before>import path from 'path';
import React, {Fragment} from 'react';
import PropTypes from 'prop-types';
import cx from 'classnames';

import {EnableableOperationPropType} from '../prop-types';
import Tooltip from '../atom/tooltip';
import Commands, {Command} from '../atom/commands';
import AtomTextEditor from '../atom/atom-text-editor';
import {getDataFromGithubUrl} from './issueish-link';
import AggregatedReviewsContainer from '../containers/aggregated-reviews-container';
import EmojiReactionsController from '../controllers/emoji-reactions-controller';
import GithubDotcomMarkdown from './github-dotcom-markdown';
import ErrorView from './error-view';
import PatchPreviewView from './patch-preview-view';
import CheckoutButton from './checkout-button';
import Timeago from './timeago';
import Octicon from '../atom/octicon';
import RefHolder from '../models/ref-holder';
import ResolveReviewThreadMutation from '../mutations/resolve-review-thread';
import UnresolveReviewThreadMutation from '../mutations/unresolve-review-thread';

export default class ReviewsView extends React.Component {
  static propTypes = {
    // Relay results
    relay: PropTypes.shape({
      environment: PropTypes.object.isRequired,
    }).isRequired,
    repository: PropTypes.object.isRequired,
    pullRequest: PropTypes.object.isRequired,

    // Package models
    multiFilePatch: PropTypes.object.isRequired,
    contextLines: PropTypes.number.isRequired,
    checkoutOp: EnableableOperationPropType.isRequired,

    // for the dotcom link in the empty state
    number: PropTypes.number.isRequired,
    repo: PropTypes.string.isRequired,
    owner: PropTypes.string.isRequired,
    workdir: PropTypes.string.isRequired,

    // Atom environment
    workspace: PropTypes.object.isRequired,
    config: PropTypes.object.isRequired,
    commands: PropTypes.object.isRequired,
    tooltips: PropTypes.object.isRequired,

    // Action methods
    openFile: PropTypes.func.isRequired,
    openDiff: PropTypes.func.isRequired,
    openPR: PropTypes.func.isRequired,
    moreContext: PropTypes.func.isRequired,
    lessContext: PropTypes.func.isRequired,
    openIssueish: PropTypes.func.isRequired,
    addSingleComment: PropTypes.func.isRequired,
  }

  constructor(props) {
    super(props);

    this.rootHolder = new RefHolder();
    this.replyHolders = new Map();
    this.state = {
      isRefreshing: false,
      resolvedThreadsMarkedVisible: new Set(),
    };
  }

  render() {
    return (

        {this.renderCommands()}

        <AggregatedReviewsContainer pullRequest={this.props.pullRequest}>
          {({errors, summaries, commentThreads, refetch}) => {
            if (errors && errors.length > 0) {
              return this.renderErrors(errors);
            }

            return (
              <Fragment>
                {this.renderHeader(refetch)}

                  {this.renderReviewSummaries(summaries)}
                  {this.renderReviewCommentThreads(commentThreads)}

              </Fragment>
            );
          }}
        </AggregatedReviewsContainer>

    );
  }

  renderErrors(errors) {
    return errors.map((error, i) => (
      <ErrorView
        key={`error-${i}`}
        title="Pagination error"
        descriptions={[error.stack || error.toString()]}
      />
    ));
  }

  renderCommands() {
    return (
      <Commands registry={this.props.commands} target={this.rootHolder}>
        <Command command="github:more-context" callback={this.props.moreContext} />
        <Command command="github:less-context" callback={this.props.lessContext} />
      </Commands>
    );
  }

  renderHeader(refetch) {
    const refresh = () => {
      if (this.state.isRefreshing) {
        return;
      }
      this.setState({isRefreshing: true});
      refetch(() => {
        this.setState({isRefreshing: false});
      });
    };
    return (

          Reviews for

            {this.props.owner}/{this.props.repo}#{this.props.number}

        <button
          className={cx(
            'github-Reviews-headerButton github-Reviews-clickable icon icon-repo-sync',
            {refreshing: this.state.isRefreshing},
          )}
          onClick={refresh}
        />
        <CheckoutButton
          checkoutOp={this.props.checkoutOp}
          classNamePrefix="github-Reviews-checkoutButton--"
          classNames={['github-Reviews-headerButton']}
        />

    );
  }

  renderEmptyState() {
    const {number, repo, owner} = this.props;
    // todo: make this open the review flow in Atom instead of dotcom
    const pullRequestURL = `https://www.github.com/${owner}/${repo}/pull/${number}/`;
    return (

          This pull request has no reviews

            Start a review

    );
  }

  renderReviewSummaries = summaries => {
    if (summaries.length === 0) {
      return this.renderEmptyState();
    }

    return (

          Summaries

          {summaries.map(this.renderReviewSummary)}

    );
  }

  renderReviewSummary = review => {
    const reviewTypes = type => {
      return {
        APPROVED: {icon: 'icon-check', copy: 'approved these changes'},
        COMMENTED: {icon: 'icon-comment', copy: 'commented'},
        CHANGES_REQUESTED: {icon: 'icon-alert', copy: 'requested changes'},
      }[type] || {icon: '', copy: ''};
    };

    const {icon, copy} = reviewTypes(review.state);

    // filter non actionable empty summary comments from this view
    if (copy === 'commented' && review.bodyHTML === '') {
      return null;
    }

    const reviewAuthor = review.author ? review.author.login : '';
    return (

          <span className={`github-ReviewSummary-icon icon ${icon}`} />

          <a className="github-ReviewSummary-username" href={`https://github.com/${reviewAuthor}`}>{reviewAuthor}
          {copy}
          <Timeago className="github-ReviewSummary-timeAgo" time={review.submittedAt} displayStyle="short" />

          <GithubDotcomMarkdown
            html={review.bodyHTML}
            switchToIssueish={this.props.openIssueish}
            openIssueishLinkInNewTab={this.openIssueishLinkInNewTab}
          />
          <EmojiReactionsController reactable={review} tooltips={this.props.tooltips} />

    );
  }

  renderReviewCommentThreads = commentThreads => {
    if (commentThreads.length === 0) {
      return null;
    }

    const resolvedThreads = commentThreads.filter(pair => pair.thread.isResolved).length;

    return (

          Comments

              Resolved
              {' '}{resolvedThreads}{' '}
              of
              {' '}{commentThreads.length}

            <progress className="github-Reviews-progessBar" value={resolvedThreads} max={commentThreads.length} />

          {commentThreads.map(this.renderReviewCommentThread)}

    );
  }

  renderReviewCommentThread = commentThread => {
    const {comments, thread} = commentThread;
    const rootComment = comments[0];
    if (!rootComment) {
      return null;
    }
    const {dir, base} = path.parse(rootComment.path);
    // TODO: fixme
    const lineNumber = rootComment.position;

    const refJumpToFileButton = new RefHolder();
    const jumpToFileDisabledLabel = 'Checkout this pull request to enable Jump To File.';

    const threadVisible = !thread.isResolved || this.state.resolvedThreadsMarkedVisible.has(thread.id);
    const positionText = lineNumber ? lineNumber : 'outdated';
    const navButtonClasses = cx('github-Review-navButton icon icon-code', {outdated: !lineNumber});

    return (
      <details className="github-Review" key={`review-comment-${rootComment.id}`}>

          {dir && {dir}}
          {dir ? '/' : ''}{base}
          {positionText}

          <Timeago className="github-Review-referenceTimeAgo" time={rootComment.createdAt} displayStyle="short" />

          <button className={navButtonClasses}
            data-path={rootComment.path} data-line={lineNumber} data-diff={rootComment.diffHunk}
            onClick={this.openFile} disabled={this.props.checkoutOp.isEnabled()}
            ref={refJumpToFileButton.setter}>
            Jump To File
          </button>
          <button className={navButtonClasses}
            data-path={rootComment.path} data-line={lineNumber}
            onClick={this.openDiff}>
            Open Diff
          </button>
          {this.props.checkoutOp.isEnabled() &&
            <Tooltip
              manager={this.props.tooltips}
              target={refJumpToFileButton}
              title={jumpToFileDisabledLabel}
              showDelay={200}
            />
          }

        {rootComment.position !== null && (
          <PatchPreviewView
            multiFilePatch={this.props.multiFilePatch}
            fileName={rootComment.path}
            diffRow={rootComment.position}
            maxRowCount={this.props.contextLines}
            config={this.props.config}
          />
        )}

        {thread.isResolved && this.renderResolvedHeader(thread, threadVisible)}
        {threadVisible && this.renderThread({thread, comments})}

    );
  }

  renderResolvedHeader = (thread, visible) => {
    return (
      <Fragment>

 Marked as resolved by @{thread.resolvedBy.login}

        {
          visible ?
            <button onClick={() => this.collapseThread(thread.id)}> Hide conversation </button> :
            <button onClick={() => this.uncollapseThread(thread.id)}> Show conversation </button>
        }
      </Fragment>
    );
  }

  renderThread = ({thread, comments}) => {
    let replyHolder = this.replyHolders.get(thread.id);
    if (!replyHolder) {
      replyHolder = new RefHolder();
      this.replyHolders.set(thread.id, replyHolder);
    }
    const lastComment = comments[comments.length - 1];

    return (
      <Fragment>

          {comments.map(this.renderComment)}

            <AtomTextEditor
              placeholderText="Reply..."
              lineNumberGutterVisible={false}
              softWrapped={true}
              autoHeight={true}
              refModel={replyHolder}
            />

          <button
            className="github-Review-replyButton btn"
            title="Add your comment"
            onClick={() => this.submitReply(replyHolder, thread, lastComment)}>
            Comment
          </button>
          {this.renderResolveButton(thread)}

      </Fragment>
    );
  }

  renderResolveButton = thread => {
    if (thread.isResolved) {
      return (
        <button
          className="github-Review-resolveButton btn btn-primary icon icon-check"
          title="Unresolve conversation"
          onClick={() => this.unresolveReviewThread(thread)}>
          Unresolve conversation
        </button>
      );
    } else {
      return (
        <button
          className="github-Review-resolveButton btn btn-primary icon icon-check"
          title="Resolve conversation"
          onClick={() => this.resolveReviewThread(thread)}>
          Resolve conversation
        </button>
      );
    }
  }

  renderComment = comment => {
    if (comment.isMinimized) {
      return (

          <Octicon icon={'fold'} className="github-Review-icon" />
          This comment was hidden

      );
    }

    return (

          <a className="github-Review-username" href={`https://github.com/${comment.author.login}`}>{comment.author.login}

            <Timeago displayStyle="long" time={comment.createdAt} />

          <GithubDotcomMarkdown
            html={comment.bodyHTML}
            switchToIssueish={this.props.openIssueish}
            openIssueishLinkInNewTab={this.openIssueishLinkInNewTab}
          />
          <EmojiReactionsController reactable={comment} tooltips={this.props.tooltips} />

    );
  }

  collapseThread = threadId => {
    const resolvedThreadsMarkedVisible = this.state.resolvedThreadsMarkedVisible;
    resolvedThreadsMarkedVisible.delete(threadId);
    this.setState({resolvedThreadsMarkedVisible});
  }

  uncollapseThread = threadId => {
    const resolvedThreadsMarkedVisible = this.state.resolvedThreadsMarkedVisible;
    resolvedThreadsMarkedVisible.add(threadId);
    this.setState({resolvedThreadsMarkedVisible});
  }

  openFile = evt => {
    if (!this.props.checkoutOp.isEnabled()) {
      const target = evt.currentTarget;
      this.props.openFile(target.dataset.path, target.dataset.diff);
    }
  }

  openDiff = evt => {
    const target = evt.currentTarget;
    this.props.openDiff(target.dataset.path, parseInt(target.dataset.line, 10));
  }

  openIssueishLinkInNewTab = evt => {
    const {repoOwner, repoName, issueishNumber} = getDataFromGithubUrl(evt.target.dataset.url);
    return this.props.openIssueish(repoOwner, repoName, issueishNumber);
  }

  resolveReviewThread = thread => {
    if (thread.viewerCanResolve) {
      this.collapseThread(thread.id);
      const onCompleted = () => this.collapseThread(thread.id);
      const onError = () => this.uncollapseThread(thread.id);
      ResolveReviewThreadMutation(thread.id, this.props.relay.environment, onCompleted, onError);
    }
  }

  unresolveReviewThread = thread => {
    if (thread.viewerCanUnresolve) {
      UnresolveReviewThreadMutation(thread.id, this.props.relay.environment);
    }
  }

  submitReply(replyHolder, thread, lastComment) {
    const body = replyHolder.map(editor => editor.getText()).getOr('');
    return this.props.addSingleComment(body, thread.id, lastComment.id);
  }
}
<commit_msg>Discard pending reviews<commit_after>import path from 'path';
import React, {Fragment} from 'react';
import PropTypes from 'prop-types';
import cx from 'classnames';

import {EnableableOperationPropType} from '../prop-types';
import Tooltip from '../atom/tooltip';
import Commands, {Command} from '../atom/commands';
import AtomTextEditor from '../atom/atom-text-editor';
import {getDataFromGithubUrl} from './issueish-link';
import AggregatedReviewsContainer from '../containers/aggregated-reviews-container';
import EmojiReactionsController from '../controllers/emoji-reactions-controller';
import GithubDotcomMarkdown from './github-dotcom-markdown';
import ErrorView from './error-view';
import PatchPreviewView from './patch-preview-view';
import CheckoutButton from './checkout-button';
import Timeago from './timeago';
import Octicon from '../atom/octicon';
import RefHolder from '../models/ref-holder';
import ResolveReviewThreadMutation from '../mutations/resolve-review-thread';
import UnresolveReviewThreadMutation from '../mutations/unresolve-review-thread';

export default class ReviewsView extends React.Component {
  static propTypes = {
    // Relay results
    relay: PropTypes.shape({
      environment: PropTypes.object.isRequired,
    }).isRequired,
    repository: PropTypes.object.isRequired,
    pullRequest: PropTypes.object.isRequired,

    // Package models
    multiFilePatch: PropTypes.object.isRequired,
    contextLines: PropTypes.number.isRequired,
    checkoutOp: EnableableOperationPropType.isRequired,

    // for the dotcom link in the empty state
    number: PropTypes.number.isRequired,
    repo: PropTypes.string.isRequired,
    owner: PropTypes.string.isRequired,
    workdir: PropTypes.string.isRequired,

    // Atom environment
    workspace: PropTypes.object.isRequired,
    config: PropTypes.object.isRequired,
    commands: PropTypes.object.isRequired,
    tooltips: PropTypes.object.isRequired,

    // Action methods
    openFile: PropTypes.func.isRequired,
    openDiff: PropTypes.func.isRequired,
    openPR: PropTypes.func.isRequired,
    moreContext: PropTypes.func.isRequired,
    lessContext: PropTypes.func.isRequired,
    openIssueish: PropTypes.func.isRequired,
    addSingleComment: PropTypes.func.isRequired,
  }

  constructor(props) {
    super(props);

    this.rootHolder = new RefHolder();
    this.replyHolders = new Map();
    this.state = {
      isRefreshing: false,
      resolvedThreadsMarkedVisible: new Set(),
    };
  }

  render() {
    return (

        {this.renderCommands()}

        <AggregatedReviewsContainer pullRequest={this.props.pullRequest}>
          {({errors, summaries, commentThreads, refetch}) => {
            if (errors && errors.length > 0) {
              return this.renderErrors(errors);
            }

            return (
              <Fragment>
                {this.renderHeader(refetch)}

                  {this.renderReviewSummaries(summaries)}
                  {this.renderReviewCommentThreads(commentThreads)}

              </Fragment>
            );
          }}
        </AggregatedReviewsContainer>

    );
  }

  renderErrors(errors) {
    return errors.map((error, i) => (
      <ErrorView
        key={`error-${i}`}
        title="Pagination error"
        descriptions={[error.stack || error.toString()]}
      />
    ));
  }

  renderCommands() {
    return (
      <Commands registry={this.props.commands} target={this.rootHolder}>
        <Command command="github:more-context" callback={this.props.moreContext} />
        <Command command="github:less-context" callback={this.props.lessContext} />
      </Commands>
    );
  }

  renderHeader(refetch) {
    const refresh = () => {
      if (this.state.isRefreshing) {
        return;
      }
      this.setState({isRefreshing: true});
      refetch(() => {
        this.setState({isRefreshing: false});
      });
    };
    return (

          Reviews for

            {this.props.owner}/{this.props.repo}#{this.props.number}

        <button
          className={cx(
            'github-Reviews-headerButton github-Reviews-clickable icon icon-repo-sync',
            {refreshing: this.state.isRefreshing},
          )}
          onClick={refresh}
        />
        <CheckoutButton
          checkoutOp={this.props.checkoutOp}
          classNamePrefix="github-Reviews-checkoutButton--"
          classNames={['github-Reviews-headerButton']}
        />

    );
  }

  renderEmptyState() {
    const {number, repo, owner} = this.props;
    // todo: make this open the review flow in Atom instead of dotcom
    const pullRequestURL = `https://www.github.com/${owner}/${repo}/pull/${number}/`;
    return (

          This pull request has no reviews

            Start a review

    );
  }

  renderReviewSummaries = summaries => {
    if (summaries.length === 0) {
      return this.renderEmptyState();
    }

    return (

          Summaries

          {summaries.map(this.renderReviewSummary)}

    );
  }

  renderReviewSummary = review => {
    const reviewTypes = type => {
      return {
        APPROVED: {icon: 'icon-check', copy: 'approved these changes'},
        COMMENTED: {icon: 'icon-comment', copy: 'commented'},
        CHANGES_REQUESTED: {icon: 'icon-alert', copy: 'requested changes'},
      }[type] || {icon: '', copy: ''};
    };

    const {icon, copy} = reviewTypes(review.state);

    // filter non actionable empty summary comments from this view
    if (review.state === 'PENDING' || (review.state === 'COMMENTED' && review.bodyHTML === '')) {
      return null;
    }

    const reviewAuthor = review.author ? review.author.login : '';
    return (

          <span className={`github-ReviewSummary-icon icon ${icon}`} />

          <a className="github-ReviewSummary-username" href={`https://github.com/${reviewAuthor}`}>{reviewAuthor}
          {copy}
          <Timeago className="github-ReviewSummary-timeAgo" time={review.submittedAt} displayStyle="short" />

          <GithubDotcomMarkdown
            html={review.bodyHTML}
            switchToIssueish={this.props.openIssueish}
            openIssueishLinkInNewTab={this.openIssueishLinkInNewTab}
          />
          <EmojiReactionsController reactable={review} tooltips={this.props.tooltips} />

    );
  }

  renderReviewCommentThreads = commentThreads => {
    if (commentThreads.length === 0) {
      return null;
    }

    const resolvedThreads = commentThreads.filter(pair => pair.thread.isResolved).length;

    return (

          Comments

              Resolved
              {' '}{resolvedThreads}{' '}
              of
              {' '}{commentThreads.length}

            <progress className="github-Reviews-progessBar" value={resolvedThreads} max={commentThreads.length} />

          {commentThreads.map(this.renderReviewCommentThread)}

    );
  }

  renderReviewCommentThread = commentThread => {
    const {comments, thread} = commentThread;
    const rootComment = comments[0];
    if (!rootComment) {
      return null;
    }
    const {dir, base} = path.parse(rootComment.path);
    // TODO: fixme
    const lineNumber = rootComment.position;

    const refJumpToFileButton = new RefHolder();
    const jumpToFileDisabledLabel = 'Checkout this pull request to enable Jump To File.';

    const threadVisible = !thread.isResolved || this.state.resolvedThreadsMarkedVisible.has(thread.id);
    const positionText = lineNumber ? lineNumber : 'outdated';
    const navButtonClasses = cx('github-Review-navButton icon icon-code', {outdated: !lineNumber});

    return (
      <details className="github-Review" key={`review-comment-${rootComment.id}`}>

          {dir && {dir}}
          {dir ? '/' : ''}{base}
          {positionText}

          <Timeago className="github-Review-referenceTimeAgo" time={rootComment.createdAt} displayStyle="short" />

          <button className={navButtonClasses}
            data-path={rootComment.path} data-line={lineNumber} data-diff={rootComment.diffHunk}
            onClick={this.openFile} disabled={this.props.checkoutOp.isEnabled()}
            ref={refJumpToFileButton.setter}>
            Jump To File
          </button>
          <button className={navButtonClasses}
            data-path={rootComment.path} data-line={lineNumber}
            onClick={this.openDiff}>
            Open Diff
          </button>
          {this.props.checkoutOp.isEnabled() &&
            <Tooltip
              manager={this.props.tooltips}
              target={refJumpToFileButton}
              title={jumpToFileDisabledLabel}
              showDelay={200}
            />
          }

        {rootComment.position !== null && (
          <PatchPreviewView
            multiFilePatch={this.props.multiFilePatch}
            fileName={rootComment.path}
            diffRow={rootComment.position}
            maxRowCount={this.props.contextLines}
            config={this.props.config}
          />
        )}

        {thread.isResolved && this.renderResolvedHeader(thread, threadVisible)}
        {threadVisible && this.renderThread({thread, comments})}

    );
  }

  renderResolvedHeader = (thread, visible) => {
    return (
      <Fragment>

 Marked as resolved by @{thread.resolvedBy.login}

        {
          visible ?
            <button onClick={() => this.collapseThread(thread.id)}> Hide conversation </button> :
            <button onClick={() => this.uncollapseThread(thread.id)}> Show conversation </button>
        }
      </Fragment>
    );
  }

  renderThread = ({thread, comments}) => {
    let replyHolder = this.replyHolders.get(thread.id);
    if (!replyHolder) {
      replyHolder = new RefHolder();
      this.replyHolders.set(thread.id, replyHolder);
    }
    const lastComment = comments[comments.length - 1];

    return (
      <Fragment>

          {comments.map(this.renderComment)}

            <AtomTextEditor
              placeholderText="Reply..."
              lineNumberGutterVisible={false}
              softWrapped={true}
              autoHeight={true}
              refModel={replyHolder}
            />

          <button
            className="github-Review-replyButton btn"
            title="Add your comment"
            onClick={() => this.submitReply(replyHolder, thread, lastComment)}>
            Comment
          </button>
          {this.renderResolveButton(thread)}

      </Fragment>
    );
  }

  renderResolveButton = thread => {
    if (thread.isResolved) {
      return (
        <button
          className="github-Review-resolveButton btn btn-primary icon icon-check"
          title="Unresolve conversation"
          onClick={() => this.unresolveReviewThread(thread)}>
          Unresolve conversation
        </button>
      );
    } else {
      return (
        <button
          className="github-Review-resolveButton btn btn-primary icon icon-check"
          title="Resolve conversation"
          onClick={() => this.resolveReviewThread(thread)}>
          Resolve conversation
        </button>
      );
    }
  }

  renderComment = comment => {
    if (comment.isMinimized) {
      return (

          <Octicon icon={'fold'} className="github-Review-icon" />
          This comment was hidden

      );
    }

    return (

          <a className="github-Review-username" href={`https://github.com/${comment.author.login}`}>{comment.author.login}

            <Timeago displayStyle="long" time={comment.createdAt} />

          <GithubDotcomMarkdown
            html={comment.bodyHTML}
            switchToIssueish={this.props.openIssueish}
            openIssueishLinkInNewTab={this.openIssueishLinkInNewTab}
          />
          <EmojiReactionsController reactable={comment} tooltips={this.props.tooltips} />

    );
  }

  collapseThread = threadId => {
    const resolvedThreadsMarkedVisible = this.state.resolvedThreadsMarkedVisible;
    resolvedThreadsMarkedVisible.delete(threadId);
    this.setState({resolvedThreadsMarkedVisible});
  }

  uncollapseThread = threadId => {
    const resolvedThreadsMarkedVisible = this.state.resolvedThreadsMarkedVisible;
    resolvedThreadsMarkedVisible.add(threadId);
    this.setState({resolvedThreadsMarkedVisible});
  }

  openFile = evt => {
    if (!this.props.checkoutOp.isEnabled()) {
      const target = evt.currentTarget;
      this.props.openFile(target.dataset.path, target.dataset.diff);
    }
  }

  openDiff = evt => {
    const target = evt.currentTarget;
    this.props.openDiff(target.dataset.path, parseInt(target.dataset.line, 10));
  }

  openIssueishLinkInNewTab = evt => {
    const {repoOwner, repoName, issueishNumber} = getDataFromGithubUrl(evt.target.dataset.url);
    return this.props.openIssueish(repoOwner, repoName, issueishNumber);
  }

  resolveReviewThread = thread => {
    if (thread.viewerCanResolve) {
      this.collapseThread(thread.id);
      const onCompleted = () => this.collapseThread(thread.id);
      const onError = () => this.uncollapseThread(thread.id);
      ResolveReviewThreadMutation(thread.id, this.props.relay.environment, onCompleted, onError);
    }
  }

  unresolveReviewThread = thread => {
    if (thread.viewerCanUnresolve) {
      UnresolveReviewThreadMutation(thread.id, this.props.relay.environment);
    }
  }

  submitReply(replyHolder, thread, lastComment) {
    const body = replyHolder.map(editor => editor.getText()).getOr('');
    return this.props.addSingleComment(body, thread.id, lastComment.id);
  }
}
<|endoftext|>


<commit_before>/*
 * React.js Starter Kit
 * Copyright (c) 2014 Konstantin Tarkus (@koistya), KriaSoft LLC.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE.txt file in the root directory of this source tree.
 */

'use strict';

var React = require('react');
var PageActions = require('../../actions/PageActions');
var App = require('../layout/App');

var AboutPage = React.createClass({

  statics: {
    layout: App
  },

  componentWillMount() {
    PageActions.set({title: 'About'});
  },

  render() {
    return (
      /* jshint ignore:start */

About

          Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean consequat tortor fermentum mi
          fermentum dignissim. Nullam vel ipsum ut ligula elementum lobortis. Maecenas aliquam, massa laoreet
          lacinia pretium, nisi urna venenatis tortor, nec imperdiet tellus libero efficitur metus. Fusce
          semper posuere ligula, et facilisis metus bibendum interdum. Mauris at mauris sit amet sem pharetra
          commodo a eu leo. Nam at est non risus cursus maximus. Nam feugiat augue libero, id consectetur
          tortor bibendum non. Quisque nec fringilla lorem. Nullam efficitur vulputate mauris, nec maximus leo
          dignissim id.

      /* jshint ignore:end */
    );
  }

});

module.exports = AboutPage;
<commit_msg>About text<commit_after>/*
 * React.js Starter Kit
 * Copyright (c) 2014 Konstantin Tarkus (@koistya), KriaSoft LLC.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE.txt file in the root directory of this source tree.
 */

'use strict';

var React = require('react');
var PageActions = require('../../actions/PageActions');
var App = require('../layout/App');

var AboutPage = React.createClass({

  statics: {
    layout: App
  },

  componentWillMount() {
    PageActions.set({title: 'About'});
  },

  render() {
    return (
      /* jshint ignore:start */

About

          Welcome in simple web page displaying current wind status for Smeťák Kite Spot.
          Smeťák is the landkiting kite spot in Prague. You can find more info about the spot
          on http://www.smetak.banda.cz/.

          I have created this page because I am a professional programmer, I like kiting
          and I needed some simple, but for some people useful project to try new, fancy
          technologies like ReactJS, Flux and others.

          If you are interested in how it is developed you can find source code
          on https://github.com/hanysek/smetak-wind-status-app.

Wind Status

          We use TSK Praha
           meteo sensors to get actual wind status and wind direction. We use meteo sensor located in Uhříněves which
          is closed to Smeťák Kite Spot. It means that wind is mostly a little bit stronger on spot:)

Credits

          TSK
          Praha Meteo information provider for Prague area.

          React /
          Flux  Starter Kit Thanks for good starting point for isomorphic application.

          ESRI Weather
          font Weather font with wind barbs.

      /* jshint ignore:end */
    );
  }

});

module.exports = AboutPage;
<|endoftext|>


<commit_before>/*
 * transports.js: Set of all transports Winston knows about
 *
 * (C) 2010 Charlie Robbins
 * MIT LICENCE
 *
 */

Object.defineProperty(exports, 'Console', {
  configurable: true,
  enumerable: true,
  get: function () {
    return require('./transports/console')
  }
});
Object.defineProperty(exports, 'File', {
  configurable: true,
  enumerable: true,
  get: function () {
    return require('./transports/file');
  }
});
Object.defineProperty(exports, 'Http', {
  configurable: true,
  enumerable: true,
  get: function () {
    return require('./transports/http');
  }
});
Object.defineProperty(exports, 'Memory', {
  configurable: true,
  enumerable: true,
  get: function () {
    return require('./transports/memory');
  }
});
<commit_msg>Update transports.js<commit_after>/*
 * transports.js: Set of all transports Winston knows about
 *
 * (C) 2010 Charlie Robbins
 * MIT LICENCE
 *
 */

Object.defineProperty(exports, 'Console', {
  configurable: true,
  enumerable: true,
  get: function () {
    return require('./transports/console').Console;
  }
});
Object.defineProperty(exports, 'File', {
  configurable: true,
  enumerable: true,
  get: function () {
    return require('./transports/file').File;
  }
});
Object.defineProperty(exports, 'Http', {
  configurable: true,
  enumerable: true,
  get: function () {
    return require('./transports/http').Http;
  }
});
Object.defineProperty(exports, 'Memory', {
  configurable: true,
  enumerable: true,
  get: function () {
    return require('./transports/memory').Memory;
  }
});
<|endoftext|>