<commit_before>
var amqp = require('amqp');
var fs   = require('fs');
var logger = require('./logger');

var rabbitqueue;
var exchangeName;
var queueName;
var connExchange_;
var connQueue_;
var routing_;
var deliveryMode;
exports.exchangeMapping = {}

//This method registers the hook and try to initialize the connection to rabbitmq server for later use.
exports.register = function () {

    logger.logdebug("About to connect and initialize queue object");
	this.init_rabbitmq_server();
	logger.logdebug("Finished initiating : " + exports.exchangeMapping[exchangeName + queueName]);
}

//Actual magic of publishing message to rabbit when email comes happen here.
exports.hook_queue = function(next, connection) {

    //Calling the get_data method and when it gets the data on callback, publish the message to queue with routing key.
    var stream = connection.transaction.message_stream.get_data(function(buffere) {
    	var exchangeData = exports.exchangeMapping[exchangeName + queueName]
    	logger.logdebug("Sending the data: "+ queueName+" Routing : "+ exchangeData + " exchange :"+connExchange_);
    	if (connExchange_ && routing_) {
    		//This is publish function of rabbitmq amqp library, currently direct queue is configured and routing is fixed.
    		//Needs to be changed.

    		connExchange_.publish(routing_, buffere,{deliveryMode: 2}, function(error){
				if (error) {
					//There was some error while sending the email to queue.
					logger.logdebug("queueFailure: #{JSON.stringify(error)}");
					exports.init_rabbitmq_server();
					return next();
				}
				else {
					//Queueing was successful, send ok as reply
					logger.logdebug( "queueSuccess");
					return next(OK,"Successfully Queued! in rabbitmq");
				}

			});

		}
		else {
			//Seems like connExchange is not defined , lets create one for next call
			exports.init_rabbitmq_server();
			return next();
		}

    });

}

//This initializes the connection to rabbitmq server, It reads values from rabbitmq.ini file in config directory.
exports.init_rabbitmq_server = function() {
	 var plugin = this;
    // this is called during init of rabbitmq

    //Read the config file rabbitmq
    var config     = plugin.config.get('rabbitmq.ini');
    //Just putting the defaults
    var rabbitmq_ip  = '127.0.0.1';
    var rabbitmq_port = '5672';
    var confirm = true;
    var durable = true;
    var autoDelete = false;
    var exchangeType = 'direct';

    //Getting the values from config file rabbitmq.ini
    if (config.rabbitmq) {
        rabbitmq_ip = config.rabbitmq.server_ip || '127.0.0.1';
        rabbitmq_port = config.rabbitmq.server_port || '5672';
        exchangeName = config.rabbitmq.exchangeName || 'emailMessages';
        exchangeType = config.rabbitmq.exchangeType || 'direct';
        confirm = config.rabbitmq.confirm === 'true'|| true;
        durable = config.rabbitmq.durable === 'true'|| true;
        autoDelete = config.rabbitmq.autoDelete == 'true' || false;
        deliveryMode = config.rabbitmq.deliveryMode || 2;
        queueName = config.rabbitmq.queueName || 'emails';
    }
    else {
    	//If config file is not available , lets get the default values
    	queueName = 'emails';
    	exchangeName = 'emailMessages';
    	deliveryMode = 2;
    	durable = true;
    }

    //Create connection to the rabbitmq server
    logger.logdebug("About to Create connection with server");
    rabbitqueue = amqp.createConnection({ host: rabbitmq_ip , port : rabbitmq_port });

    //Declaring listerner on error on connection.
    rabbitqueue.on('error',function(error) {
    	logger.logdebug("There was some error on the connection : "+error);
    });

	//Declaring listerner on close on connection.
    rabbitqueue.on('close',function(close) {
    	logger.logdebug(" Connection  is beingclosed : "+close);
    });

	/* Declaring the function to perform when connection is established and ready, function involves like:
	 *	1. Creating or connecting to Exchange.
	 *  2. Creating or connecting to Queue.
	 *  3. Binding the Exchange and Queue.
	 *  4. Saving some variables in global to be used while publishing message.
	 */

    rabbitqueue.on('ready', function () {
    	logger.logdebug("Connection is ready, will try making exchange");
    	// Now connection is ready will try to open exchange with config data.
    	rabbitqueue.exchange(exchangeName, {  type: exchangeType,  confirm: confirm,  durable: durable }, function(connExchange) {

    		logger.logdebug("connExchange with server "+connExchange + " autoDelete : "+autoDelete);

    		//Exchange is now open, will try to open queue.
  			return rabbitqueue.queue(queueName,{autoDelete: autoDelete,  durable:  durable  } , function(connQueue) {
  			logger.logdebug("connQueue with server "+connQueue);

  			//Creating the Routing key to bind the queue and exchange.
    		var key, routing;
    		routing = "" + queueName + "Routing";

    		//Will try to bing queue and exchange which was created above.
    		connQueue.bind(connExchange, routing);
    		key = exchangeName + queueName;

    		//Save the variables for publising later.
    		if (!exports.exchangeMapping[key]) {
      			exports.exchangeMapping[key] = [];
    		}
    		connExchange_ = connExchange;
    		connQueue_ = connQueue;
    		routing_ = routing;
    		exports.exchangeMapping[key].push({
      			exchange : connExchange_,
      			queue : connQueue_,
      			routing : routing_,
      			queueName : queueName
    		});
    		logger.logdebug("exchange: " + exchangeName + ", queue: " + queueName+"  exchange : "+connExchange_ +" queue : "+connQueue_ );
  			});
		});

	});

}
<commit_msg>Removed and replaced all the tabs to 4 space, coding standard changes of Haraka<commit_after>var amqp = require('amqp');
var fs   = require('fs');
var logger = require('./logger');

var rabbitqueue;
var exchangeName;
var queueName;
var connExchange_;
var connQueue_;
var routing_;
var deliveryMode;
exports.exchangeMapping = {}

//This method registers the hook and try to initialize the connection to rabbitmq server for later use.
exports.register = function () {
    logger.logdebug("About to connect and initialize queue object");
    this.init_rabbitmq_server();
    logger.logdebug("Finished initiating : " + exports.exchangeMapping[exchangeName + queueName]);
}

//Actual magic of publishing message to rabbit when email comes happen here.
exports.hook_queue = function(next, connection) {
    //Calling the get_data method and when it gets the data on callback, publish the message to queue with routing key.
    var stream = connection.transaction.message_stream.get_data(function(buffere) {
        var exchangeData = exports.exchangeMapping[exchangeName + queueName]
        logger.logdebug("Sending the data: "+ queueName+" Routing : "+ exchangeData + " exchange :"+connExchange_);
        if (connExchange_ && routing_) {
            //This is publish function of rabbitmq amqp library, currently direct queue is configured and routing is fixed.
            //Needs to be changed.
            connExchange_.publish(routing_, buffere,{deliveryMode: 2}, function(error){
                if (error) {
                    //There was some error while sending the email to queue.
                    logger.logdebug("queueFailure: #{JSON.stringify(error)}");
                    exports.init_rabbitmq_server();
                    return next();
                }
                else {
                    //Queueing was successful, send ok as reply
                    logger.logdebug( "queueSuccess");
                    return next(OK,"Successfully Queued! in rabbitmq");
                }

            });

        }
        else {
            //Seems like connExchange is not defined , lets create one for next call
            exports.init_rabbitmq_server();
            return next();
        }

    });

}

//This initializes the connection to rabbitmq server, It reads values from rabbitmq.ini file in config directory.
exports.init_rabbitmq_server = function() {
     var plugin = this;
    // this is called during init of rabbitmq

    //Read the config file rabbitmq
    var config     = plugin.config.get('rabbitmq.ini');
    //Just putting the defaults
    var rabbitmq_ip  = '127.0.0.1';
    var rabbitmq_port = '5672';
    var confirm = true;
    var durable = true;
    var autoDelete = false;
    var exchangeType = 'direct';

    //Getting the values from config file rabbitmq.ini
    if (config.rabbitmq) {
        rabbitmq_ip = config.rabbitmq.server_ip || '127.0.0.1';
        rabbitmq_port = config.rabbitmq.server_port || '5672';
        exchangeName = config.rabbitmq.exchangeName || 'emailMessages';
        exchangeType = config.rabbitmq.exchangeType || 'direct';
        confirm = config.rabbitmq.confirm === 'true'|| true;
        durable = config.rabbitmq.durable === 'true'|| true;
        autoDelete = config.rabbitmq.autoDelete == 'true' || false;
        deliveryMode = config.rabbitmq.deliveryMode || 2;
        queueName = config.rabbitmq.queueName || 'emails';
    }
    else {
        //If config file is not available , lets get the default values
        queueName = 'emails';
        exchangeName = 'emailMessages';
        deliveryMode = 2;
        durable = true;
    }

    //Create connection to the rabbitmq server
    logger.logdebug("About to Create connection with server");
    rabbitqueue = amqp.createConnection({ host: rabbitmq_ip , port : rabbitmq_port });

    //Declaring listerner on error on connection.
    rabbitqueue.on('error',function(error) {
        logger.logdebug("There was some error on the connection : "+error);
    });

    //Declaring listerner on close on connection.
    rabbitqueue.on('close',function(close) {
        logger.logdebug(" Connection  is beingclosed : "+close);
    });

    /* Declaring the function to perform when connection is established and ready, function involves like:
     *    1. Creating or connecting to Exchange.
     *  2. Creating or connecting to Queue.
     *  3. Binding the Exchange and Queue.
     *  4. Saving some variables in global to be used while publishing message.
     */

    rabbitqueue.on('ready', function () {
        logger.logdebug("Connection is ready, will try making exchange");
        // Now connection is ready will try to open exchange with config data.
        rabbitqueue.exchange(exchangeName, {  type: exchangeType,  confirm: confirm,  durable: durable }, function(connExchange) {

            logger.logdebug("connExchange with server "+connExchange + " autoDelete : "+autoDelete);

            //Exchange is now open, will try to open queue.
              return rabbitqueue.queue(queueName,{autoDelete: autoDelete,  durable:  durable  } , function(connQueue) {
              logger.logdebug("connQueue with server "+connQueue);

              //Creating the Routing key to bind the queue and exchange.
            var key, routing;
            routing = "" + queueName + "Routing";

            //Will try to bing queue and exchange which was created above.
            connQueue.bind(connExchange, routing);
            key = exchangeName + queueName;

            //Save the variables for publising later.
            if (!exports.exchangeMapping[key]) {
                  exports.exchangeMapping[key] = [];
            }
            connExchange_ = connExchange;
            connQueue_ = connQueue;
            routing_ = routing;
            exports.exchangeMapping[key].push({
                  exchange : connExchange_,
                  queue : connQueue_,
                  routing : routing_,
                  queueName : queueName
            });
            logger.logdebug("exchange: " + exchangeName + ", queue: " + queueName+"  exchange : "+connExchange_ +" queue : "+connQueue_ );
              });
        });

    });

}
<|endoftext|>


<commit_before>var gulp = require('gulp');
var plumber = require('gulp-plumber');
var notify = require("gulp-notify");
var pug = require('gulp-pug');
var sass = require('gulp-sass');
var sourcemaps = require('gulp-sourcemaps');
var autoprefixer = require('gulp-autoprefixer');
var concat = require('gulp-concat');
var rename = require("gulp-rename");
var uglify = require('gulp-uglify');
var browserSync = require('browser-sync').create();
var gutil = require('gulp-util');
var flatten = require('gulp-flatten');
var copy = require('gulp-copy');
var del = require('del');
// var usemin = require('gulp-usemin');
// var minifyHtml = require('gulp-minify-html');
// var rev = require('gulp-rev');
var rebuildSass = false;
var rebuildNumber = 0;
var host = 'http://localhost:3000/'; // localhost 用
var appId = '628193067351657'; // localhost 用
// var host = 'https://edenyeh.github.io/project-name/'; // GitHub 用
// var appId = '554563908047907'; // GitHub 用
var hostProd = ''; // 正式用
var appIdProd = ''; // 正式用

var filesPug = [
  './pug/**/!(layout|include)/*.pug'],
  filesPugTemplate = [
    './pug/layout/*.pug',
    './pug/include/*.pug'];
var filesSass = [
  'sass/*.sass',
  'sass/**/!(include|require)/*.sass'],
  filesCSS = filesSass.map(function (file) { return file.replace('sass', 'tmp').replace('.sass', '.css'); }),
  filesComponentCSS = [
    'components/normalize-css/normalize.css'
  ], // Component 的 CSS(ex: 'components/fontawesome/css/font-awesome.min.css')
  filesComponentFont = [], // Component 的 Font(ex: 'components/fontawesome/**/fonts/*.*')
  filesCSSMinify = filesComponentCSS.concat(filesCSS),
  filesJavascript = [
    'javascript/common.js'], // 自己寫的 JavaScript
  filesComponentJavascript = [
    'components/jquery/dist/jquery.min.js'], // Component 的 JavaScript
  filesComponentJavascriptMap = [
    'components/jquery/dist/jquery.min.map'], // Component 的 JavaScript Map
  filesJavascriptMinify = filesComponentJavascript.concat(['tmp/script.uglify.js']),
  filesAssets = [
    'images/**/*.*',
    'plugins/**/*.*',
    'fake_files/**/*.*'], // 純粹複製
  filesRootAssets = [
    '*.txt',
    '*.ico',
    '*.jpg',
    '*.png'
  ], // 純粹複製
  fileHtml5shiv = [
    'components/html5shiv/dist/html5shiv-printshiv.min.js'], // 舊瀏覽器支援 HTML5 Tag (手動複製檔案)
  fileHtml5shiv_prod = [
    'javascripts/html5shiv-printshiv.min.js']; // 舊瀏覽器支援 HTML5 Tag (Production 路徑, 手動複製檔案)

// =============== 整體自動化 Start ===============
gulp.task('html:init', function () {
  pug2html(filesPug);
});
gulp.task('html:pretty', ['del:prod'], function () {
  return gulp.src(filesPug)
    .pipe(pug({
      locals: {
        host: hostProd,
        appId: appIdProd,
        fileHtml5shiv_prod: fileHtml5shiv_prod
      },
      pretty: true
    }))
    .pipe(rename({
      extname: '.html'
    }))
    .pipe(gulp.dest('../app_prod/'))
    .pipe(gutil.buffer(function (err, files) {
      gutil.log(gutil.colors.yellow('html:pretty @ ' + new Date()));
    }));
});

// Sass 樣式
gulp.task('sass:init', function () {
  return sass2css(filesSass);
});
gulp.task('sass:rebuild', function () {
  rebuildSass = true;
  return sass2css(filesSass);
});
gulp.task('sass:compressed', function () {
  return gulp.src(filesSass)
    .pipe(plumber({ errorHandler: notify.onError("Error: <%= error.message %>") }))
    .pipe(sass({
      outputStyle: 'compressed'
    }))
    .pipe(autoprefixer())
    .pipe(gulp.dest('tmp/'))
    .pipe(gutil.buffer(function (err, files) {
      gutil.log(gutil.colors.yellow('sass:compressed @ ' + new Date()));
    }));
});

// Concat 串連
// CSS
gulp.task('css:init', ['sass:init'], function () {
  return concat2style();
});
gulp.task('css:rebuild', ['sass:rebuild'], function () {
  concat2style();
});
gulp.task('css:changed', function () {
  if (rebuildSass === false) {
    concat2style();
  } else {
    rebuildNumber++;
    if (rebuildNumber === filesCSS.length) {
      rebuildNumber = 0;
      rebuildSass = false;
      concat2style();
    };
  };
});
// JS
gulp.task('js:init', function () {
  gulp.src(filesJavascript)
    .pipe(concat('script.js'))
    .pipe(gulp.dest('../app_dev/assets/javascripts/'))
    .pipe(gutil.buffer(function (err, files) {
      gutil.log(gutil.colors.yellow('js:init @ ' + new Date()));
    }));
});
gulp.task('js:rebuild', function () {
  gulp.src(filesJavascript)
    .pipe(plumber({ errorHandler: notify.onError("Error: <%= error.message %>") }))
    .pipe(concat('script.js'))
    .pipe(gulp.dest('../app_dev/assets/javascripts/'))
    .pipe(browserSync.reload({ stream: true }))
    .pipe(gutil.buffer(function (err, files) {
      gutil.log(gutil.colors.yellow('js:rebuild @ ' + new Date()));
    }));
});
// Minify
gulp.task('concat:css-minify', ['del:prod', 'sass:compressed'], function () {
  return gulp.src(filesCSSMinify)
    .pipe(concat('style.min.css'))
    .pipe(gulp.dest('../app_prod/assets/stylesheets/'))
    .pipe(gutil.buffer(function (err, files) {
      gutil.log(gutil.colors.yellow('concat:css-minify @ ' + new Date()));
    }));
});
gulp.task('concat:js-minify', ['del:prod', 'uglify'], function () {
  return gulp.src(filesJavascriptMinify)
    .pipe(sourcemaps.init({ loadMaps: true }))
    .pipe(concat('script.min.js'))
    // .pipe(sourcemaps.write('./'))
    .pipe(gulp.dest('../app_prod/assets/javascripts/'))
    .pipe(gutil.buffer(function (err, files) {
      gutil.log(gutil.colors.yellow('concat:js-minify @ ' + new Date()));
    }));
});

// Uglify JavaScript 壓縮
gulp.task('uglify', function () {
  return gulp.src('../app_dev/assets/javascripts/script.js')
    .pipe(uglify())
    .pipe(rename({
      suffix: '.uglify'
    }))
    .pipe(gulp.dest('tmp/'))
    .pipe(gutil.buffer(function (err, files) {
      gutil.log(gutil.colors.yellow('uglify @ ' + new Date()));
    }));
});

// 複製 Components
gulp.task('copy:components', function () {
  return gulp.src(filesComponentJavascript.concat(filesComponentJavascriptMap).concat(filesComponentCSS).concat(filesComponentFont))
    .pipe(sourcemaps.init({ loadMaps: true }))
    .pipe(copy('../app_dev/assets/'))
    .pipe(gutil.buffer(function (err, files) {
      gutil.log(gutil.colors.yellow('copy:components @ ' + new Date()));
    }));
});
gulp.task('copy:components-prod-font', ['del:prod'], function () {
  return gulp.src(filesComponentFont)
    .pipe(flatten({ includeParents: 1 }))
    .pipe(gulp.dest('../app_prod/assets/'))
    .pipe(gutil.buffer(function (err, files) {
      gutil.log(gutil.colors.yellow('copy:components-prod-font @ ' + new Date()));
    }));
});

// 複製
// ----- 開發版 -----
// 獨立任務(複製 images 資料夾)
gulp.task('copy:assets-images-dev', function () {
  return gulp.src(['images/**/*.*'])
    .pipe(copy('../app_dev/assets/'))
    .pipe(gutil.buffer(function (err, files) {
      gutil.log(gutil.colors.yellow('copy:assets-images-dev @ ' + new Date()));
    }));
});
// 複製根目錄資源
gulp.task('copy:root-assets-dev', function () {
  return gulp.src(filesRootAssets)
    .pipe(copy('../app_dev/'))
    .pipe(gutil.buffer(function (err, files) {
      gutil.log(gutil.colors.yellow('copy:root-assets-dev @ ' + new Date()));
    }));
});
// 複製 plugins...(除了 images) 目錄資源
gulp.task('copy:assets-dev', function () {
  var temp = filesAssets.slice();
  temp.splice(temp.indexOf('images/**/*.*'), 1);
  return gulp.src(temp)
    .pipe(copy('../app_dev/assets/'))
    .pipe(gutil.buffer(function (err, files) {
      gutil.log(gutil.colors.yellow('copy:assets-dev @ ' + new Date()));
    }));
});
// ----- 產品版 -----
// 複製 根 目錄資源
gulp.task('copy:root-assets-prod', ['del:prod'], function () {
  return gulp.src(filesRootAssets)
    .pipe(copy('../app_prod/'))
    .pipe(gutil.buffer(function (err, files) {
      gutil.log(gutil.colors.yellow('copy:root-assets-prod @ ' + new Date()));
    }));
});
// 複製 images, plugins... 目錄資源
gulp.task('copy:assets-prod', ['del:prod'], function () {
  return gulp.src(filesAssets)
    .pipe(copy('../app_prod/assets/'))
    .pipe(gutil.buffer(function (err, files) {
      gutil.log(gutil.colors.yellow('copy:assets-prod @ ' + new Date()));
    }));
});

// 移除資料夾
gulp.task('del:dev', function () {
  return del(['tmp/**', '../app_dev/**'], { force: true });
});
gulp.task('del:prod', function () {
  return del('../app_prod/**', { force: true });
});
gulp.task('del:prod-assets-min', function () {
  del([
    '../app_prod/assets/javascripts/script.min.js',
    '../app_prod/assets/stylesheets/style.min.css'], { force: true });
});

// 版本號替換（完全由前端開發時可用）
gulp.task('usemin', ['copy:root-assets-prod', 'copy:assets-prod', 'copy:components-prod-font', 'html:pretty', 'concat:css-minify', 'concat:js-minify'], function () {
  return gulp.src('../app_prod/**/*.html')
    .pipe(usemin({
      css: [rev],
      js: [rev],
      html: [function () { return minifyHtml({ empty: true }); }]
    }))
    .pipe(gulp.dest('../app_prod/'))
    .pipe(gutil.buffer(function (err, files) {
      gutil.log(gutil.colors.yellow('usemin @ ' + new Date()));
    }));
});
// =============== 整體自動化 End ===============

// =============== 轉譯函式 Start ===============
function pug2html(param) {
  var includeParents = filesPugTemplate.length <= 2 ? 0 : 1;
  gulp.src(param)
    .pipe(plumber({ errorHandler: notify.onError("Error: <%= error.message %>") }))
    .pipe(pug({
      locals: {
        dev: true,
        host: host,
        appId: appId,
        fileHtml5shiv: fileHtml5shiv,
        filesComponentCSS: filesComponentCSS,
        filesComponentJavascript: filesComponentJavascript
      },
      pretty: true
    }))
    .pipe(flatten({ includeParents: includeParents }))
    .pipe(gulp.dest('../app_dev/'))
    .on('finish', function () {
      gulp.src(param)
        .pipe(browserSync.reload({ stream: true }))
        .pipe(gutil.buffer(function (err, files) {
          gutil.log(gutil.colors.yellow('pug2html @ ' + new Date()));
        }))
    });
};

function sass2css(param) {
  var index;
  var newPath;
  if (typeof param === 'object') {
    // sass 初始化
    newPath = param;
  } else {
    // sass 異動時
    index = param.indexOf('app_src/');
    newPath = param.substring(index).replace('app_src', '.').replace('/sass', '/sass/**');
  };

  return gulp.src(newPath)
    .pipe(plumber({ errorHandler: notify.onError("Error: <%= error.message %>") }))
    .pipe(sourcemaps.init())
    .pipe(sass({
      outputStyle: 'expanded'
    }))
    .pipe(autoprefixer())
    .pipe(sourcemaps.write())
    .pipe(gulp.dest('tmp/'))
    .pipe(gutil.buffer(function (err, files) {
      gutil.log(gutil.colors.yellow('sass2css @ ' + new Date()));
    }));
};

function concat2style() {
  return gulp.src(filesCSS)
    .pipe(plumber({ errorHandler: notify.onError("Error: <%= error.message %>") }))
    .pipe(sourcemaps.init({ loadMaps: true }))
    .pipe(concat('style.css'))
    .pipe(sourcemaps.write())
    .pipe(gulp.dest('../app_dev/assets/stylesheets/'))
    .pipe(browserSync.stream())
    .pipe(gutil.buffer(function (err, files) {
      gutil.log(gutil.colors.yellow('concat2style @ ' + new Date()));
    }));
};
// =============== 轉譯函式 End ===============

// 起動 Server
gulp.task('connect:dev', function () {
  return browserSync.init({
    server: {
      baseDir: '../app_dev/'
    }
  });
});
gulp.task('connect:prod', function () {
  return browserSync.init({
    server: {
      baseDir: '../app_prod/'
    }
  });
});

gulp.task('default', ['connect:dev', 'copy:root-assets-dev', 'copy:assets-dev', 'copy:components', 'html:init', 'css:init', 'js:init'], function () {
  gulp.watch(filesPug, function (e) {
    pug2html(e.path);
  });
  gulp.watch(filesPugTemplate, function (e) {
    pug2html(filesPug);
  });
  gulp.watch(filesSass, function (e) {
    sass2css(e.path);
  });
  gulp.watch(['sass/require/*.sass', 'sass/include/*.sass'], function (e) {
    gulp.start(['css:rebuild']);
  });
  gulp.watch('tmp/**/*.css', function (e) {
    gulp.start(['css:changed']);
  });
  gulp.watch('javascript/*.js', function (e) {
    gulp.start(['js:rebuild']);
  });
});
gulp.task('prod', ['copy:root-assets-prod', 'copy:assets-prod', 'copy:components-prod-font', 'html:pretty', 'concat:css-minify', 'concat:js-minify'], function () {
  gulp.start(['connect:prod']);
});
// gulp.task('prod', ['usemin'], function() {
//   gulp.start(['connect:prod']);
// });
<commit_msg>修正 gulpfile.js pug 路徑<commit_after>var gulp = require('gulp');
var plumber = require('gulp-plumber');
var notify = require("gulp-notify");
var pug = require('gulp-pug');
var sass = require('gulp-sass');
var sourcemaps = require('gulp-sourcemaps');
var autoprefixer = require('gulp-autoprefixer');
var concat = require('gulp-concat');
var rename = require("gulp-rename");
var uglify = require('gulp-uglify');
var browserSync = require('browser-sync').create();
var gutil = require('gulp-util');
var flatten = require('gulp-flatten');
var copy = require('gulp-copy');
var del = require('del');
// var usemin = require('gulp-usemin');
// var minifyHtml = require('gulp-minify-html');
// var rev = require('gulp-rev');
var rebuildSass = false;
var rebuildNumber = 0;
var host = 'http://localhost:3000/'; // localhost 用
var appId = '628193067351657'; // localhost 用
// var host = 'https://edenyeh.github.io/project-name/'; // GitHub 用
// var appId = '554563908047907'; // GitHub 用
var hostProd = ''; // 正式用
var appIdProd = ''; // 正式用

var filesPug = [
  'pug/**/!(layout|include)/*.pug'],
  filesPugTemplate = [
    'pug/layout/*.pug',
    'pug/include/*.pug'];
var filesSass = [
  'sass/*.sass',
  'sass/**/!(include|require)/*.sass'],
  filesCSS = filesSass.map(function (file) { return file.replace('sass', 'tmp').replace('.sass', '.css'); }),
  filesComponentCSS = [
    'components/normalize-css/normalize.css'
  ], // Component 的 CSS(ex: 'components/fontawesome/css/font-awesome.min.css')
  filesComponentFont = [], // Component 的 Font(ex: 'components/fontawesome/**/fonts/*.*')
  filesCSSMinify = filesComponentCSS.concat(filesCSS),
  filesJavascript = [
    'javascript/common.js'], // 自己寫的 JavaScript
  filesComponentJavascript = [
    'components/jquery/dist/jquery.min.js'], // Component 的 JavaScript
  filesComponentJavascriptMap = [
    'components/jquery/dist/jquery.min.map'], // Component 的 JavaScript Map
  filesJavascriptMinify = filesComponentJavascript.concat(['tmp/script.uglify.js']),
  filesAssets = [
    'images/**/*.*',
    'plugins/**/*.*',
    'fake_files/**/*.*'], // 純粹複製
  filesRootAssets = [
    '*.txt',
    '*.ico',
    '*.jpg',
    '*.png'
  ], // 純粹複製
  fileHtml5shiv = [
    'components/html5shiv/dist/html5shiv-printshiv.min.js'], // 舊瀏覽器支援 HTML5 Tag (手動複製檔案)
  fileHtml5shiv_prod = [
    'javascripts/html5shiv-printshiv.min.js']; // 舊瀏覽器支援 HTML5 Tag (Production 路徑, 手動複製檔案)

// =============== 整體自動化 Start ===============
gulp.task('html:init', function () {
  pug2html(filesPug);
});
gulp.task('html:pretty', ['del:prod'], function () {
  return gulp.src(filesPug)
    .pipe(pug({
      locals: {
        host: hostProd,
        appId: appIdProd,
        fileHtml5shiv_prod: fileHtml5shiv_prod
      },
      pretty: true
    }))
    .pipe(rename({
      extname: '.html'
    }))
    .pipe(gulp.dest('../app_prod/'))
    .pipe(gutil.buffer(function (err, files) {
      gutil.log(gutil.colors.yellow('html:pretty @ ' + new Date()));
    }));
});

// Sass 樣式
gulp.task('sass:init', function () {
  return sass2css(filesSass);
});
gulp.task('sass:rebuild', function () {
  rebuildSass = true;
  return sass2css(filesSass);
});
gulp.task('sass:compressed', function () {
  return gulp.src(filesSass)
    .pipe(plumber({ errorHandler: notify.onError("Error: <%= error.message %>") }))
    .pipe(sass({
      outputStyle: 'compressed'
    }))
    .pipe(autoprefixer())
    .pipe(gulp.dest('tmp/'))
    .pipe(gutil.buffer(function (err, files) {
      gutil.log(gutil.colors.yellow('sass:compressed @ ' + new Date()));
    }));
});

// Concat 串連
// CSS
gulp.task('css:init', ['sass:init'], function () {
  return concat2style();
});
gulp.task('css:rebuild', ['sass:rebuild'], function () {
  concat2style();
});
gulp.task('css:changed', function () {
  if (rebuildSass === false) {
    concat2style();
  } else {
    rebuildNumber++;
    if (rebuildNumber === filesCSS.length) {
      rebuildNumber = 0;
      rebuildSass = false;
      concat2style();
    };
  };
});
// JS
gulp.task('js:init', function () {
  gulp.src(filesJavascript)
    .pipe(concat('script.js'))
    .pipe(gulp.dest('../app_dev/assets/javascripts/'))
    .pipe(gutil.buffer(function (err, files) {
      gutil.log(gutil.colors.yellow('js:init @ ' + new Date()));
    }));
});
gulp.task('js:rebuild', function () {
  gulp.src(filesJavascript)
    .pipe(plumber({ errorHandler: notify.onError("Error: <%= error.message %>") }))
    .pipe(concat('script.js'))
    .pipe(gulp.dest('../app_dev/assets/javascripts/'))
    .pipe(browserSync.reload({ stream: true }))
    .pipe(gutil.buffer(function (err, files) {
      gutil.log(gutil.colors.yellow('js:rebuild @ ' + new Date()));
    }));
});
// Minify
gulp.task('concat:css-minify', ['del:prod', 'sass:compressed'], function () {
  return gulp.src(filesCSSMinify)
    .pipe(concat('style.min.css'))
    .pipe(gulp.dest('../app_prod/assets/stylesheets/'))
    .pipe(gutil.buffer(function (err, files) {
      gutil.log(gutil.colors.yellow('concat:css-minify @ ' + new Date()));
    }));
});
gulp.task('concat:js-minify', ['del:prod', 'uglify'], function () {
  return gulp.src(filesJavascriptMinify)
    .pipe(sourcemaps.init({ loadMaps: true }))
    .pipe(concat('script.min.js'))
    // .pipe(sourcemaps.write('./'))
    .pipe(gulp.dest('../app_prod/assets/javascripts/'))
    .pipe(gutil.buffer(function (err, files) {
      gutil.log(gutil.colors.yellow('concat:js-minify @ ' + new Date()));
    }));
});

// Uglify JavaScript 壓縮
gulp.task('uglify', function () {
  return gulp.src('../app_dev/assets/javascripts/script.js')
    .pipe(uglify())
    .pipe(rename({
      suffix: '.uglify'
    }))
    .pipe(gulp.dest('tmp/'))
    .pipe(gutil.buffer(function (err, files) {
      gutil.log(gutil.colors.yellow('uglify @ ' + new Date()));
    }));
});

// 複製 Components
gulp.task('copy:components', function () {
  return gulp.src(filesComponentJavascript.concat(filesComponentJavascriptMap).concat(filesComponentCSS).concat(filesComponentFont))
    .pipe(sourcemaps.init({ loadMaps: true }))
    .pipe(copy('../app_dev/assets/'))
    .pipe(gutil.buffer(function (err, files) {
      gutil.log(gutil.colors.yellow('copy:components @ ' + new Date()));
    }));
});
gulp.task('copy:components-prod-font', ['del:prod'], function () {
  return gulp.src(filesComponentFont)
    .pipe(flatten({ includeParents: 1 }))
    .pipe(gulp.dest('../app_prod/assets/'))
    .pipe(gutil.buffer(function (err, files) {
      gutil.log(gutil.colors.yellow('copy:components-prod-font @ ' + new Date()));
    }));
});

// 複製
// ----- 開發版 -----
// 獨立任務(複製 images 資料夾)
gulp.task('copy:assets-images-dev', function () {
  return gulp.src(['images/**/*.*'])
    .pipe(copy('../app_dev/assets/'))
    .pipe(gutil.buffer(function (err, files) {
      gutil.log(gutil.colors.yellow('copy:assets-images-dev @ ' + new Date()));
    }));
});
// 複製根目錄資源
gulp.task('copy:root-assets-dev', function () {
  return gulp.src(filesRootAssets)
    .pipe(copy('../app_dev/'))
    .pipe(gutil.buffer(function (err, files) {
      gutil.log(gutil.colors.yellow('copy:root-assets-dev @ ' + new Date()));
    }));
});
// 複製 plugins...(除了 images) 目錄資源
gulp.task('copy:assets-dev', function () {
  var temp = filesAssets.slice();
  temp.splice(temp.indexOf('images/**/*.*'), 1);
  return gulp.src(temp)
    .pipe(copy('../app_dev/assets/'))
    .pipe(gutil.buffer(function (err, files) {
      gutil.log(gutil.colors.yellow('copy:assets-dev @ ' + new Date()));
    }));
});
// ----- 產品版 -----
// 複製 根 目錄資源
gulp.task('copy:root-assets-prod', ['del:prod'], function () {
  return gulp.src(filesRootAssets)
    .pipe(copy('../app_prod/'))
    .pipe(gutil.buffer(function (err, files) {
      gutil.log(gutil.colors.yellow('copy:root-assets-prod @ ' + new Date()));
    }));
});
// 複製 images, plugins... 目錄資源
gulp.task('copy:assets-prod', ['del:prod'], function () {
  return gulp.src(filesAssets)
    .pipe(copy('../app_prod/assets/'))
    .pipe(gutil.buffer(function (err, files) {
      gutil.log(gutil.colors.yellow('copy:assets-prod @ ' + new Date()));
    }));
});

// 移除資料夾
gulp.task('del:dev', function () {
  return del(['tmp/**', '../app_dev/**'], { force: true });
});
gulp.task('del:prod', function () {
  return del('../app_prod/**', { force: true });
});
gulp.task('del:prod-assets-min', function () {
  del([
    '../app_prod/assets/javascripts/script.min.js',
    '../app_prod/assets/stylesheets/style.min.css'], { force: true });
});

// 版本號替換（完全由前端開發時可用）
gulp.task('usemin', ['copy:root-assets-prod', 'copy:assets-prod', 'copy:components-prod-font', 'html:pretty', 'concat:css-minify', 'concat:js-minify'], function () {
  return gulp.src('../app_prod/**/*.html')
    .pipe(usemin({
      css: [rev],
      js: [rev],
      html: [function () { return minifyHtml({ empty: true }); }]
    }))
    .pipe(gulp.dest('../app_prod/'))
    .pipe(gutil.buffer(function (err, files) {
      gutil.log(gutil.colors.yellow('usemin @ ' + new Date()));
    }));
});
// =============== 整體自動化 End ===============

// =============== 轉譯函式 Start ===============
function pug2html(param) {
  var includeParents = filesPugTemplate.length <= 2 ? 0 : 1;
  gulp.src(param)
    .pipe(plumber({ errorHandler: notify.onError("Error: <%= error.message %>") }))
    .pipe(pug({
      locals: {
        dev: true,
        host: host,
        appId: appId,
        fileHtml5shiv: fileHtml5shiv,
        filesComponentCSS: filesComponentCSS,
        filesComponentJavascript: filesComponentJavascript
      },
      pretty: true
    }))
    .pipe(flatten({ includeParents: includeParents }))
    .pipe(gulp.dest('../app_dev/'))
    .on('finish', function () {
      gulp.src(param)
        .pipe(browserSync.reload({ stream: true }))
        .pipe(gutil.buffer(function (err, files) {
          gutil.log(gutil.colors.yellow('pug2html @ ' + new Date()));
        }))
    });
};

function sass2css(param) {
  var index;
  var newPath;
  if (typeof param === 'object') {
    // sass 初始化
    newPath = param;
  } else {
    // sass 異動時
    index = param.indexOf('app_src/');
    newPath = param.substring(index).replace('app_src', '.').replace('/sass', '/sass/**');
  };

  return gulp.src(newPath)
    .pipe(plumber({ errorHandler: notify.onError("Error: <%= error.message %>") }))
    .pipe(sourcemaps.init())
    .pipe(sass({
      outputStyle: 'expanded'
    }))
    .pipe(autoprefixer())
    .pipe(sourcemaps.write())
    .pipe(gulp.dest('tmp/'))
    .pipe(gutil.buffer(function (err, files) {
      gutil.log(gutil.colors.yellow('sass2css @ ' + new Date()));
    }));
};

function concat2style() {
  return gulp.src(filesCSS)
    .pipe(plumber({ errorHandler: notify.onError("Error: <%= error.message %>") }))
    .pipe(sourcemaps.init({ loadMaps: true }))
    .pipe(concat('style.css'))
    .pipe(sourcemaps.write())
    .pipe(gulp.dest('../app_dev/assets/stylesheets/'))
    .pipe(browserSync.stream())
    .pipe(gutil.buffer(function (err, files) {
      gutil.log(gutil.colors.yellow('concat2style @ ' + new Date()));
    }));
};
// =============== 轉譯函式 End ===============

// 起動 Server
gulp.task('connect:dev', function () {
  return browserSync.init({
    server: {
      baseDir: '../app_dev/'
    }
  });
});
gulp.task('connect:prod', function () {
  return browserSync.init({
    server: {
      baseDir: '../app_prod/'
    }
  });
});

gulp.task('default', ['connect:dev', 'copy:root-assets-dev', 'copy:assets-dev', 'copy:components', 'html:init', 'css:init', 'js:init'], function () {
  gulp.watch(filesPug, function (e) {
    pug2html(e.path);
  });
  gulp.watch(filesPugTemplate, function (e) {
    pug2html(filesPug);
  });
  gulp.watch(filesSass, function (e) {
    sass2css(e.path);
  });
  gulp.watch(['sass/require/*.sass', 'sass/include/*.sass'], function (e) {
    gulp.start(['css:rebuild']);
  });
  gulp.watch('tmp/**/*.css', function (e) {
    gulp.start(['css:changed']);
  });
  gulp.watch('javascript/*.js', function (e) {
    gulp.start(['js:rebuild']);
  });
});
gulp.task('prod', ['copy:root-assets-prod', 'copy:assets-prod', 'copy:components-prod-font', 'html:pretty', 'concat:css-minify', 'concat:js-minify'], function () {
  gulp.start(['connect:prod']);
});
// gulp.task('prod', ['usemin'], function() {
//   gulp.start(['connect:prod']);
// });
<|endoftext|>


<commit_before>import ua from 'vigour-ua/navigator' // again scope it differently this is bit dirty

// just use hasTouch
const touch = typeof window !== 'undefined' && ((('ontouchstart' in global) ||
  global.DocumentTouch &&
  document instanceof global.DocumentTouch) ||
  navigator.msMaxTouchPoints)

// super unreliable check for chrome emulator for development (on mac only)
// const isChromeEmulator = touch &&
//   navigator.vendor === 'Google Inc.' &&
//   navigator.platform === 'MacIntel'

if (ua.platform === 'ios' && touch) {
  document.documentElement.style.cursor = 'pointer' // ios test...
  document.body.style.cursor = 'pointer'
}

export default listen

function listen () {
  const l = arguments.length
  const a = []
  for (let i = 0; i < l; i++) {
    let key = arguments[i]
    let listener = arguments[++i]
    console.log(key)
    addEventListener(key, listener, key === 'focus' || key === 'scroll' || key === 'blur')
    a.push(key, listener)
  }
  return () => {
    for (let i = 0; i < l; i++) { // remove listeners test!
      document.removeEventListener(a[i], a[++i])
    }
  }
}

const addEventListener = function addEventListener (key, listener, capture) {
  document.addEventListener(key, listener, capture)
}
// : function addEventListener (key, listener, capture) { // chrome emulator tests
//   const touchEvent = key.indexOf('mouse') === -1
//   if (touchEvent) { document.addEventListener(key, listener, capture) }
// }
<commit_msg>removed log<commit_after>import ua from 'vigour-ua/navigator' // again scope it differently this is bit dirty

// just use hasTouch
const touch = typeof window !== 'undefined' && ((('ontouchstart' in global) ||
  global.DocumentTouch &&
  document instanceof global.DocumentTouch) ||
  navigator.msMaxTouchPoints)

// super unreliable check for chrome emulator for development (on mac only)
// const isChromeEmulator = touch &&
//   navigator.vendor === 'Google Inc.' &&
//   navigator.platform === 'MacIntel'

if (ua.platform === 'ios' && touch) {
  document.documentElement.style.cursor = 'pointer' // ios test...
  document.body.style.cursor = 'pointer'
}

export default listen

function listen () {
  const l = arguments.length
  const a = []
  for (let i = 0; i < l; i++) {
    let key = arguments[i]
    let listener = arguments[++i]
    addEventListener(key, listener, key === 'focus' || key === 'scroll' || key === 'blur')
    a.push(key, listener)
  }
  return () => {
    for (let i = 0; i < l; i++) { // remove listeners test!
      document.removeEventListener(a[i], a[++i])
    }
  }
}

const addEventListener = function addEventListener (key, listener, capture) {
  document.addEventListener(key, listener, capture)
}
// : function addEventListener (key, listener, capture) { // chrome emulator tests
//   const touchEvent = key.indexOf('mouse') === -1
//   if (touchEvent) { document.addEventListener(key, listener, capture) }
// }
<|endoftext|>


<commit_before>import 'datejs'

import firebase from 'firebase'

export class UserQwest {
  constructor(props) {
    this.assignedTo = props.assignedTo || null
    this.title = props.title || null
  }

  update(qwestData) {
    // update the UserQwest
    this.title = qwestData.title
  }
}

export class AssignedUserQwest {
  constructor(props) {
    this.assignedBy = props.createdBy || null
    this.title = props.title || null
  }

  update(qwestData) {
    // update the AssignedUserQwest
    this.title = qwestData.title
  }
}

export class AssigningUserQwest {
  constructor(props) {
    this.assignedTo = props.assignedTo || null
    this.accepted = props.accepted || null
    this.title = props.title || null
  }

  update(qwestData) {
    // update the AssigningUserQwest
    this.title = qwestData.title
  }
}

export default class Qwest {
  constructor(props) {
    this.createdBy = props.createdBy || firebase.auth().currentUser.uid
    this.createdOn = props.createdOn || Date.today().setTimeToNow().toString()
    this.assignedTo = props.assignedTo || null
    this.accepted = props.accepted || null
    this.completed = props.completed || null
    this.title = props.title || null
    this.description = props.description || null
  }

  create(successCallback) {
    // Get a key for a new Qwest
    var newQwestKey = firebase.database().ref().child('qwests').push().key

    // Create UserQwest object from properties
    const userQwest = new UserQwest(this)

    // Prepare updates
    let updates = {}
    updates['/qwests/' + newQwestKey] = this
    updates['/user-qwests/' + this.createdBy + '/active/' + newQwestKey] = userQwest

    // Update the database
    return firebase.database().ref().update(updates).then(successCallback)
  }

  update(qwestData) {
    // update the Qwest
    this.title = qwestData.title
    this.description = qwestData.description
  }
}
<commit_msg>created updatedOn field<commit_after>import 'datejs'

import firebase from 'firebase'

export class UserQwest {
  constructor(props) {
    this.assignedTo = props.assignedTo || null
    this.title = props.title || null
  }

  update(qwestData) {
    // update the UserQwest
    this.title = qwestData.title
  }
}

export class AssignedUserQwest {
  constructor(props) {
    this.assignedBy = props.createdBy || null
    this.title = props.title || null
  }

  update(qwestData) {
    // update the AssignedUserQwest
    this.title = qwestData.title
  }
}

export class AssigningUserQwest {
  constructor(props) {
    this.assignedTo = props.assignedTo || null
    this.accepted = props.accepted || null
    this.title = props.title || null
  }

  update(qwestData) {
    // update the AssigningUserQwest
    this.title = qwestData.title
  }
}

export default class Qwest {
  constructor(props) {
    this.createdBy = props.createdBy || firebase.auth().currentUser.uid
    this.createdOn = props.createdOn || Date.today().setTimeToNow().toString()
    this.updatedOn = props.createdOn || Date.today().setTimeToNow().toString()
    this.assignedTo = props.assignedTo || null
    this.accepted = props.accepted || null
    this.completed = props.completed || null
    this.title = props.title || null
    this.description = props.description || null
  }

  create(successCallback) {
    // Get a key for a new Qwest
    var newQwestKey = firebase.database().ref().child('qwests').push().key

    // Create UserQwest object from properties
    const userQwest = new UserQwest(this)

    // Prepare updates
    let updates = {}
    updates['/qwests/' + newQwestKey] = this
    updates['/user-qwests/' + this.createdBy + '/active/' + newQwestKey] = userQwest

    // Update the database
    return firebase.database().ref().update(updates).then(successCallback)
  }

  update(qwestData) {
    // update the Qwest
    this.title = qwestData.title
    this.description = qwestData.description
    this.updatedOn = Date.today().setTimeToNow().toString()
  }
}
<|endoftext|>


<commit_before>function Position(x, y) {
  this.x = x
  this.y = y
}

Position.prototype.plus = function(another) {
  return new Position(this.x + another.x, this.y + another.y)
}

function Direction(x, y) {
  this.x = x
  this.y = y
}

Direction.NORTH = new Direction(0, -1)
Direction.prototype.isNorth = function() {
  return this.equals(Direction.NORTH)
}

Direction.SOUTH = new Direction(0, 1)
Direction.prototype.isSouth = function() {
  return this.equals(Direction.SOUTH)
}

Direction.WEST = new Direction(-1, 0)
Direction.prototype.isWest = function() {
  return this.equals(Direction.WEST)
}

Direction.EAST = new Direction(1, 0)
Direction.prototype.isEast = function() {
  return this.equals(Direction.EAST)
}

Direction.prototype.turnRight = function() {
  return new Direction(-this.y, this.x)
}

Direction.prototype.turnLeft = function() {
  return new Direction(this.y, -this.x)
}

Position.prototype.clone = Direction.prototype.clone = function() {
  return new this.constructor(this.x, this.y)
}

Position.prototype.equals = Direction.prototype.equals = function(another) {
  return another instanceof this.constructor
    && another.x == this.x
    && another.y == this.y
}

function Field() {
  this.ziegel = 0
  this.marke = false
  this.quader = false
}

Field.prototype.clone = function() {
  var f = new Field()
  f.ziegel = this.ziegel
  f.marke  = this.marke
  f.quader = this.quader
  return f
}

App.Models.Environment = Backbone.Model.extend({

  defaults: {
    position: new Position(0, 0),
    direction: Direction.SOUTH
  },

  initialize: function() {
    if (!this.get('fields')) {
      this.set({
        fields: matrix(
          this.get('width'),
          this.get('depth'),
          function() { return new Field() }
        )
      })
    }

    this
      .bind('change:position',  _.bind(this.trigger, this, 'change:robot'))
      .bind('change:direction', _.bind(this.trigger, this, 'change:robot'))
      .bind('change:fields',    _.bind(this.trigger, this, 'change:all'))

    // Cache (for performance reasons)
    var setCurrentField = _(function() {
      this.$currentField = this.getField(this.$position)
    }).bind(this)
    setCurrentField()
    this
      .bind('change:position', setCurrentField)
      .bind('change:fields',   setCurrentField)
  },

  // Overwrite backbone's set method for performance reasons
  set: function(attrs, options) {
    // - validations, - escaped attributes, - changes tracking
    if (attrs.attributes) attrs = attrs.attributes
    var silent = options && options.silent
    var change = false
    for (var attr in attrs) {
      change = true
      var val = attrs[attr]
      this.attributes[attr] = val
      this['$'+attr] = val
      if (!silent) this.trigger('change:'+attr)
    }
    if (change && !silent) this.trigger('change')
  },

  // Overwrite backbone's clone method
  clone: function() {
    // + make a copy of the attributes
    return new this.constructor(clone(this.attributes))
  },

  triggerChangeField: function(p) {
    this.trigger('change:field', p.x, p.y, this.getField(p))
    this.trigger('change')
  },

  getField: function(position) {
    return this.$fields[position.x][position.y]
  },

  forward: function() {
    return this.$position.plus(this.$direction)
  },

  istZiegel: function(n) {
    if (this.istWand()) return false
    var ziegel = this.getField(this.forward()).ziegel
    return n ? (ziegel == n) : !!ziegel
  },

  hinlegen: function() {
    if (this.istWand()) error("Karol kann keinen Ziegel hinlegen. Er steht vor einer Wand.")
    var nextPosition = this.forward()
    this.getField(nextPosition).ziegel += 1
    this.triggerChangeField(nextPosition)
  },

  aufheben: function() {
    if (this.istWand()) error("Karol kann keinen Ziegel aufheben. Er steht vor einer Wand.")
    var nextPosition = this.forward()
    var field = this.getField(nextPosition)
    if (!field.ziegel) error("Karol kann keinen Ziegel aufheben, da kein Ziegel vor ihm liegt.")
    field.ziegel--
    this.triggerChangeField(nextPosition)
  },

  markeSetzen: function() {
    this.$currentField.marke = true
    this.triggerChangeField(this.$position)
  },

  markeLoeschen: function() {
    this.$currentField.marke = false
    this.triggerChangeField(this.$position)
  },

  marke: function() {
    this.$currentField.marke = !this.$currentField.marke
    this.triggerChangeField(this.$position)
  },

  istMarke: function() {
    return this.$currentField.marke
  },

  isValid: function(position) {
    var x = position.x
    ,   z = position.y
    return x >= 0 && x < this.get('width') && z >= 0 && z < this.get('depth')
  },

  istWand: function() {
    var next = this.forward()
    return !this.isValid(next) || this.getField(next).quader
  },

  linksDrehen: function() {
    this.set({ direction: this.$direction.turnLeft() })
  },

  rechtsDrehen: function() {
    this.set({ direction: this.$direction.turnRight() })
  },

  schritt: function() {
    if (this.istWand()) error("Karol kann keinen Schritt machen, er steht vor einer Wand.")
    var newPosition = this.forward()
    if (Math.abs(this.$currentField.ziegel - this.getField(newPosition).ziegel) > 1) {
      error("Karol kann nur einen Ziegel pro Schritt nach oben oder unten springen.")
    }
    this.set({ position: newPosition })
  },

  schrittRueckwaerts: function() {
    this.probiere(_(function() {
      this.linksDrehen()
      this.linksDrehen()
      this.schritt()
      this.linksDrehen()
      this.linksDrehen()
    }).bind(this))
  },

  quader: function() {
    var position = this.forward()
    if (!this.isValid(position)) error("Karol kann keinen Quader hinlegen. Er steht vor einer Wand.")
    var field = this.getField(position)
    if (field.quader) error("Karol kann keinen Quader hinlegen, da schon einer liegt.")
    if (field.ziegel) error("Karol kann keinen Quader hinlegen, da auf dem Feld schon Ziegel liegen.")
    field.quader = true
    this.triggerChangeField(position)
  },

  entfernen: function() {
    var position = this.forward()
    if (!this.isValid(position)) error("Karol kann keinen Quader entfernen. Er steht vor einer Wand.")
    var field = this.getField(position)
    if (!field.quader) error("Karol kann keinen Quader entfernen, da auf dem Feld kein Quader liegt.")
    field.quader = false
    this.triggerChangeField(position)
  },

  ton: beep,

  istNorden: function() { return this.$direction.isNorth() },
  istSueden: function() { return this.$direction.isSouth() },
  istWesten: function() { return this.$direction.isWest() },
  istOsten:  function() { return this.$direction.isEast() },

  probiere: function(fn) {
    var clone = this.clone()
    try {
      return fn()
    } catch(exc) {
      this.set(clone)
      this.trigger('change:all')
      this.trigger('change')
    }
  },

  run: function(code) {
    this.backup = this.clone()

    var self = this
    this.execute(code, function(stack) {
      console.log('Commands: ', stack)
      self.stack = stack
    })
  },

  execute: function(code, callback) {
    // TODO: refactor?
    var iframe = document.createElement('iframe')
    iframe.style.display = 'none'
    document.body.appendChild(iframe)
    var win = iframe.contentWindow
    win.parent = null
    var karol = win.karol = {}
    var stack = []
    var self = this
    var timed = []
    var END_EXC = new Error('end')

    function stop(obj) {
      clearTimeout(obj)
      clearInterval(obj)
      if (typeof obj.abort == 'function') obj.abort()
    }

    function stopAll() {
      _(timed).each(stop)
      timed = []
    }

    function exec(fn) {
      try {
        fn()
      } catch (exc) {
        if (exc != END_EXC) {
          stack.push(exc)
        }
        stopAll()
      }
      end()
    }

    function end() {
      if (!timed.length) {
        document.body.removeChild(iframe)
        callback(stack)
      }
    }

    _.each(['istWand', 'schritt', 'linksDrehen', 'rechtsDrehen', 'hinlegen', 'aufheben', 'istZiegel', 'markeSetzen', 'markeLoeschen', 'istMarke', 'istNorden', 'istSueden', 'istWesten', 'istOsten', 'ton', 'probiere'], function(name) {
      karol[name] = function(n) {
        n = n || 1

        if (HIGHLIGHT_LINE) {
          try {
            error()
          } catch (exc) {
            var lineNumber = getLineNumber(exc.stack, 1)
          }
        } else {
          var lineNumber = null
        }

        if (self[name].length == 0) {
          for (var i = 0; i < n; i++) {
            var result = self[name]()
            stack.push([name, lineNumber])
          }
        } else {
          var result = self[name].apply(self, arguments)
          stack.push([name, lineNumber])
        }
        return result
      }
    })

    win.warten = function(fn, ms) {
      var timeout = setTimeout(function() {
        removeFromArray(timed, timeout)
        exec(fn)
      }, ms)
      timed.push(timeout)
      return timeout
    }

    win.periode = function(fn, ms) {
      var interval = setInterval(function() {
        exec(fn)
      }, ms)
      timed.push(interval)
      return interval
    }

    win.laden = function(opts, fn) {
      var req = $.ajax(opts)
        .success(function(responseText) {
          removeFromArray(timed, req)
          exec(_.bind(fn, null, responseText))
        })
        .error(function() {
          removeFromArray(timed, req)
        })
      timed.push(req)
      return req
    }

    win.stoppen = function(obj) {
      stop(obj)
      removeFromArray(timed, obj)
      end()
    }

    win.beenden = function() {
      throw END_EXC
    }

    exec(function() {
      win.document.write('<script>'+code+'</script>') // evil, I know
    })
  },

  next: function() {
    var pair = this.stack.shift()
    ,   command = pair[0]
    ,   lineNumber = pair[1]

    if (typeof command == 'string') {
      this[command]()
    } else if (command instanceof Error) {
      alert(command)
    }

    if (lineNumber) this.trigger('line', lineNumber)
  },

  replay: function() {
    this.reset()

    var self = this
    var interval = setInterval(function() {
      if (self.stack.length == 0) {
        clearInterval(interval)
      } else {
        self.next()
      }
    }, 150)
  },

  reset: function() {
    this.set(this.backup.attributes)
    this.trigger('change:all')
    this.trigger('change')
  },

  eachField: function(fn) {
    var fields = this.$fields
    ,   w = this.get('width')
    ,   d = this.get('depth')
    for (var x = 0; x < w; x++) {
      for (var y = 0; y < d; y++) {
        fn(x, y, fields[x][y])
      }
    }
  }

})
<commit_msg>Added import and export methods to environment<commit_after>function Position(x, y) {
  this.x = x
  this.y = y
}

Position.prototype.plus = function(another) {
  return new Position(this.x + another.x, this.y + another.y)
}

function Direction(x, y) {
  this.x = x
  this.y = y
}

Direction.NORTH = new Direction(0, -1)
Direction.prototype.isNorth = function() {
  return this.equals(Direction.NORTH)
}

Direction.SOUTH = new Direction(0, 1)
Direction.prototype.isSouth = function() {
  return this.equals(Direction.SOUTH)
}

Direction.WEST = new Direction(-1, 0)
Direction.prototype.isWest = function() {
  return this.equals(Direction.WEST)
}

Direction.EAST = new Direction(1, 0)
Direction.prototype.isEast = function() {
  return this.equals(Direction.EAST)
}

Direction.prototype.turnRight = function() {
  return new Direction(-this.y, this.x)
}

Direction.prototype.turnLeft = function() {
  return new Direction(this.y, -this.x)
}

Position.prototype.clone = Direction.prototype.clone = function() {
  return new this.constructor(this.x, this.y)
}

Position.prototype.equals = Direction.prototype.equals = function(another) {
  return another instanceof this.constructor
    && another.x == this.x
    && another.y == this.y
}

function Field() {
  this.ziegel = 0
  this.marke = false
  this.quader = false
}

Field.prototype.clone = function() {
  var f = new Field()
  f.ziegel = this.ziegel
  f.marke  = this.marke
  f.quader = this.quader
  return f
}

App.Models.Environment = Backbone.Model.extend({

  defaults: {
    position: new Position(0, 0),
    direction: Direction.SOUTH
  },

  initialize: function() {
    if (!this.get('fields')) {
      this.set({
        fields: matrix(
          this.get('width'),
          this.get('depth'),
          function() { return new Field() }
        )
      })
    }

    this
      .bind('change:position',  _.bind(this.trigger, this, 'change:robot'))
      .bind('change:direction', _.bind(this.trigger, this, 'change:robot'))
      .bind('change:fields',    _.bind(this.trigger, this, 'change:all'))

    // Cache (for performance reasons)
    var setCurrentField = _(function() {
      this.$currentField = this.getField(this.$position)
    }).bind(this)
    setCurrentField()
    this
      .bind('change:position', setCurrentField)
      .bind('change:fields',   setCurrentField)
  },

  // Overwrite backbone's set method for performance reasons
  set: function(attrs, options) {
    // - validations, - escaped attributes, - changes tracking
    if (attrs.attributes) attrs = attrs.attributes
    var silent = options && options.silent
    var change = false
    for (var attr in attrs) {
      change = true
      var val = attrs[attr]
      this.attributes[attr] = val
      this['$'+attr] = val
      if (!silent) this.trigger('change:'+attr)
    }
    if (change && !silent) this.trigger('change')
  },

  // Overwrite backbone's clone method
  clone: function() {
    // + make a copy of the attributes
    return new this.constructor(clone(this.attributes))
  },

  triggerChangeField: function(p) {
    this.trigger('change:field', p.x, p.y, this.getField(p))
    this.trigger('change')
  },

  getField: function(position) {
    return this.$fields[position.x][position.y]
  },

  forward: function() {
    return this.$position.plus(this.$direction)
  },

  istZiegel: function(n) {
    if (this.istWand()) return false
    var ziegel = this.getField(this.forward()).ziegel
    return n ? (ziegel == n) : !!ziegel
  },

  hinlegen: function() {
    if (this.istWand()) error("Karol kann keinen Ziegel hinlegen. Er steht vor einer Wand.")
    var nextPosition = this.forward()
    this.getField(nextPosition).ziegel += 1
    this.triggerChangeField(nextPosition)
  },

  aufheben: function() {
    if (this.istWand()) error("Karol kann keinen Ziegel aufheben. Er steht vor einer Wand.")
    var nextPosition = this.forward()
    var field = this.getField(nextPosition)
    if (!field.ziegel) error("Karol kann keinen Ziegel aufheben, da kein Ziegel vor ihm liegt.")
    field.ziegel--
    this.triggerChangeField(nextPosition)
  },

  markeSetzen: function() {
    this.$currentField.marke = true
    this.triggerChangeField(this.$position)
  },

  markeLoeschen: function() {
    this.$currentField.marke = false
    this.triggerChangeField(this.$position)
  },

  marke: function() {
    this.$currentField.marke = !this.$currentField.marke
    this.triggerChangeField(this.$position)
  },

  istMarke: function() {
    return this.$currentField.marke
  },

  isValid: function(position) {
    var x = position.x
    ,   z = position.y
    return x >= 0 && x < this.get('width') && z >= 0 && z < this.get('depth')
  },

  istWand: function() {
    var next = this.forward()
    return !this.isValid(next) || this.getField(next).quader
  },

  linksDrehen: function() {
    this.set({ direction: this.$direction.turnLeft() })
  },

  rechtsDrehen: function() {
    this.set({ direction: this.$direction.turnRight() })
  },

  schritt: function() {
    if (this.istWand()) error("Karol kann keinen Schritt machen, er steht vor einer Wand.")
    var newPosition = this.forward()
    if (Math.abs(this.$currentField.ziegel - this.getField(newPosition).ziegel) > 1) {
      error("Karol kann nur einen Ziegel pro Schritt nach oben oder unten springen.")
    }
    this.set({ position: newPosition })
  },

  schrittRueckwaerts: function() {
    this.probiere(_(function() {
      this.linksDrehen()
      this.linksDrehen()
      this.schritt()
      this.linksDrehen()
      this.linksDrehen()
    }).bind(this))
  },

  quader: function() {
    var position = this.forward()
    if (!this.isValid(position)) error("Karol kann keinen Quader hinlegen. Er steht vor einer Wand.")
    var field = this.getField(position)
    if (field.quader) error("Karol kann keinen Quader hinlegen, da schon einer liegt.")
    if (field.ziegel) error("Karol kann keinen Quader hinlegen, da auf dem Feld schon Ziegel liegen.")
    field.quader = true
    this.triggerChangeField(position)
  },

  entfernen: function() {
    var position = this.forward()
    if (!this.isValid(position)) error("Karol kann keinen Quader entfernen. Er steht vor einer Wand.")
    var field = this.getField(position)
    if (!field.quader) error("Karol kann keinen Quader entfernen, da auf dem Feld kein Quader liegt.")
    field.quader = false
    this.triggerChangeField(position)
  },

  ton: beep,

  istNorden: function() { return this.$direction.isNorth() },
  istSueden: function() { return this.$direction.isSouth() },
  istWesten: function() { return this.$direction.isWest() },
  istOsten:  function() { return this.$direction.isEast() },

  probiere: function(fn) {
    var clone = this.clone()
    try {
      return fn()
    } catch(exc) {
      this.set(clone)
      this.trigger('change:all')
      this.trigger('change')
    }
  },

  run: function(code) {
    this.backup = this.clone()

    var self = this
    this.execute(code, function(stack) {
      console.log('Commands: ', stack)
      self.stack = stack
    })
  },

  execute: function(code, callback) {
    // TODO: refactor?
    var iframe = document.createElement('iframe')
    iframe.style.display = 'none'
    document.body.appendChild(iframe)
    var win = iframe.contentWindow
    win.parent = null
    var karol = win.karol = {}
    var stack = []
    var self = this
    var timed = []
    var END_EXC = new Error('end')

    function stop(obj) {
      clearTimeout(obj)
      clearInterval(obj)
      if (typeof obj.abort == 'function') obj.abort()
    }

    function stopAll() {
      _(timed).each(stop)
      timed = []
    }

    function exec(fn) {
      try {
        fn()
      } catch (exc) {
        if (exc != END_EXC) {
          stack.push(exc)
        }
        stopAll()
      }
      end()
    }

    function end() {
      if (!timed.length) {
        document.body.removeChild(iframe)
        callback(stack)
      }
    }

    _.each(['istWand', 'schritt', 'linksDrehen', 'rechtsDrehen', 'hinlegen', 'aufheben', 'istZiegel', 'markeSetzen', 'markeLoeschen', 'istMarke', 'istNorden', 'istSueden', 'istWesten', 'istOsten', 'ton', 'probiere'], function(name) {
      karol[name] = function(n) {
        n = n || 1

        if (HIGHLIGHT_LINE) {
          try {
            error()
          } catch (exc) {
            var lineNumber = getLineNumber(exc.stack, 1)
          }
        } else {
          var lineNumber = null
        }

        if (self[name].length == 0) {
          for (var i = 0; i < n; i++) {
            var result = self[name]()
            stack.push([name, lineNumber])
          }
        } else {
          var result = self[name].apply(self, arguments)
          stack.push([name, lineNumber])
        }
        return result
      }
    })

    win.warten = function(fn, ms) {
      var timeout = setTimeout(function() {
        removeFromArray(timed, timeout)
        exec(fn)
      }, ms)
      timed.push(timeout)
      return timeout
    }

    win.periode = function(fn, ms) {
      var interval = setInterval(function() {
        exec(fn)
      }, ms)
      timed.push(interval)
      return interval
    }

    win.laden = function(opts, fn) {
      var req = $.ajax(opts)
        .success(function(responseText) {
          removeFromArray(timed, req)
          exec(_.bind(fn, null, responseText))
        })
        .error(function() {
          removeFromArray(timed, req)
        })
      timed.push(req)
      return req
    }

    win.stoppen = function(obj) {
      stop(obj)
      removeFromArray(timed, obj)
      end()
    }

    win.beenden = function() {
      throw END_EXC
    }

    exec(function() {
      win.document.write('<script>'+code+'</script>') // evil, I know
    })
  },

  next: function() {
    var pair = this.stack.shift()
    ,   command = pair[0]
    ,   lineNumber = pair[1]

    if (typeof command == 'string') {
      this[command]()
    } else if (command instanceof Error) {
      alert(command)
    }

    if (lineNumber) this.trigger('line', lineNumber)
  },

  replay: function() {
    this.reset()

    var self = this
    var interval = setInterval(function() {
      if (self.stack.length == 0) {
        clearInterval(interval)
      } else {
        self.next()
      }
    }, 150)
  },

  reset: function() {
    this.set(this.backup.attributes)
    this.trigger('change:all')
    this.trigger('change')
  },

  eachField: function(fn) {
    var fields = this.$fields
    ,   w = this.get('width')
    ,   d = this.get('depth')
    for (var x = 0; x < w; x++) {
      for (var y = 0; y < d; y++) {
        fn(x, y, fields[x][y])
      }
    }
  },

  toString: function() {
    var tokens = []
    var p = function(c) { tokens.push(c) }

    p('KarolVersion2Deutsch')

    p(this.get('width'))
    p(this.get('depth'))
    p(5)

    p(this.$position.x)
    p(this.$position.y)
    p(this.$currentField.ziegel)

    var fields = this.get('fields')
    var x = this.get('width')
    ,   y = this.get('depth')
    for (var i = 0; i < x; i++) {
      for (var j = 0; j < y; j++) {
        var field = fields[i][j]
        if (field.quader) tokens = tokens.concat(['q','q','n','n','n'])
        for (var k = 0; k < 5; k++) p(k < field.ziegel ? 'z' : 'n')
        p(field.marke ? 'm' : 'o')
      }
    }

    return tokens.join(' ')
  }

}, {

  Field: Field,
  Position: Position,
  Direction: Direction,

  fromString: function(str) {
    // Parse .kdw files

    var tokens = str.split(/\s/)
    var shift = _(tokens.shift).bind(tokens)
    var _int = function() { return parseInt(shift(), 10) }

    tokens.shift() // "KarolVersion2Deutsch"

    // Dimensions of the world
    var x = _int(), y = _int(), z = _int()

    // Position of the robot
    var px = _int(), py = _int(), pz = _int()

    var env = new Environment({
      width: x,
      depth: y,
      position: new Position(px, py)
    })
    var fields = env.get('fields')

    for (var i = 0; i < x; i++) {
      for (var j = 0; j < y; j++) {
        var field = new Field()
        if (tokens[0] == 'q') field.quader = true
        for (var k = 0; k < z; k++) {
          if (shift() == 'z') field.ziegel++
        }
        field.marke = (shift() == 'm')
        fields[i][j] = field
      }
    }

    return env
  }

})
<|endoftext|>


<commit_before>import React, { Fragment } from 'react'
import {
  Heading,
  Container,
  Card,
  Flex,
  Box,
  Text,
  LargeButton,
  Icon,
  Section,
  Link as A,
  radius
} from '@hackclub/design-system'
import Helmet from 'react-helmet'
import Link from 'gatsby-link'
import Nav from 'components/Nav'
import Stat from 'components/Stat'
import {
  Triangle,
  Hexagon,
  Pentagon,
  Square,
  Circle,
  Line
} from 'components/Shapes'
import DonateForm from 'components/donate/DonateForm'
import Spent from 'components/donate/Spent'
import Footer from 'components/Footer'
import commaNumber from 'comma-number'
import donors from 'components/donate/donors.json'

const Header = Section.withComponent('header').extend`
  background: url('/pattern.svg');
  > div {
    display: grid;
    grid-gap: ${({ theme }) => theme.space[4]}px;
    ${({ theme }) => theme.mediaQueries.md} {
      grid-template-columns: 3fr 2fr;
    }
  }
`

const Row = Box.extend`
  text-align: left;
  ${({ theme }) => theme.mediaQueries.md} {
    display: grid;
    grid-gap: ${({ theme }) => theme.space[3]}px;
    grid-template-columns: ${({ reverse }) =>
      reverse ? '3fr 2fr' : '2fr 3fr'};
  }
`

const Financials = Box.extend`
  display: grid;
  border-radius: ${({ theme }) => theme.radius};
  overflow: hidden;

  ${({ theme }) => theme.mediaQueries.md} {
    grid-template-columns: 2fr 3fr;
  }
`

const Stats = Box.extend`
  div {
    width: 100%;
    display: block;
    text-align: left !important;
  }
  span:before {
    content: '$';
    font-size: ${({ theme }) => theme.fontSizes[4]}px;
    margin-left: -12px;
    vertical-align: super;
  }
  p {
    color: ${({ theme }) => theme.colors.red[1]};
  }
`

const Shapes = Box.extend`
  display: none;
  ${({ theme }) => theme.mediaQueries.md} {
    display: block;
    float: right;
    position: relative;
    z-index: -1;
    svg {
      position: absolute;
    }
  }
`

const WishShapes = Shapes.extend`
  svg {
    &:first-child {
      top: 0;
      right: 0;
      color: rgba(115, 45, 228, 0.75);
      z-index: 1;
    }
    &:last-child {
      top: 0;
      right: 4rem;
      color: rgba(45, 228, 207, 0.75);
    }
  }
`
const ContributionShapes = Shapes.extend`
  svg {
    right: 12rem;
    top: 1rem;
    color: ${({ theme }) => theme.colors.pink[4]};
  }
`
const DonorsShapes = Shapes.extend`
  svg:first-child {
    right: 8rem;
    top: 2rem;
    color: ${({ theme }) => theme.colors.orange[4]};
  }
  svg:last-child {
    right: 4rem;
    top: 4rem;
    color: ${({ theme }) => theme.hexa('blue.4', 0.75)};
  }
`

const headline = { f: [5, 6], mb: 3, style: { lineHeight: '1.125' } }
const subhline = { f: [3, 4], style: { lineHeight: '1.375' } }
const subtext = { f: [3, 4], style: { lineHeight: '1.5' } }

const contentContainer = {
  maxWidth: 64,
  w: 1,
  p: 3,
  color: 'black',
  style: { position: 'relative' }
}
const content = { maxWidth: 48, mx: 0, color: 'black' }

A.link = A.withComponent(Link)

const title = 'Donate to Hack Club'
const description =
  'Contribute today to empower the next generation and help start a coding club at every high school.'

const stats = {
  monthly: 6742.71,
  student: 5,
  club: 100
}

const DonorCard = ({ name }) => (
  <Card bg="snow" p={3} m={2}>
    <Text {...subtext} color="inherit" children={name} />
  </Card>
)

const DonorListing = ({ name, url }) =>
  url ? (

      <DonorCard name={name} />

  ) : (
    <DonorCard name={name} />
  )

export default () => (
  <Fragment>
    <Helmet
      title={title}
      meta={[
        { name: 'twitter:title', content: title },
        { name: 'description', content: description },
        { name: 'twitter:description', content: description },
        { property: 'og:title', content: title },
        { property: 'og:description', content: description },
        { property: 'og:url', content: 'https://hackclub.com/donate' }
      ]}
    />

      <Container {...contentContainer} align="left" py={[4]}>
        <Container maxWidth={36} mx={0}>
          <Heading.h1 color="primary" f={[3, 4]} caps>
            Donate to Hack Club
          </Heading.h1>
          <Heading.h2 my={3} {...headline}>
            We rely on people like you to bring coding to the world.
          </Heading.h2>
          <Text {...subhline}>
            Contribute today to empower the next generation. Help start a Hack
            Club at every high school.
          </Text>
          <Text mt={3} f={2} color="muted">
            Your contribution is tax-deductible.

            Hack Club is a 501(c)(3) non-profit with the EIN 81-2908499.
          </Text>
        </Container>
        <Card bg="snow" p={[3, 4]} style={{ overflow: 'hidden' }}>
          <DonateForm />
        </Card>
      </Container>

    <Container {...contentContainer}>
      <WishShapes mt={3}>
        <Triangle size={128} rotate={64} />
        <Circle size={128} />
      </WishShapes>
      <Container {...content}>
        <Heading.h2 {...headline}>Transparent, free, and open.</Heading.h2>
        <Text my={3} {...subtext}>
          Hack Club is a new kind of non-profit with{' '}
          total transparency. We open source all of our{' '}
          content
          {' and '}
          code. Many of our leaders
          and members are also available on{' '}
          our Slack.
        </Text>
        <Text my={3} {...subtext}>
          You deserve to know exactly where your contribution will go—so{' '}

            all of our financials are public
          .
        </Text>
      </Container>
      <Row my={5} {...content}>
        <Box>
          <Heading.h2 {...headline}>
            Every last contribution is for our clubs.
          </Heading.h2>
          <ContributionShapes>
            <Pentagon size={128} rotate={16} />
          </ContributionShapes>
        </Box>
        <Box>
          <Text {...subtext}>
            We strive to build a maximally-efficient organization, spending 98%
            of funds given directly on our clubs.
          </Text>
          <Text mt={3} {...subtext}>
            When you give to Hack Club, your money goes where students need it
            most.
          </Text>
          <Financials mt={4}>
            <Box bg="primary" color="white" p={[3, 4]} pr={2}>
              <Heading.h3 f={4} mb={2} caps>
                Financials
              </Heading.h3>
              <Stats>
                <Stat
                  f={6}
                  value={commaNumber(stats.monthly)}
                  label="monthly spend"
                  mb={3}
                  w={1}
                />
                <Stat f={6} mb={2} value={stats.club} label="per club" />
                <Stat f={6} value={stats.student} label="per student" />
              </Stats>
            </Box>
            <Box bg="snow" p={[3, 4]}>
              <Heading.h3 f={4} my={0} caps>
                Spending breakdown
              </Heading.h3>
              <Spent />
            </Box>
          </Financials>
        </Box>
      </Row>
      <Container {...content}>
        <Heading.h2 {...headline}>
          Contribute more than just dollars.
        </Heading.h2>
        <Text my={3} {...subtext}>
          We accept donations of time, technical or hard science fiction books,
          stocks/other securities, and cryptocurrency.
        </Text>
        <Text my={3} {...subtext}>
          Please get in touch at{' '}
          donate@hackclub.com if you're
          interested in making a contribution or have any questions.
        </Text>
      </Container>
      <Row my={5} {...content}>
        <Box>
          <Heading.h2 {...headline}>Some of our amazing donors.</Heading.h2>
          <DonorsShapes>
            <Hexagon size={128} rotate={64} />
            <Circle size={128} />
          </DonorsShapes>
        </Box>
        <Flex m={-2} wrap>
          {Object.keys(donors).map(name => (
            <DonorListing key={name} name={name} url={donors[name]} />
          ))}
        </Flex>
      </Row>
    </Container>

  </Fragment>
)
<commit_msg>Use apostrophe on donate page<commit_after>import React, { Fragment } from 'react'
import {
  Heading,
  Container,
  Card,
  Flex,
  Box,
  Text,
  LargeButton,
  Icon,
  Section,
  Link as A,
  radius
} from '@hackclub/design-system'
import Helmet from 'react-helmet'
import Link from 'gatsby-link'
import Nav from 'components/Nav'
import Stat from 'components/Stat'
import {
  Triangle,
  Hexagon,
  Pentagon,
  Square,
  Circle,
  Line
} from 'components/Shapes'
import DonateForm from 'components/donate/DonateForm'
import Spent from 'components/donate/Spent'
import Footer from 'components/Footer'
import commaNumber from 'comma-number'
import donors from 'components/donate/donors.json'

const Header = Section.withComponent('header').extend`
  background: url('/pattern.svg');
  > div {
    display: grid;
    grid-gap: ${({ theme }) => theme.space[4]}px;
    ${({ theme }) => theme.mediaQueries.md} {
      grid-template-columns: 3fr 2fr;
    }
  }
`

const Row = Box.extend`
  text-align: left;
  ${({ theme }) => theme.mediaQueries.md} {
    display: grid;
    grid-gap: ${({ theme }) => theme.space[3]}px;
    grid-template-columns: ${({ reverse }) =>
      reverse ? '3fr 2fr' : '2fr 3fr'};
  }
`

const Financials = Box.extend`
  display: grid;
  border-radius: ${({ theme }) => theme.radius};
  overflow: hidden;

  ${({ theme }) => theme.mediaQueries.md} {
    grid-template-columns: 2fr 3fr;
  }
`

const Stats = Box.extend`
  div {
    width: 100%;
    display: block;
    text-align: left !important;
  }
  span:before {
    content: '$';
    font-size: ${({ theme }) => theme.fontSizes[4]}px;
    margin-left: -12px;
    vertical-align: super;
  }
  p {
    color: ${({ theme }) => theme.colors.red[1]};
  }
`

const Shapes = Box.extend`
  display: none;
  ${({ theme }) => theme.mediaQueries.md} {
    display: block;
    float: right;
    position: relative;
    z-index: -1;
    svg {
      position: absolute;
    }
  }
`

const WishShapes = Shapes.extend`
  svg {
    &:first-child {
      top: 0;
      right: 0;
      color: rgba(115, 45, 228, 0.75);
      z-index: 1;
    }
    &:last-child {
      top: 0;
      right: 4rem;
      color: rgba(45, 228, 207, 0.75);
    }
  }
`
const ContributionShapes = Shapes.extend`
  svg {
    right: 12rem;
    top: 1rem;
    color: ${({ theme }) => theme.colors.pink[4]};
  }
`
const DonorsShapes = Shapes.extend`
  svg:first-child {
    right: 8rem;
    top: 2rem;
    color: ${({ theme }) => theme.colors.orange[4]};
  }
  svg:last-child {
    right: 4rem;
    top: 4rem;
    color: ${({ theme }) => theme.hexa('blue.4', 0.75)};
  }
`

const headline = { f: [5, 6], mb: 3, style: { lineHeight: '1.125' } }
const subhline = { f: [3, 4], style: { lineHeight: '1.375' } }
const subtext = { f: [3, 4], style: { lineHeight: '1.5' } }

const contentContainer = {
  maxWidth: 64,
  w: 1,
  p: 3,
  color: 'black',
  style: { position: 'relative' }
}
const content = { maxWidth: 48, mx: 0, color: 'black' }

A.link = A.withComponent(Link)

const title = 'Donate to Hack Club'
const description =
  'Contribute today to empower the next generation and help start a coding club at every high school.'

const stats = {
  monthly: 6742.71,
  student: 5,
  club: 100
}

const DonorCard = ({ name }) => (
  <Card bg="snow" p={3} m={2}>
    <Text {...subtext} color="inherit" children={name} />
  </Card>
)

const DonorListing = ({ name, url }) =>
  url ? (

      <DonorCard name={name} />

  ) : (
    <DonorCard name={name} />
  )

export default () => (
  <Fragment>
    <Helmet
      title={title}
      meta={[
        { name: 'twitter:title', content: title },
        { name: 'description', content: description },
        { name: 'twitter:description', content: description },
        { property: 'og:title', content: title },
        { property: 'og:description', content: description },
        { property: 'og:url', content: 'https://hackclub.com/donate' }
      ]}
    />

      <Container {...contentContainer} align="left" py={[4]}>
        <Container maxWidth={36} mx={0}>
          <Heading.h1 color="primary" f={[3, 4]} caps>
            Donate to Hack Club
          </Heading.h1>
          <Heading.h2 my={3} {...headline}>
            We rely on people like you to bring coding to the world.
          </Heading.h2>
          <Text {...subhline}>
            Contribute today to empower the next generation. Help start a Hack
            Club at every high school.
          </Text>
          <Text mt={3} f={2} color="muted">
            Your contribution is tax-deductible.

            Hack Club is a 501(c)(3) non-profit with the EIN 81-2908499.
          </Text>
        </Container>
        <Card bg="snow" p={[3, 4]} style={{ overflow: 'hidden' }}>
          <DonateForm />
        </Card>
      </Container>

    <Container {...contentContainer}>
      <WishShapes mt={3}>
        <Triangle size={128} rotate={64} />
        <Circle size={128} />
      </WishShapes>
      <Container {...content}>
        <Heading.h2 {...headline}>Transparent, free, and open.</Heading.h2>
        <Text my={3} {...subtext}>
          Hack Club is a new kind of non-profit with{' '}
          total transparency. We open source all of our{' '}
          content
          {' and '}
          code. Many of our leaders
          and members are also available on{' '}
          our Slack.
        </Text>
        <Text my={3} {...subtext}>
          You deserve to know exactly where your contribution will go—so{' '}

            all of our financials are public
          .
        </Text>
      </Container>
      <Row my={5} {...content}>
        <Box>
          <Heading.h2 {...headline}>
            Every last contribution is for our clubs.
          </Heading.h2>
          <ContributionShapes>
            <Pentagon size={128} rotate={16} />
          </ContributionShapes>
        </Box>
        <Box>
          <Text {...subtext}>
            We strive to build a maximally-efficient organization, spending 98%
            of funds given directly on our clubs.
          </Text>
          <Text mt={3} {...subtext}>
            When you give to Hack Club, your money goes where students need it
            most.
          </Text>
          <Financials mt={4}>
            <Box bg="primary" color="white" p={[3, 4]} pr={2}>
              <Heading.h3 f={4} mb={2} caps>
                Financials
              </Heading.h3>
              <Stats>
                <Stat
                  f={6}
                  value={commaNumber(stats.monthly)}
                  label="monthly spend"
                  mb={3}
                  w={1}
                />
                <Stat f={6} mb={2} value={stats.club} label="per club" />
                <Stat f={6} value={stats.student} label="per student" />
              </Stats>
            </Box>
            <Box bg="snow" p={[3, 4]}>
              <Heading.h3 f={4} my={0} caps>
                Spending breakdown
              </Heading.h3>
              <Spent />
            </Box>
          </Financials>
        </Box>
      </Row>
      <Container {...content}>
        <Heading.h2 {...headline}>
          Contribute more than just dollars.
        </Heading.h2>
        <Text my={3} {...subtext}>
          We accept donations of time, technical or hard science fiction books,
          stocks/other securities, and cryptocurrency.
        </Text>
        <Text my={3} {...subtext}>
          Please get in touch at{' '}
          donate@hackclub.com if you’re
          interested in making a contribution or have any questions.
        </Text>
      </Container>
      <Row my={5} {...content}>
        <Box>
          <Heading.h2 {...headline}>Some of our amazing donors.</Heading.h2>
          <DonorsShapes>
            <Hexagon size={128} rotate={64} />
            <Circle size={128} />
          </DonorsShapes>
        </Box>
        <Flex m={-2} wrap>
          {Object.keys(donors).map(name => (
            <DonorListing key={name} name={name} url={donors[name]} />
          ))}
        </Flex>
      </Row>
    </Container>

  </Fragment>
)
<|endoftext|>