<commit_before>import https from 'https'
import http2 from 'http2'
import fs from 'fs'
import chokidar from 'chokidar'
import mimeTypes from 'mime-types'
import WebSocket from 'ws'
import os from 'os'

import { fileURLToPath } from 'url'
import { dirname } from 'path'

const directoryName = dirname(fileURLToPath(import.meta.url))

export default class Server {
  constructor({ path = '', watch = false, watchPath = '', watchIgnore = '', verbose = false, port = 3000 } = {}) {
    /**
     * Create HTTP2 Server
     */
    this.http2SecureServer = http2.createSecureServer({
      key: fs.readFileSync(`${directoryName}/server.key`),
      cert: fs.readFileSync(`${directoryName}/server.crt`),
    }, (request, response) => {
      const url = `.${request.url}`
      if (fs.existsSync(url) && fs.statSync(url).isDirectory() && !request.url.endsWith('/')) {
        response.writeHead(301, {
          'Location': `${request.url}/`,
        })
        response.end()
      }
    })

    this.http2SecureServer.on('error', (error) => {
      if (error.code === 'EADDRINUSE') {
        port++
        this.http2SecureServer.listen(port)
      } else {
        console.error(error)
      }
    })

    this.http2SecureServer.on('listening', () => {
      /**
       * Log currently used URLs
       */
      const networkInterfaces = os.networkInterfaces()
      console.log(`https://localhost:${port}/${path}`)
      for (const key of Object.keys(networkInterfaces)) {
        const networkInterfacesArray = networkInterfaces[key]
        for (const networkInterface of networkInterfacesArray) {
          if (networkInterface.family !== 'IPv4' || networkInterface.internal) {
            continue
          }
          console.log(`https://${networkInterface.address}:${port}/${path}`)
        }
      }
      console.log('\n')

      /**
       * Create WebSocket server to refresh page on file change
       */
      const webSocketServer = https.createServer({
        key: fs.readFileSync(`${directoryName}/server.key`),
        cert: fs.readFileSync(`${directoryName}/server.crt`),
      })
      this._wss = new WebSocket.Server({ server: webSocketServer })
      webSocketServer.listen(++port)

      if (watch) {
        chokidar.watch(watchPath, {
          ignored: watchIgnore,
          ignoreInitial: true,
        }).on('change', (path) => {
          if (verbose) {
            console.log(`${path} just changed, refresh.`)
          }
          this.refresh()
        })
      }
    })

    this.http2SecureServer.on('stream', (stream, headers) => {
      const requestAuthority = headers[http2.constants.HTTP2_HEADER_AUTHORITY]
      const requestScheme = headers[http2.constants.HTTP2_HEADER_SCHEME]
      const requestPath = headers[http2.constants.HTTP2_HEADER_PATH]
      const requestRange = headers[http2.constants.HTTP2_HEADER_RANGE]

      const url = new URL(`${requestScheme}://${requestAuthority}${requestPath}`)

      let filePath = `.${url.pathname}`

      /**
       * Rewrite to root if url doesn't exist and isn't a file
       */
      if (!/\.[^/]*$/.test(filePath) && !fs.existsSync(filePath)) {
        filePath = './'
      }

      /**
       * If path is a directory then set index.html file by default
       */
      if (fs.existsSync(filePath) && fs.statSync(filePath).isDirectory()) {
        filePath += filePath.endsWith('/') ? 'index.html' : '/index.html'
      }

      try {
        /**
         * Add socket code on html pages for live reloading
         */
        if (filePath.endsWith('.html')) {
          let fileContent = fs.readFileSync(filePath, {
            encoding: 'utf-8',
          })
          fileContent = fileContent.replace('', `
`)
          stream.end(fileContent)
        } else {
          const responseHeaders = {
            'content-type': String(mimeTypes.lookup(filePath)),
          }
          if (requestRange) {
            responseHeaders['Accept-Ranges'] = 'bytes'
          }
          stream.respondWithFile(decodeURIComponent(filePath), responseHeaders)
        }
      } catch (error) {
        if (error.code === 'ENOENT') {
          stream.respond({ ':status': http2.constants.HTTP_STATUS_NOT_FOUND })
        } else {
          stream.respond({ ':status': http2.constants.HTTP_STATUS_INTERNAL_SERVER_ERROR })
        }
        stream.end()
      }
    })

    this.http2SecureServer.listen(port)
  }

  refresh() {
    for (const client of this._wss.clients) {
      if (client.readyState === WebSocket.OPEN) {
        client.send()
      }
    }
  }
}
<commit_msg>server: fix watchIgnore<commit_after>import https from 'https'
import http2 from 'http2'
import fs from 'fs'
import chokidar from 'chokidar'
import mimeTypes from 'mime-types'
import WebSocket from 'ws'
import os from 'os'

import { fileURLToPath } from 'url'
import { dirname } from 'path'

const directoryName = dirname(fileURLToPath(import.meta.url))

export default class Server {
  constructor({ path = '', watch = false, watchPath = '', watchIgnore = undefined, verbose = false, port = 3000 } = {}) {
    /**
     * Create HTTP2 Server
     */
    this.http2SecureServer = http2.createSecureServer({
      key: fs.readFileSync(`${directoryName}/server.key`),
      cert: fs.readFileSync(`${directoryName}/server.crt`),
    }, (request, response) => {
      const url = `.${request.url}`
      if (fs.existsSync(url) && fs.statSync(url).isDirectory() && !request.url.endsWith('/')) {
        response.writeHead(301, {
          'Location': `${request.url}/`,
        })
        response.end()
      }
    })

    this.http2SecureServer.on('error', (error) => {
      if (error.code === 'EADDRINUSE') {
        port++
        this.http2SecureServer.listen(port)
      } else {
        console.error(error)
      }
    })

    this.http2SecureServer.on('listening', () => {
      /**
       * Log currently used URLs
       */
      const networkInterfaces = os.networkInterfaces()
      console.log(`https://localhost:${port}/${path}`)
      for (const key of Object.keys(networkInterfaces)) {
        const networkInterfacesArray = networkInterfaces[key]
        for (const networkInterface of networkInterfacesArray) {
          if (networkInterface.family !== 'IPv4' || networkInterface.internal) {
            continue
          }
          console.log(`https://${networkInterface.address}:${port}/${path}`)
        }
      }
      console.log('\n')

      /**
       * Create WebSocket server to refresh page on file change
       */
      const webSocketServer = https.createServer({
        key: fs.readFileSync(`${directoryName}/server.key`),
        cert: fs.readFileSync(`${directoryName}/server.crt`),
      })
      this._wss = new WebSocket.Server({ server: webSocketServer })
      webSocketServer.listen(++port)

      if (watch) {
        chokidar.watch(watchPath, {
          ignored: watchIgnore,
          ignoreInitial: true,
        }).on('change', (path) => {
          if (verbose) {
            console.log(`${path} just changed, refresh.`)
          }
          this.refresh()
        })
      }
    })

    this.http2SecureServer.on('stream', (stream, headers) => {
      const requestAuthority = headers[http2.constants.HTTP2_HEADER_AUTHORITY]
      const requestScheme = headers[http2.constants.HTTP2_HEADER_SCHEME]
      const requestPath = headers[http2.constants.HTTP2_HEADER_PATH]
      const requestRange = headers[http2.constants.HTTP2_HEADER_RANGE]

      const url = new URL(`${requestScheme}://${requestAuthority}${requestPath}`)

      let filePath = `.${url.pathname}`

      /**
       * Rewrite to root if url doesn't exist and isn't a file
       */
      if (!/\.[^/]*$/.test(filePath) && !fs.existsSync(filePath)) {
        filePath = './'
      }

      /**
       * If path is a directory then set index.html file by default
       */
      if (fs.existsSync(filePath) && fs.statSync(filePath).isDirectory()) {
        filePath += filePath.endsWith('/') ? 'index.html' : '/index.html'
      }

      try {
        /**
         * Add socket code on html pages for live reloading
         */
        if (filePath.endsWith('.html')) {
          let fileContent = fs.readFileSync(filePath, {
            encoding: 'utf-8',
          })
          fileContent = fileContent.replace('', `
`)
          stream.end(fileContent)
        } else {
          const responseHeaders = {
            'content-type': String(mimeTypes.lookup(filePath)),
          }
          if (requestRange) {
            responseHeaders['Accept-Ranges'] = 'bytes'
          }
          stream.respondWithFile(decodeURIComponent(filePath), responseHeaders)
        }
      } catch (error) {
        if (error.code === 'ENOENT') {
          stream.respond({ ':status': http2.constants.HTTP_STATUS_NOT_FOUND })
        } else {
          stream.respond({ ':status': http2.constants.HTTP_STATUS_INTERNAL_SERVER_ERROR })
        }
        stream.end()
      }
    })

    this.http2SecureServer.listen(port)
  }

  refresh() {
    for (const client of this._wss.clients) {
      if (client.readyState === WebSocket.OPEN) {
        client.send()
      }
    }
  }
}
<|endoftext|>


<commit_before>$(document).ready(function(){

  $('#from-date').datepicker({
		dateFormat: 'dd/mm/yy'
	});

	$('#to-date').datepicker({
		dateFormat: 'dd/mm/yy'
	});

	var zero_pad = function(num,count)
  {
    var numZeropad = num + '';
    while(numZeropad.length < count) {
      numZeropad = "0" + numZeropad;
    }
    return numZeropad;
  }
	var today = new Date();
	var day = zero_pad(today.getDate(), 2);
	var month = zero_pad(today.getMonth() + 1, 2);
	var year = today.getFullYear();
	$("#to-date").attr('value',  day + "/" + month + "/" + year);

  var spinner_options = {
    lines: 7, // The number of lines to draw
    length: 2, // The length of each line
    width: 4, // The line thickness
    radius: 1, // The radius of the inner circle
    color: '#000', // #rbg or #rrggbb
    speed: 0.9, // Rounds per second
    trail: 60, // Afterglow percentage
    shadow: false // Whether to render a shadow
  };

	// LETS GO!!
  $("#submit_form").click(function(){

    $("label").removeClass("error");

    var spin = new Spinner(spinner_options).spin();
    $("form").append("

");
    $("#spinner").append(spin.el);

    var user = $.trim($("#user").attr("value")), count = 0, total_retweets = 0;
    var start_date = null, end_date = null;
    var from_date = $.trim($('#from-date').attr("value")), to_date = $.trim($('#to-date').attr("value"));

    // reset some things
    $("#tweets").html("");
    $("ul#errors").remove();
    $("#download").remove();
    $("#tweet-table").remove();
    $("p.dates").hide();
    $("p.total").hide();

    if(user == ""){
      // error
      render_errros("Oh no, you haven't entered a user name.", ["user"]);
      return false;
    }

    if(from_date == ""){
      render_errros("Oops, you haven't filled in the "from date" field.", ["from-date"]);
      return false;
    }
    if(to_date == ""){
      render_errros("Wha! you haven't filled in the "to date" field.", ["to-date"]);
      return false;
    }

    var r = /(\d*)\/(\d*)\/(\d*)/;
    from_date = r.exec(from_date);
    to_date = r.exec(to_date);
    var from_date = new Date(from_date[3],from_date[2] - 1,from_date[1]);
    var to_date = new Date(to_date[3],to_date[2] - 1,to_date[1]);
    var today = new Date();
    if(from_date > today){
      render_errros("Oopsy, the selected "from date" is in the future.", ["from-date"]);
      return false;
    }
    if(to_date > today){
      render_errros("Wha! the selected "to date" is in the future.", ["to-date"]);
      return false;
    }

    if(from_date > to_date){
      // error
      render_errros("Uh oh, the "from date" is greater than "to date".", ["from-date", "to-date"]);
      return false;
    }

    render_table();

    var total_tweets = 0;
    twitterlib.timeline(user, { limit: 200 }, function (tweets, options) {
      for(var x = 0; x < tweets.length; x = x+1){
        var retweets = tweets[x].retweet_count;
        var date = new Date(tweets[x].created_at);
        if(date < to_date && date > from_date){
          if(retweets > 0){
            total_retweets = total_retweets + parseInt(retweets);
            var text = this.ify.clean(tweets[x].text);
            //$("#tweets").append(li_html({user: user, id: tweets[x].id_str, text: text, created_at: tweets[x].created_at, retweets: retweets}));
            add_table_row(date, text, retweets, user, tweets[x].id_str);
            if(start_date === null){
              start_date = tweets[x].created_at;
            }
            end_date = tweets[x].created_at;
          } // end retweet check
        } // end date check
      } // end for

      $("#total").html(total_retweets);
      $("p.total").show();
      $("#start_date").html(start_date);
      $("#end_date").html(end_date);
      $("p.dates").show();
      total_tweets = total_tweets + tweets.length;
      //console.log(total_tweets);
      count++;
      if (date > from_date && total_tweets < 3200) {
        this.next();
      }
      else{
        $("#spinner").remove();
        render_download_button(generate_filename(), generate_data());
        // downloadify_options = {
        //   swf: 'media/downloadify.swf',
        //   downloadImage: 'media/download_csv.png',
        //   width: 100,
        //   height: 32,
        //   filename: generate_filename(),
        //   data: generate_data(),
        //   dataType: 'string',
        //   transparent: true,
        //   append: false
        // };
        // $("#download").downloadify( downloadify_options );
      }
    }); // end twitterlib

    return false;
  }); // end click

  var li_html = function(info){
    var html = "

" + info.text +  "
";
    html = html + "
" + info.created_at + ", Retweets: " + info.retweets + "

"
    return html;
  }

  var render_table = function(){
    var $table = $("table#tweet-table");
    if($table.length <= 0){
      $("#content").append("
 \

 \
      Date	 \
      Tweet	 \
      Retweets	 \
      Link	 \

 \

");
      $table = $("table");
    }
    else{
      $("table#tweet-table").html("");
    }
    return $("table#tweet-table");
  }

  var add_table_row = function(date, tweet, retweets, user, id){
    var link = "http://www.twitter.com/" + user + "/status/" + id;
    var row = "
 " + date + "	 " + tweet + "	 " + retweets + "	 " + link + "
";
    $("table").append(row);
  }

  var render_download_button = function(filename, data){
    var $dl = $("#download");

    if($dl.length <= 0){
      $("p.dates.meta").after('
Download CSV
');
    }
    $('#download a').attr('href', 'data:text/csv;charset=utf-8,' + encodeURIComponent(data));
    return $("#download");
  }

  var render_errros = function(error, els){
    var $ul = $("ul#errors");
    var li = null;
    if($ul.length <= 0){
      $("form#form_options").before("

");
      $ul = $("ul#errors");
    }
    li = '
' + error + '
';
    for(var x = 0; x < els.length; x = x + 1){
      $("#" + els[x]).prev().addClass("error");
    }
    $ul.append(li);
    $("#spinner div").remove();
    return false;
  }

  $.fn.spin = function(opts) {
    this.each(function() {
      var $this = $(this), spinner = $this.data('spinner');

      if (spinner) spinner.stop();
      if (opts !== false) {
        opts = $.extend({color: $this.css('color')}, opts);
        spinner = new Spinner(opts).spin(this);
        $this.data('spinner', spinner);
      }
    });
    return this;
  };

  var generate_filename = function(){
    var dates = $("p.dates.meta span");
    var user = $("#user").attr("value");
    var from = $("#from-date").attr("value").replace(/\//g, '-');
    var to = $("#to-date").attr("value").replace(/\//g, '-');
    var name = "retweets_for_" + user + "_" + from + "_-_" + to + ".csv"; // +;
    return name;
  }
  var generate_data = function(){
    var data = "";
    $("table#tweet-table tr").each(function(){
      $(this).children().each(function(){
        var cell = $(this).html().replace('"', '\"', 'g');
        cell = cell.replace('\'', "\'", 'g');
        cell = cell.replace(/(<([^>]+)>)/ig,"");
        cell = Encoder.htmlDecode(cell);
        data = data + '"' + cell + '",';
      });
      data = data + "\n";
    });
    console.log(data);
    return data;
  }

});

/* dependency: jquery */
var downloadDataURI = function(options) {
  if(!options) {
    return;
  }
  $.isPlainObject(options) || (options = {data: options});
  if(!$.browser.webkit) {
    location.href = options.data;
  }
  options.filename || (options.filename = "download." + options.data.split(",")[0].split(";")[0].substring(5).split("/")[1]);
  options.url || (options.url = "http://download-data-uri.appspot.com/");
  $('<form method="post" action="'+options.url+'" style="display:none"><input type="hidden" name="filename" value="'+options.filename+'"/><input type="hidden" name="data" value="'+options.data+'"/></form>').submit().remove();
}<commit_msg>Remove dumb downloadDataURI funtion as it didnt work<commit_after>$(document).ready(function(){

  $('#from-date').datepicker({
		dateFormat: 'dd/mm/yy'
	});

	$('#to-date').datepicker({
		dateFormat: 'dd/mm/yy'
	});

	var zero_pad = function(num,count)
  {
    var numZeropad = num + '';
    while(numZeropad.length < count) {
      numZeropad = "0" + numZeropad;
    }
    return numZeropad;
  }
	var today = new Date();
	var day = zero_pad(today.getDate(), 2);
	var month = zero_pad(today.getMonth() + 1, 2);
	var year = today.getFullYear();
	$("#to-date").attr('value',  day + "/" + month + "/" + year);

  var spinner_options = {
    lines: 7, // The number of lines to draw
    length: 2, // The length of each line
    width: 4, // The line thickness
    radius: 1, // The radius of the inner circle
    color: '#000', // #rbg or #rrggbb
    speed: 0.9, // Rounds per second
    trail: 60, // Afterglow percentage
    shadow: false // Whether to render a shadow
  };

	// LETS GO!!
  $("#submit_form").click(function(){

    $("label").removeClass("error");

    var spin = new Spinner(spinner_options).spin();
    $("form").append("

");
    $("#spinner").append(spin.el);

    var user = $.trim($("#user").attr("value")), count = 0, total_retweets = 0;
    var start_date = null, end_date = null;
    var from_date = $.trim($('#from-date').attr("value")), to_date = $.trim($('#to-date').attr("value"));

    // reset some things
    $("#tweets").html("");
    $("ul#errors").remove();
    $("#download").remove();
    $("#tweet-table").remove();
    $("p.dates").hide();
    $("p.total").hide();

    if(user == ""){
      // error
      render_errros("Oh no, you haven't entered a user name.", ["user"]);
      return false;
    }

    if(from_date == ""){
      render_errros("Oops, you haven't filled in the "from date" field.", ["from-date"]);
      return false;
    }
    if(to_date == ""){
      render_errros("Wha! you haven't filled in the "to date" field.", ["to-date"]);
      return false;
    }

    var r = /(\d*)\/(\d*)\/(\d*)/;
    from_date = r.exec(from_date);
    to_date = r.exec(to_date);
    var from_date = new Date(from_date[3],from_date[2] - 1,from_date[1]);
    var to_date = new Date(to_date[3],to_date[2] - 1,to_date[1]);
    var today = new Date();
    if(from_date > today){
      render_errros("Oopsy, the selected "from date" is in the future.", ["from-date"]);
      return false;
    }
    if(to_date > today){
      render_errros("Wha! the selected "to date" is in the future.", ["to-date"]);
      return false;
    }

    if(from_date > to_date){
      // error
      render_errros("Uh oh, the "from date" is greater than "to date".", ["from-date", "to-date"]);
      return false;
    }

    render_table();

    var total_tweets = 0;
    twitterlib.timeline(user, { limit: 200 }, function (tweets, options) {
      for(var x = 0; x < tweets.length; x = x+1){
        var retweets = tweets[x].retweet_count;
        var date = new Date(tweets[x].created_at);
        if(date < to_date && date > from_date){
          if(retweets > 0){
            total_retweets = total_retweets + parseInt(retweets);
            var text = this.ify.clean(tweets[x].text);
            //$("#tweets").append(li_html({user: user, id: tweets[x].id_str, text: text, created_at: tweets[x].created_at, retweets: retweets}));
            add_table_row(date, text, retweets, user, tweets[x].id_str);
            if(start_date === null){
              start_date = tweets[x].created_at;
            }
            end_date = tweets[x].created_at;
          } // end retweet check
        } // end date check
      } // end for

      $("#total").html(total_retweets);
      $("p.total").show();
      $("#start_date").html(start_date);
      $("#end_date").html(end_date);
      $("p.dates").show();
      total_tweets = total_tweets + tweets.length;
      //console.log(total_tweets);
      count++;
      if (date > from_date && total_tweets < 3200) {
        this.next();
      }
      else{
        $("#spinner").remove();
        render_download_button(generate_filename(), generate_data());
        // downloadify_options = {
        //   swf: 'media/downloadify.swf',
        //   downloadImage: 'media/download_csv.png',
        //   width: 100,
        //   height: 32,
        //   filename: generate_filename(),
        //   data: generate_data(),
        //   dataType: 'string',
        //   transparent: true,
        //   append: false
        // };
        // $("#download").downloadify( downloadify_options );
      }
    }); // end twitterlib

    return false;
  }); // end click

  var li_html = function(info){
    var html = "

" + info.text +  "
";
    html = html + "
" + info.created_at + ", Retweets: " + info.retweets + "

"
    return html;
  }

  var render_table = function(){
    var $table = $("table#tweet-table");
    if($table.length <= 0){
      $("#content").append("
 \

 \
      Date	 \
      Tweet	 \
      Retweets	 \
      Link	 \

 \

");
      $table = $("table");
    }
    else{
      $("table#tweet-table").html("");
    }
    return $("table#tweet-table");
  }

  var add_table_row = function(date, tweet, retweets, user, id){
    var link = "http://www.twitter.com/" + user + "/status/" + id;
    var row = "
 " + date + "	 " + tweet + "	 " + retweets + "	 " + link + "
";
    $("table").append(row);
  }

  var render_download_button = function(filename, data){
    var $dl = $("#download");

    if($dl.length <= 0){
      $("p.dates.meta").after('
Download CSV
');
    }
    $('#download a').attr('href', 'data:text/csv;charset=utf-8,' + encodeURIComponent(data));
    return $("#download");
  }

  var render_errros = function(error, els){
    var $ul = $("ul#errors");
    var li = null;
    if($ul.length <= 0){
      $("form#form_options").before("

");
      $ul = $("ul#errors");
    }
    li = '
' + error + '
';
    for(var x = 0; x < els.length; x = x + 1){
      $("#" + els[x]).prev().addClass("error");
    }
    $ul.append(li);
    $("#spinner div").remove();
    return false;
  }

  $.fn.spin = function(opts) {
    this.each(function() {
      var $this = $(this), spinner = $this.data('spinner');

      if (spinner) spinner.stop();
      if (opts !== false) {
        opts = $.extend({color: $this.css('color')}, opts);
        spinner = new Spinner(opts).spin(this);
        $this.data('spinner', spinner);
      }
    });
    return this;
  };

  var generate_filename = function(){
    var dates = $("p.dates.meta span");
    var user = $("#user").attr("value");
    var from = $("#from-date").attr("value").replace(/\//g, '-');
    var to = $("#to-date").attr("value").replace(/\//g, '-');
    var name = "retweets_for_" + user + "_" + from + "_-_" + to + ".csv"; // +;
    return name;
  }
  var generate_data = function(){
    var data = "";
    $("table#tweet-table tr").each(function(){
      $(this).children().each(function(){
        var cell = $(this).html().replace('"', '\"', 'g');
        cell = cell.replace('\'', "\'", 'g');
        cell = cell.replace(/(<([^>]+)>)/ig,"");
        cell = Encoder.htmlDecode(cell);
        data = data + '"' + cell + '",';
      });
      data = data + "\n";
    });
    console.log(data);
    return data;
  }

});<|endoftext|>


<commit_before>var
	fs = require('fs'),
	url = require('url'),
	path = require('path'),
	exec = require('child_process').exec,
	jsdom = require('jsdom'),
	dom = require('./dom'),
	jady = require('./jady');

var
	opts = require('../options'),
	publicPath = opts.common.pathToSpecs;

module.exports = function reply(req, res, next) {
	var
		parsedUrl = url.parse(req.url, true),
		urlPath = parsedUrl.pathname,
		urlHost = req.headers.host,
		urlAdress = (parsedUrl.protocol || "") + urlHost + urlPath,
		tpl = parsedUrl.query.get,
		id = parsedUrl.query.id,
		wrap = parsedUrl.query.wrap || true,
        phantom = parsedUrl.query.ph || false;
//debugger;

//// if we have query on index.html
	if (path.basename(parsedUrl.path).indexOf('index.html') != -1 && parsedUrl.query.get) {
// reading file..
		fs.readFile(publicPath + '/' + urlPath, function (err, data) {
            var responseData = data || '';

            if (err) res.end('Huston, we have 404.\n'+ err);

            if (responseData !== '') {

                // make data for template
                function reqHandler(res, html) {
                    if (html.source) {
                        //// переменные для Jade
                        var locals = {
                            head: {
                                title: html.title,
                                mAuthor: html.meta.author,
                                mKeywords: html.meta.keywords,
                                mDescription: html.meta.description,
                                scripts: html.scripts,
                                stylesheets: html.styles
                            },
                            body: {
                                spec: html.source.content,
                                specLength: html.source.length,
                                specId: html.source.id,
                                specIdSum: html.source.idSum,
                                homeLink: 'http://'+ urlAdress
                            },
                            pretty: true
                        };

                        res.writeHead(200, {'Content-Type': 'text/html; charset=utf-8'});
                        res.end(jady(locals, tpl));

                    } else res.end('STDOUT: can\'t recieve content.');
                }

        // if using PhantomJs
                if (phantom) {

                    var params = "sudo core/clarify/phantomjs "+
                        "core/clarify/phantom/ph.js "+
                        "http://"+ urlAdress +" "+ id +" "+ wrap;

                    // executes ph.js via phantomjs like separate child process
                    exec(params, function (err, stdout, stderr) {
                        if (err) console.log('Exec report error: ' + err);
                        else {
                            try {
                                var html = JSON.parse(stdout);
                            } catch(e) {
                                html = 'Parsing error: ' + e;
                            }
        // PhantomJS output
        console.log(html);
        // got to show some view
                            reqHandler(res, html);
                        }
                    });

                }
        // jsdom starts
                else {
                    jsdom.env(responseData.toString(), function (err, win) {
        // 	     		jsdom.env(publicPath + '/' + urlPath, function (err, win) { // url mode
                        if (err) console.log('JSdom report error: ' + err);
                        else {
                            console.log('JSDOM', wrap);
                            var
                                doc = win.document,
                                html = {};

                            try {
                                html.title = doc.title;
                                html.meta = dom.getMeta(doc);
                                html.styles = dom.getHeadData(doc)[0];
                                html.scripts = dom.getHeadData(doc)[1];
                                html.source = dom.getSource(doc, id, wrap);
                            } catch (e) {
                                html.err = {
                                    text: e,
                                    type: e.name
                                };
                            }
        console.log(html);

        // got to show some view
                            reqHandler(res, html);
                        }
                    });
                }

            }
		});
	} else next();
};

// TODO: check list below
// [done] beatify HTML output
// [done] create JSON with data from <HEAD>
// [done] parse several blocks in same page with one request
// [done] switchers to another specs from cleared one;
// [..partial] client-side UI controls to clarify specs
// [done] phantomjs -> jsdom
// [...] support for other template engines
// * [] connect custom templates and scripts
// * [] avoid hardcoded paths
// [done] clear template - @param {GET} clr
// * [] use css/js optionally by GET params
// * [] save user session settings
// * [] try POST instead GET
// * [] Ajax
// * [] link from already clarified code to original spec page
// * [] phantomjs error with try to get unavaliable script
// * [] screenshots by phatnomjs
// * [] phantomjs: not to close session (improve perfomance?);
// * [] buttons  to add custom libraries to clarified page (jQuery, require);
// * [] another context templates [mob, clr, ...]
<commit_msg>Updating TODOs in Clarify<commit_after>var
	fs = require('fs'),
	url = require('url'),
	path = require('path'),
	exec = require('child_process').exec,
	jsdom = require('jsdom'),
	dom = require('./dom'),
	jady = require('./jady');

var
	opts = require('../options'),
	publicPath = opts.common.pathToSpecs;

module.exports = function reply(req, res, next) {
	var
		parsedUrl = url.parse(req.url, true),
		urlPath = parsedUrl.pathname,
		urlHost = req.headers.host,
		urlAdress = (parsedUrl.protocol || "") + urlHost + urlPath,
		tpl = parsedUrl.query.get,
		id = parsedUrl.query.id,
		wrap = parsedUrl.query.wrap || true,
        phantom = parsedUrl.query.ph || false;
//debugger;

//// if we have query on index.html
	if (path.basename(parsedUrl.path).indexOf('index.html') != -1 && parsedUrl.query.get) {
// reading file..
		fs.readFile(publicPath + '/' + urlPath, function (err, data) {
            var responseData = data || '';

            if (err) res.end('Huston, we have 404.\n'+ err);

            if (responseData !== '') {

                // make data for template
                function reqHandler(res, html) {
                    if (html.source) {
                        //// переменные для Jade
                        var locals = {
                            head: {
                                title: html.title,
                                mAuthor: html.meta.author,
                                mKeywords: html.meta.keywords,
                                mDescription: html.meta.description,
                                scripts: html.scripts,
                                stylesheets: html.styles
                            },
                            body: {
                                spec: html.source.content,
                                specLength: html.source.length,
                                specId: html.source.id,
                                specIdSum: html.source.idSum,
                                homeLink: 'http://'+ urlAdress
                            },
                            pretty: true
                        };

                        res.writeHead(200, {'Content-Type': 'text/html; charset=utf-8'});
                        res.end(jady(locals, tpl));

                    } else res.end('STDOUT: can\'t recieve content.');
                }

        // if using PhantomJs
                if (phantom) {

                    var params = "sudo core/clarify/phantomjs "+
                        "core/clarify/phantom/ph.js "+
                        "http://"+ urlAdress +" "+ id +" "+ wrap;

                    // executes ph.js via phantomjs like separate child process
                    exec(params, function (err, stdout, stderr) {
                        if (err) console.log('Exec report error: ' + err);
                        else {
                            try {
                                var html = JSON.parse(stdout);
                            } catch(e) {
                                html = 'Parsing error: ' + e;
                            }
        // PhantomJS output
        console.log(html);
        // got to show some view
                            reqHandler(res, html);
                        }
                    });

                }
        // jsdom starts
                else {
                    jsdom.env(responseData.toString(), function (err, win) {
        // 	     		jsdom.env(publicPath + '/' + urlPath, function (err, win) { // url mode
                        if (err) console.log('JSdom report error: ' + err);
                        else {
                            console.log('JSDOM', wrap);
                            var
                                doc = win.document,
                                html = {};

                            try {
                                html.title = doc.title;
                                html.meta = dom.getMeta(doc);
                                html.styles = dom.getHeadData(doc)[0];
                                html.scripts = dom.getHeadData(doc)[1];
                                html.source = dom.getSource(doc, id, wrap);
                            } catch (e) {
                                html.err = {
                                    text: e,
                                    type: e.name
                                };
                            }
        console.log(html);

        // got to show some view
                            reqHandler(res, html);
                        }
                    });
                }

            }
		});
	} else next();
};

// TODO: check list below
// [done] beatify HTML output
// [done] create JSON with data from <HEAD>
// [done] parse several blocks in same page with one request
// [done] switchers to another specs from cleared one;
// [done] clear template - @param {GET} clr
// [done] phantomjs -> jsdom
// [..partial] client-side UI controls to clarify specs
// [...] support for other template engines
// * [] Add check for OS, if not Mac, then link to other phantomjs binary, that need to be git ignored and custom description about the lack of it with installation instructions must be provided
// * [] connect custom templates and scripts
// * [] avoid hardcoded paths
// * [] use css/js optionally by GET params
// * [] save user session settings
// * [] try POST instead GET
// * [] Ajax
// * [] link from already clarified code to original spec page
// * [] phantomjs error with try to get unavaliable script
// * [] screenshots by phatnomjs
// * [] phantomjs: not to close session (improve perfomance?);
// * [] buttons  to add custom libraries to clarified page (jQuery, require);
// * [] another context templates [mob, clr, ...]
<|endoftext|>


<commit_before><script>
/*
===================================
| DATALAYER ARCHITECTURE: SHOPIFY |
-----------------------------------

DEFINITION:
A data layer helps you collect more accurate analytics data, that in turn allows you to better understand what potential buyers are doing on your website and where you can make improvements. It also reduces the time to implement marketing tags on a website, and reduces the need for IT involvement, leaving them to get on with implementing new features and fixing bugs.

RESOURCES:
http://www.datalayerdoctor.com/a-gentle-introduction-to-the-data-layer-for-digital-marketers/
http://www.simoahava.com/analytics/data-layer/

AUTHORS:
Mechelle Warneke = [{
Email: mechellewarneke@gmail.com,
Website: mechellewarneke.com,
BVACCEL: [{
    Email: mechelle@bvaccel.com,
    Position: XO Strategist | Technical Web Analyst
}]
}];

Tyler Shambora = [{
Website: tylershambora.com,
BVACCEL: [{
    Email: tyler@bvaccel.com,
    Position: Lead Web Developer
}]
}];

EXTERNAL DEPENDENCIES:
* jQuery Cookie Plugin v1.4.1 - https://github.com/carhartl/jquery-cookie

DataLayer Architecture: Shopify v1.3.1
COPYRIGHT 2016
LICENSES: MIT ( https://opensource.org/licenses/MIT )
*/

/* =======================
| PREREQUISITE LIBRARIES |
----------------------- */

// jquery-cookies.js

/* ======================
| Begin dataLayer Build |
---------------------- */

console.log("----------------------\nBEGIN DATALAYER BUILD\nTHEME PAGES\n----------------------");
window.dataLayer = window.dataLayer || []
console.log('Page Template = {{template}}');
var template = '{{template}}';

/* Landing Page Cookie
-----------------------
1. Detect if user just landed on the site
2. Only fires if Page Title matches website */

$.cookie.raw = true;

if( $.cookie('landingPage') === undefined || $.cookie('landingPage').length === 0 ){

	$.cookie('landingPage', unescape);
	$.removeCookie('landingPage',{ path: '/' });
	$.cookie('landingPage', 'landed',{ path: '/' });
	var landingPage = true;
	console.log("Landing Page = True");

}else{

	$.cookie('landingPage', unescape);
	$.removeCookie('landingPage',{ path: '/' });
	$.cookie('landingPage', 'refresh',{ path: '/' });
	console.log("Landing Page = False");

}

/* Log State Cookie
------------------- */

{% if customer %}
var logState = "Logged In";
{% endif %}

if( typeof logState == "undefined" ){
	var logState = "Logged Out";
	$.cookie('logState', unescape);
	$.removeCookie('logState',{ path: '/' });
	$.cookie('logState', 'loggedOut',{ path: '/' })
	console.log("Log State = Logged Out");
}

if( logState === "Logged In"){
	if( $.cookie('logState') === "loggedOut" || $.cookie('logState') === undefined ){

		$.cookie('logState', unescape);
		$.removeCookie('logState',{ path: '/' });
		$.cookie('logState', 'firstLog',{ path: '/' });
		console.log("Log State = Logged In");

	}else if( $.cookie("logState") === "firstLog"  ){

		$.cookie('logState', unescape);
		$.removeCookie('logState',{ path: '/' });
		$.cookie('logState', 'refresh',{ path: '/' });
		console.log("Log State = Logged In");

	}else{
		console.log("Log State = Logged In");
	}
}

if( $.cookie("logState") === "firstLog"  ){
	var firstLog = "True";
}else{
	var firstLog = "False";
}
console.log("First Log = "+firstLog);

    // TIMESTAMP
    var ts = Date.now();
    console.log("Timestamp = "+ts);

/* ==========
| DATALAYERS |
----------- */

/* DATALAYER: Landing Page
--------------------------
Fires any time a user first lands on the site. */

if( $.cookie("landingPage") === "landed"  ){
	dataLayer.push({
		'pageType':'Landing Page',
		'event':'Landing Page'
	});
}

/* DATALAYER: Log State
-----------------------
1. Determine if user is logged in or not.
2. Return User specific data. */

dataLayer.push({
	{% if shop.customer_accounts_enabled %}
	{% if customer %}
	'userId': '{{customer.id}}',
	'customerEmail': '{{customer.email}}',
	'logState': "Logged In",
	{% else %}
	'logState': "Logged Out",
	{% endif %}
	{% endif %}
	'firstLog': firstLog,
	'clientId': cid[0],
	'customerEmail': '{{customer.email}}',
	'timestamp': ts
},{'event':'Log State'});

/* DATALAYER: Checkout
-------------------------- */

{% if template contains "checkout" %}
if (Shopify.Checkout.step === 'contact_information'){
	dataLayer.push({
		'event':'Checkout',
		'event':'Customer Information'});
}else if (Shopify.Checkout.step === 'shipping_method'){
	dataLayer.push({
		'event':'Shipping Information'});
}else if( Shopify.Checkout.step === "payment_method" ){
	dataLayer.push({
		'event':'Add Payment Info'});
}
{% endif %}

/* DATALAYER: Confirmation
-------------------------- */

if(Shopify.Checkout.page == "thank_you"){
	{% if first_time_accessed %}
	dataLayer.push({
		'transactionId': '{{checkout.order_id}}',
		'transactionNumber': '{{checkout.order_number}}',
		'transactionAffiliation': '{{shop.name}}',
		'transactionTotal': '{{checkout.total_price |  money_without_currency}}',
		'transactionTax': '{{checkout.total_price |  money_without_currency}}',
		'transactionShipping': '{{checkout.shipping_price |  money_without_currency}}',

		{% for discount in checkout.discounts %}
		'promoCode':'{{discount.code}}',
		'discount': '{{discount.amount | money_without_currency}}',
		{% endfor %}

		'transactionProducts': [

		{% for line_item in line_items %}
		{
			'id': '{{line_item.product_id}}',
			'sku': '{{line_item.sku}}',
			'variantId': '{{line_item.variant_id}}',
			'name': '{{line_item.title}}',
			'category': '{{ collection.title }}',
			'price': '{{line_item.price | money_without_currency}}',
			'quantity': '{{line_item.quantity}}'
		},
		{% endfor %}

		]},{
			'pageType':'Confirmation',
			'event':'Confirmation'
		});
	 {% endif %}
}

/* DATALAYER: All Pages
-----------------------
Fire all pages trigger after all additional dataLayers have loaded. */

dataLayer.push({
	'event':'All Pages'
});
</script>
<commit_msg>Removed cid vars in script<commit_after><script>
/*
===================================
| DATALAYER ARCHITECTURE: SHOPIFY |
-----------------------------------

DEFINITION:
A data layer helps you collect more accurate analytics data, that in turn allows you to better understand what potential buyers are doing on your website and where you can make improvements. It also reduces the time to implement marketing tags on a website, and reduces the need for IT involvement, leaving them to get on with implementing new features and fixing bugs.

RESOURCES:
http://www.datalayerdoctor.com/a-gentle-introduction-to-the-data-layer-for-digital-marketers/
http://www.simoahava.com/analytics/data-layer/

AUTHORS:
Mechelle Warneke = [{
Email: mechellewarneke@gmail.com,
Website: mechellewarneke.com,
BVACCEL: [{
    Email: mechelle@bvaccel.com,
    Position: XO Strategist | Technical Web Analyst
}]
}];

Tyler Shambora = [{
Website: tylershambora.com,
BVACCEL: [{
    Email: tyler@bvaccel.com,
    Position: Lead Web Developer
}]
}];

EXTERNAL DEPENDENCIES:
* jQuery Cookie Plugin v1.4.1 - https://github.com/carhartl/jquery-cookie

DataLayer Architecture: Shopify v1.3.1
COPYRIGHT 2016
LICENSES: MIT ( https://opensource.org/licenses/MIT )
*/

/* =======================
| PREREQUISITE LIBRARIES |
----------------------- */

// jquery-cookies.js

/* ======================
| Begin dataLayer Build |
---------------------- */

console.log("----------------------\nBEGIN DATALAYER BUILD\nTHEME PAGES\n----------------------");
window.dataLayer = window.dataLayer || []
console.log('Page Template = {{template}}');
var template = '{{template}}';

/* Landing Page Cookie
-----------------------
1. Detect if user just landed on the site
2. Only fires if Page Title matches website */

$.cookie.raw = true;

if( $.cookie('landingPage') === undefined || $.cookie('landingPage').length === 0 ){

	$.cookie('landingPage', unescape);
	$.removeCookie('landingPage',{ path: '/' });
	$.cookie('landingPage', 'landed',{ path: '/' });
	var landingPage = true;
	console.log("Landing Page = True");

}else{

	$.cookie('landingPage', unescape);
	$.removeCookie('landingPage',{ path: '/' });
	$.cookie('landingPage', 'refresh',{ path: '/' });
	console.log("Landing Page = False");

}

/* Log State Cookie
------------------- */

{% if customer %}
var logState = "Logged In";
{% endif %}

if( typeof logState == "undefined" ){
	var logState = "Logged Out";
	$.cookie('logState', unescape);
	$.removeCookie('logState',{ path: '/' });
	$.cookie('logState', 'loggedOut',{ path: '/' })
	console.log("Log State = Logged Out");
}

if( logState === "Logged In"){
	if( $.cookie('logState') === "loggedOut" || $.cookie('logState') === undefined ){

		$.cookie('logState', unescape);
		$.removeCookie('logState',{ path: '/' });
		$.cookie('logState', 'firstLog',{ path: '/' });
		console.log("Log State = Logged In");

	}else if( $.cookie("logState") === "firstLog"  ){

		$.cookie('logState', unescape);
		$.removeCookie('logState',{ path: '/' });
		$.cookie('logState', 'refresh',{ path: '/' });
		console.log("Log State = Logged In");

	}else{
		console.log("Log State = Logged In");
	}
}

if( $.cookie("logState") === "firstLog"  ){
	var firstLog = "True";
}else{
	var firstLog = "False";
}
console.log("First Log = "+firstLog);

    // TIMESTAMP
    var ts = Date.now();
    console.log("Timestamp = "+ts);

/* ==========
| DATALAYERS |
----------- */

/* DATALAYER: Landing Page
--------------------------
Fires any time a user first lands on the site. */

if( $.cookie("landingPage") === "landed"  ){
	dataLayer.push({
		'pageType':'Landing Page',
		'event':'Landing Page'
	});
}

/* DATALAYER: Log State
-----------------------
1. Determine if user is logged in or not.
2. Return User specific data. */

dataLayer.push({
	{% if shop.customer_accounts_enabled %}
	{% if customer %}
	'userId': '{{customer.id}}',
	'customerEmail': '{{customer.email}}',
	'logState': "Logged In",
	{% else %}
	'logState': "Logged Out",
	{% endif %}
	{% endif %}
	'firstLog': firstLog,
	'customerEmail': '{{customer.email}}',
	'timestamp': ts
},{'event':'Log State'});

/* DATALAYER: Checkout
-------------------------- */

{% if template contains "checkout" %}
if (Shopify.Checkout.step === 'contact_information'){
	dataLayer.push({
		'event':'Checkout',
		'event':'Customer Information'});
}else if (Shopify.Checkout.step === 'shipping_method'){
	dataLayer.push({
		'event':'Shipping Information'});
}else if( Shopify.Checkout.step === "payment_method" ){
	dataLayer.push({
		'event':'Add Payment Info'});
}
{% endif %}

/* DATALAYER: Confirmation
-------------------------- */

if(Shopify.Checkout.page == "thank_you"){
	{% if first_time_accessed %}
	dataLayer.push({
		'transactionId': '{{checkout.order_id}}',
		'transactionNumber': '{{checkout.order_number}}',
		'transactionAffiliation': '{{shop.name}}',
		'transactionTotal': '{{checkout.total_price |  money_without_currency}}',
		'transactionTax': '{{checkout.total_price |  money_without_currency}}',
		'transactionShipping': '{{checkout.shipping_price |  money_without_currency}}',

		{% for discount in checkout.discounts %}
		'promoCode':'{{discount.code}}',
		'discount': '{{discount.amount | money_without_currency}}',
		{% endfor %}

		'transactionProducts': [

		{% for line_item in line_items %}
		{
			'id': '{{line_item.product_id}}',
			'sku': '{{line_item.sku}}',
			'variantId': '{{line_item.variant_id}}',
			'name': '{{line_item.title}}',
			'category': '{{ collection.title }}',
			'price': '{{line_item.price | money_without_currency}}',
			'quantity': '{{line_item.quantity}}'
		},
		{% endfor %}

		]},{
			'pageType':'Confirmation',
			'event':'Confirmation'
		});
	 {% endif %}
}

/* DATALAYER: All Pages
-----------------------
Fire all pages trigger after all additional dataLayers have loaded. */

dataLayer.push({
	'event':'All Pages'
});
</script>
<|endoftext|>


<commit_before>module.exports = [
  {
    name: 'Apples',
    notes: 'Fresh fresh apples',
    need_to_restock: false,
    user_id: null
  },
  {
    name: 'Pears',
    notes: 'I like pears',
    need_to_restock: false,
    user_id: null
  },
  {
    name: 'Milk',
    notes: 'MOO dairy',
    need_to_restock: false,
    user_id: null
  },
  {
    name: 'Bread',
    notes: '<3 carbs',
    need_to_restock: false,
    user_id: null
  }
];
<commit_msg>Change fields to align with schema design<commit_after>module.exports = [
  {
    id: 1,
    itemname: 'Apples',
    notes: 'Fresh fresh apples',
    need_to_restock: false,
    user_id: null
  },
  {
    id: 2,
    itemname: 'Pears',
    notes: 'I like pears',
    need_to_restock: false,
    user_id: null
  },
  {
    id: 3,
    itemname: 'Milk',
    notes: 'MOO dairy',
    need_to_restock: false,
    user_id: null
  },
  {
    id: 4,
    itemname: 'Bread',
    notes: '<3 carbs',
    need_to_restock: false,
    user_id: null
  }
];
<|endoftext|>


<commit_before>'use strict';
/**
 * @fileoverview This file conforms to the template expectations of
 * app/layouts/jslib.html; namely: this file expects jslibJsArgs to be a window
 * global containing the Google Drive HOST id of a public folder under which it
 * can find:
 *   1) an index.json file at the root listing the folder's contents
 *   2) art work to be displayed in this template.
 */

/** @constructor */
var httpRequest = function() {};

/**
 * Thin wrapper for {@link httpRequest.send}.
 * @param {string} url
 * @return {!Promise}
 */
httpRequest.get = function(url) {
  return httpRequest.send(url, httpRequest.Method.GET);
};

/** @enum {string} */
httpRequest.Method = {GET: 'GET', POST: 'POST'};

/**
 * Copy/pasted nearly verbatim from Apache2-licensed code snippet here:
 *   http://www.html5rocks.com/en/tutorials/es6/promises/
 *
 * @param {string} url
 * @param {httpRequest.Method} method
 * @return {!Promise}
 *     Promise to have made {@code method} request against {@code url}.
 */
httpRequest.send = function(url, method) {
  return new Promise(function(resolve, reject) {
    // Create CORS XHR object.
    //   from www.html5rocks.com/en/tutorials/cors/
    var req = new XMLHttpRequest();
    if ("withCredentials" in req) {
      req.open(method, url, true); // XHR for Chrome/Firefox/Opera/Safari.
    } else if (typeof XDomainRequest != "undefined") {
      req = new XDomainRequest(); // XDomainRequest for IE.
      req.open(method, url);
    } else {
      throw new Error("CORS not suppored; you're on some awful browser...");
    }

    req.onload = function() {
      if (req.status == 200) {
        resolve(req.response);
      } else {
        reject(Error(req.statusText));
      }
    };

    req.onerror = function() { reject(Error(arguments)); };

    req.send();
  });
};

/** @constructor */
var GdriveHost = function() {
  this.baseUrl = GdriveHost.BASE_URL
  this.get = httpRequest.get;
};

/** @const {string} */
GdriveHost.BASE_URL = 'https://content.j.zac.sh/art/';

/**
 * @param {string} relativePath
 * @return {string} Fully qualifieda path to {@code relativePath}.
 */
GdriveHost.prototype.getUrl = function(relativePath) {
 return this.baseUrl + '/' + relativePath;
};

/**
 * Thin wrapper for {@link httpRequest#get}.
 *
 * @param {string} path
 * @return {!Promise}
 */
GdriveHost.prototype.getRelativeFile = function(path) {
  return httpRequest.get(this.getUrl(path));
};

/**
 * @param {!GdriveHost} gdriveHost
 * @param {!Document} doc
 * @constructor
 */
var Artwork = function(gdriveHost, doc) {
  /** @private {!Document} */
  this.doc_ = doc;

  /** @private {!Element} */
  this.containerEl_ = Artwork.injectContainer_(this.doc_);

  /** @private {?Element} */
  this.gridEl_ = null;

  /** @private {!GdriveHost} */
  this.gdriveHost_ = gdriveHost;

  /** @private {Artwork.DirListing} */
  this.index_ = null;

  /** @private {!Array.<!Promise>} */
  this.queue_ = [];

  /** @private {!Object.<string, boolean>} */
  this.isLoaded_ = {};

  // Fetch artwork index
  this.queue_.push(
      this.gdriveHost_.
        getRelativeFile(Artwork.INDEX_PATH).
        then(function(index) {
          this.index_ = JSON.parse(index);
        }.bind(this)));
};

/**
 * @param {!Document} doc
 * @return {!Element} container
 * @private
 */
Artwork.injectContainer_ = function(doc) {
  var rootNode = doc.querySelectorAll('footer')[0];

  var containerEl = doc.createElement('div');
  containerEl.setAttribute('id', 'artwork');
  rootNode.parentNode.
      insertBefore(containerEl, rootNode);

  return containerEl;
};

/**
 * Recursive array of relative filenames, keyed by the relative filename of the
 * directory they're children of.
 *
 * @typedef {Object.<string, Array.<string|!Object>>}
 */
Artwork.DirListing;

/**
 * NOTE: contents are automatically generated by:
 *    https://github.com/jzacsh/bin/blob/23eca54534744/share/cron_cdn-update-art.sh
 * and its dependency:
 *    https://github.com/jzacsh/bin/blob/23eca54534744/share/dirtojson.js
 *
 * @const {string} expected subpath to a JSON index
 */
Artwork.INDEX_PATH = 'index.json';

/** @const {number} standard size of sections to add to grid */
Artwork.GRID_ADDITION = 8;

/** enum {boolean} */
Artwork.Enabled = {
  CAPTION: false
};

/** @return {!Promise} to be ready to render artwork. */
Artwork.prototype.ready = function() {
  return Promise.all(this.queue_);
};

/**
 * @param {string} subPath (may include slashes)
 * @return {string}
 * @private
 */
Artwork.prototype.getUrl_ = function(subPath) {
  return this.gdriveHost_.getUrl(encodeURI(subPath));
};

/**
 * Appends a thumbnail for {@code fileName} to Artwork grid.
 *
 * @param {string} fileName
 * @return {!Element} newly appended thumbnail for {@code fileName}
 * @private
 */
Artwork.prototype.addArtToGrid_ = function(fileName) {
  var fileUrl = this.getUrl_(fileName);

  var thumbNailEl = this.doc_.createElement('li');
  thumbNailEl.setAttribute('class', 'thumbnail');

  var anchorEl = this.doc_.createElement('a');
  anchorEl.setAttribute('target', '_blank');
  anchorEl.setAttribute('href', fileUrl);
  thumbNailEl.appendChild(anchorEl);

  var imgEl = this.doc_.createElement('img');
  imgEl.setAttribute('src', fileUrl);
  // TODO(zacsh) remove this hard-coded performance net for the browser, once i
  // am serving multiple sizes
  imgEl.setAttribute('width', '300px');
  anchorEl.appendChild(imgEl);

  if (Artwork.Enabled.CAPTION) {
    var captionEl = this.doc_.createElement('span');
    captionEl.setAttribute('class', 'caption');
    captionEl.textContent = fileName;
    anchorEl.appendChild(captionEl);
  }

  this.gridEl_.appendChild(thumbNailEl);
  this.isLoaded_[fileName] = true;
  return thumbNailEl;
};

/**
 * Renders a visual grid of artwork on the page.
 * @param {!Array.<string>} artwork
 */
Artwork.prototype.addToGrid = function(artwork) {
  if (!this.gridEl_) {
    this.buildGrid_();
  }

  artwork.forEach(function(work) {
    if (!this.isGoodAdd_(work)) {
      return;
    }

    this.addArtToGrid_(work);
  }.bind(this));
};

/** @private */
Artwork.prototype.buildGrid_ = function() {
  this.gridEl_ = this.doc_.createElement('ul');
  this.gridEl_.setAttribute('class', 'grid');
  this.containerEl_.appendChild(this.gridEl_);

  var buttonEl = this.doc_.createElement('button');
  buttonEl.textContent = 'Load more';
  buttonEl.addEventListener('click', this.addMoreArt.bind(this));
  this.containerEl_.appendChild(buttonEl);
};

/**
 * @param {string|!Object} artwork value of {@link Artwork.DirListing}.
 * @return {boolean}
 * @private
 */
Artwork.prototype.isGoodAdd_ = function(artwork) {
  return Boolean(
      // TODO(zacsh): deal with this when you break artwork into "vector" vs
      // "raster" vs "doodle"
      typeof artwork != 'object' &&

      // TODO(zacsh) remove this guard once i'm serving multiple sizes; ie:
      // using this cron script, again:
      // github.com/jzacsh/bin/blob/master/share/prep_images
      !artwork.match(/\.svg$/) &&

      artwork != Artwork.INDEX_PATH &&
      artwork != 'copying' &&

      !this.isLoaded_[artwork]);
};

/**
 * @param {number} subsetSize
 * @return {!Array.<string>}
 * @private
 */
// TODO remove use of this once some sort of paging mechanism is in place
Artwork.prototype.getRandomSubset_ = function(subsetSize) {
  if (subsetSize === this.index_.art.length) {
    return this.index_.art.slice();  // bad request, mock new array
  }

  var getRandArt = Artwork.getRandomIntExclusive_.
      bind(null  /*this*/, 0  /*min*/, this.index_.art.length);

  var tries = 0, maxTries = 2 * subsetSize; // while loops make me nervous

  var subset = [];
  while (++tries < maxTries && subset.length < subsetSize) {
    var randArt = this.index_.art[getRandArt()];
    if (!this.isGoodAdd_(randArt)) {
      continue;
    }

    subset.push(randArt);
  }
  return subset;
};

/** Loads random small subset of artwork into the grid. */
Artwork.prototype.addRandomArt = function() {
  this.addToGrid(this.getRandomSubset_(Artwork.GRID_ADDITION  /*size*/));
};

/** Loads random small subset of artwork into the grid. */
Artwork.prototype.addMoreArt = function() {
  var added = 0;
  this.addToGrid(this.index_.art.filter(function(artwork, i) {
    var willAdd = added < Artwork.GRID_ADDITION && this.isGoodAdd_(artwork);
    added += willAdd;
    return willAdd;
  }.bind(this)));
};

/**
 * NOTE: Taken from MIT-licensed (at least)[1] snippets on this page:
 * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/random
 *
 * [1]: developer.mozilla.org/en-US/docs/MDN/About#Copyrights_and_licenses
 *
 * @param {number} min
 * @param {number} max
 * @return {number}
 * @private
 */
Artwork.getRandomIntExclusive_ = function(min, max) {
  return Math.floor(Math.random() * (max - min)) + min;
};

this.document.addEventListener('DOMContentLoaded', function() {
  var gdriveHost = new GdriveHost();
  var artwork = new Artwork(gdriveHost, this /*doc*/);

  artwork.ready().then(function() {
    artwork.addRandomArt();
  });
});
<commit_msg>more my style (inherited from google closure days)<commit_after>'use strict';
/**
 * @fileoverview This file conforms to the template expectations of
 * app/layouts/jslib.html; namely: this file expects jslibJsArgs to be a window
 * global containing the Google Drive HOST id of a public folder under which it
 * can find:
 *   1) an index.json file at the root listing the folder's contents
 *   2) art work to be displayed in this template.
 */

/** @constructor */
var httpRequest = function() {};

/**
 * Thin wrapper for {@link httpRequest.send}.
 * @param {string} url
 * @return {!Promise}
 */
httpRequest.get = function(url) {
  return httpRequest.send(url, httpRequest.Method.GET);
};

/** @enum {string} */
httpRequest.Method = {GET: 'GET', POST: 'POST'};

/**
 * Copy/pasted nearly verbatim from Apache2-licensed code snippet here:
 *   http://www.html5rocks.com/en/tutorials/es6/promises/
 *
 * @param {string} url
 * @param {httpRequest.Method} method
 * @return {!Promise}
 *     Promise to have made {@code method} request against {@code url}.
 */
httpRequest.send = function(url, method) {
  return new Promise(function(resolve, reject) {
    // Create CORS XHR object.
    //   from www.html5rocks.com/en/tutorials/cors/
    var req = new XMLHttpRequest();
    if (req.withCredentials) {
      req.open(method, url, true); // XHR for Chrome/Firefox/Opera/Safari.
    } else if (typeof XDomainRequest != "undefined") {
      req = new XDomainRequest(); // XDomainRequest for IE.
      req.open(method, url);
    } else {
      throw new Error("CORS not suppored; you're on some awful browser...");
    }

    req.onload = function() {
      if (req.status == 200) {
        resolve(req.response);
      } else {
        reject(Error(req.statusText));
      }
    };

    req.onerror = function() { reject(Error(arguments)); };

    req.send();
  });
};

/** @constructor */
var GdriveHost = function() {
  this.baseUrl = GdriveHost.BASE_URL
  this.get = httpRequest.get;
};

/** @const {string} */
GdriveHost.BASE_URL = 'https://content.j.zac.sh/art/';

/**
 * @param {string} relativePath
 * @return {string} Fully qualifieda path to {@code relativePath}.
 */
GdriveHost.prototype.getUrl = function(relativePath) {
 return this.baseUrl + '/' + relativePath;
};

/**
 * Thin wrapper for {@link httpRequest#get}.
 *
 * @param {string} path
 * @return {!Promise}
 */
GdriveHost.prototype.getRelativeFile = function(path) {
  return httpRequest.get(this.getUrl(path));
};

/**
 * @param {!GdriveHost} gdriveHost
 * @param {!Document} doc
 * @constructor
 */
var Artwork = function(gdriveHost, doc) {
  /** @private {!Document} */
  this.doc_ = doc;

  /** @private {!Element} */
  this.containerEl_ = Artwork.injectContainer_(this.doc_);

  /** @private {?Element} */
  this.gridEl_ = null;

  /** @private {!GdriveHost} */
  this.gdriveHost_ = gdriveHost;

  /** @private {Artwork.DirListing} */
  this.index_ = null;

  /** @private {!Array.<!Promise>} */
  this.queue_ = [];

  /** @private {!Object.<string, boolean>} */
  this.isLoaded_ = {};

  // Fetch artwork index
  this.queue_.push(
      this.gdriveHost_.
        getRelativeFile(Artwork.INDEX_PATH).
        then(function(index) {
          this.index_ = JSON.parse(index);
        }.bind(this)));
};

/**
 * @param {!Document} doc
 * @return {!Element} container
 * @private
 */
Artwork.injectContainer_ = function(doc) {
  var rootNode = doc.querySelectorAll('footer')[0];

  var containerEl = doc.createElement('div');
  containerEl.setAttribute('id', 'artwork');
  rootNode.parentNode.
      insertBefore(containerEl, rootNode);

  return containerEl;
};

/**
 * Recursive array of relative filenames, keyed by the relative filename of the
 * directory they're children of.
 *
 * @typedef {Object.<string, Array.<string|!Object>>}
 */
Artwork.DirListing;

/**
 * NOTE: contents are automatically generated by:
 *    https://github.com/jzacsh/bin/blob/23eca54534744/share/cron_cdn-update-art.sh
 * and its dependency:
 *    https://github.com/jzacsh/bin/blob/23eca54534744/share/dirtojson.js
 *
 * @const {string} expected subpath to a JSON index
 */
Artwork.INDEX_PATH = 'index.json';

/** @const {number} standard size of sections to add to grid */
Artwork.GRID_ADDITION = 8;

/** enum {boolean} */
Artwork.Enabled = {
  CAPTION: false
};

/** @return {!Promise} to be ready to render artwork. */
Artwork.prototype.ready = function() {
  return Promise.all(this.queue_);
};

/**
 * @param {string} subPath (may include slashes)
 * @return {string}
 * @private
 */
Artwork.prototype.getUrl_ = function(subPath) {
  return this.gdriveHost_.getUrl(encodeURI(subPath));
};

/**
 * Appends a thumbnail for {@code fileName} to Artwork grid.
 *
 * @param {string} fileName
 * @return {!Element} newly appended thumbnail for {@code fileName}
 * @private
 */
Artwork.prototype.addArtToGrid_ = function(fileName) {
  var fileUrl = this.getUrl_(fileName);

  var thumbNailEl = this.doc_.createElement('li');
  thumbNailEl.setAttribute('class', 'thumbnail');

  var anchorEl = this.doc_.createElement('a');
  anchorEl.setAttribute('target', '_blank');
  anchorEl.setAttribute('href', fileUrl);
  thumbNailEl.appendChild(anchorEl);

  var imgEl = this.doc_.createElement('img');
  imgEl.setAttribute('src', fileUrl);
  // TODO(zacsh) remove this hard-coded performance net for the browser, once i
  // am serving multiple sizes
  imgEl.setAttribute('width', '300px');
  anchorEl.appendChild(imgEl);

  if (Artwork.Enabled.CAPTION) {
    var captionEl = this.doc_.createElement('span');
    captionEl.setAttribute('class', 'caption');
    captionEl.textContent = fileName;
    anchorEl.appendChild(captionEl);
  }

  this.gridEl_.appendChild(thumbNailEl);
  this.isLoaded_[fileName] = true;
  return thumbNailEl;
};

/**
 * Renders a visual grid of artwork on the page.
 * @param {!Array.<string>} artwork
 */
Artwork.prototype.addToGrid = function(artwork) {
  if (!this.gridEl_) {
    this.buildGrid_();
  }

  artwork.forEach(function(work) {
    if (!this.isGoodAdd_(work)) {
      return;
    }

    this.addArtToGrid_(work);
  }.bind(this));
};

/** @private */
Artwork.prototype.buildGrid_ = function() {
  this.gridEl_ = this.doc_.createElement('ul');
  this.gridEl_.setAttribute('class', 'grid');
  this.containerEl_.appendChild(this.gridEl_);

  var buttonEl = this.doc_.createElement('button');
  buttonEl.textContent = 'Load more';
  buttonEl.addEventListener('click', this.addMoreArt.bind(this));
  this.containerEl_.appendChild(buttonEl);
};

/**
 * @param {string|!Object} artwork value of {@link Artwork.DirListing}.
 * @return {boolean}
 * @private
 */
Artwork.prototype.isGoodAdd_ = function(artwork) {
  return Boolean(
      // TODO(zacsh): deal with this when you break artwork into "vector" vs
      // "raster" vs "doodle"
      typeof artwork != 'object' &&

      // TODO(zacsh) remove this guard once i'm serving multiple sizes; ie:
      // using this cron script, again:
      // github.com/jzacsh/bin/blob/master/share/prep_images
      !artwork.match(/\.svg$/) &&

      artwork != Artwork.INDEX_PATH &&
      artwork != 'copying' &&

      !this.isLoaded_[artwork]);
};

/**
 * @param {number} subsetSize
 * @return {!Array.<string>}
 * @private
 */
// TODO remove use of this once some sort of paging mechanism is in place
Artwork.prototype.getRandomSubset_ = function(subsetSize) {
  if (subsetSize === this.index_.art.length) {
    return this.index_.art.slice();  // bad request, mock new array
  }

  var getRandArt = Artwork.getRandomIntExclusive_.
      bind(null  /*this*/, 0  /*min*/, this.index_.art.length);

  var tries = 0, maxTries = 2 * subsetSize; // while loops make me nervous

  var subset = [];
  while (++tries < maxTries && subset.length < subsetSize) {
    var randArt = this.index_.art[getRandArt()];
    if (!this.isGoodAdd_(randArt)) {
      continue;
    }

    subset.push(randArt);
  }
  return subset;
};

/** Loads random small subset of artwork into the grid. */
Artwork.prototype.addRandomArt = function() {
  this.addToGrid(this.getRandomSubset_(Artwork.GRID_ADDITION  /*size*/));
};

/** Loads random small subset of artwork into the grid. */
Artwork.prototype.addMoreArt = function() {
  var added = 0;
  this.addToGrid(this.index_.art.filter(function(artwork, i) {
    var willAdd = added < Artwork.GRID_ADDITION && this.isGoodAdd_(artwork);
    added += willAdd;
    return willAdd;
  }.bind(this)));
};

/**
 * NOTE: Taken from MIT-licensed (at least)[1] snippets on this page:
 * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/random
 *
 * [1]: developer.mozilla.org/en-US/docs/MDN/About#Copyrights_and_licenses
 *
 * @param {number} min
 * @param {number} max
 * @return {number}
 * @private
 */
Artwork.getRandomIntExclusive_ = function(min, max) {
  return Math.floor(Math.random() * (max - min)) + min;
};

this.document.addEventListener('DOMContentLoaded', function() {
  var gdriveHost = new GdriveHost();
  var artwork = new Artwork(gdriveHost, this /*doc*/);

  artwork.ready().then(function() {
    artwork.addRandomArt();
  });
});
<|endoftext|>


<commit_before>function comment_form_callback(response) {
    parsed = $("<div/>").html(response);
    if (parsed.find('#next').attr('href') == location.pathname) {
        $('#comment_form').clearForm();
        location.href = location.href + '#comments';
        location.reload();
        return;
    }
    form = parsed.find('form');
    if (form.length) {
        // problems with the form submission
        $("#comment_form").replaceWith(form);
        recaptcha_ajax_field = $("#comment_form #recaptcha_ajax_field");
        if (typeof recaptcha_ajax_callback !== 'undefined') {
            recaptcha_ajax_callback();
        }
        $("#comment_form").ajaxForm(comment_form_ajax_options);
    } else {
        $("#comment_form").clearForm().find('.errorlist').remove();
        $("#overlay .contentWrap").empty().append(parsed.find('.comment_posted'));
        $("#overlay").overlay({api:true}).load();
    }
}

comment_form_ajax_options = {
    success: comment_form_callback,
    dataType: 'html'
}

$(document).ready(function() {
    $("#comment_form").ajaxForm(comment_form_ajax_options);
});<commit_msg>Internet Explorer says that the link is a full path<commit_after>function comment_form_callback(response) {
    parsed = $("<div/>").html(response);
    next = parsed.find('#next').attr('href');
    if ((next == location.pathname) ||
        (next == location.href)) {
        $('#comment_form').clearForm();
        location.href = location.href + '#comments';
        location.reload();
        return;
    }
    form = parsed.find('form');
    if (form.length) {
        // problems with the form submission
        $("#comment_form").replaceWith(form);
        recaptcha_ajax_field = $("#comment_form #recaptcha_ajax_field");
        if (typeof recaptcha_ajax_callback !== 'undefined') {
            recaptcha_ajax_callback();
        }
        $("#comment_form").ajaxForm(comment_form_ajax_options);
    } else {
        $("#comment_form").clearForm().find('.errorlist').remove();
        $("#overlay .contentWrap").empty().append(parsed.find('.comment_posted'));
        $("#overlay").overlay({api:true}).load();
    }
}

comment_form_ajax_options = {
    success: comment_form_callback,
    dataType: 'html'
}

$(document).ready(function() {
    $("#comment_form").ajaxForm(comment_form_ajax_options);
});<|endoftext|>


<commit_before>
// vim: set ts=4 sw=4 et:
"use strict";

var DRAWING_REFRESH = 5000;
var DRAWING_PUSH_REFRESH = 5000;
var DRAWING_GRID_COLOR = '#444';
var DRAWING_GRID_WIDTH = 0.5;
var DRAWING_BOUNDARIES_COLOR = '#888';
var DRAWING_BOUNDARIES_WIDTH = 2;
var DRAWING_URL_UPDATE = "/api/drawing/update";
var DRAWING_URL_POST = "/api/drawing/post";

var POSITION_TYPE_DRAWING = 0;
var POSITION_TYPE_ZONE = 0;

function Drawing( p_session, p_canvas_id ){
    var self = this;

    /**
     *
     */
    this.pull_patches = function( callback ){
        $.ajax({
            // FIXME: request with previous user_id
            url: DRAWING_URL_UPDATE,
            dataType: "json",
            type: 'GET',
            context: self,
            success: function( response ){
                // FIXME: set cookie with user_id for next time
                console.log('drawing/update response : ' + JSON.stringify( response ) );

                if (callback){  callback( self ); }
            }
        });
    };

    /**
     *
     */
    this.push_patches = function( callback ){
        $.ajax({
            // FIXME: request with previous user_id
            url: DRAWING_URL_POST,
            dataType: "json",
            type: 'POST',
            data: JSON.stringify( { patches: [] }),
            context: self,
            success: function( response ){
                // FIXME: set cookie with user_id for next time
                console.log('drawing/post response : ' + JSON.stringify( response ) );

                if (callback){  callback( self ); }
            }
        });
        // FIXME: send content only if aggregate is not empty
    };

    /**
     * Convert local grid to canvas position
     */
    function local_to_canvas_position( local_position ) {
        return {
            x: Math.floor( local_position.x * self.column_size ),
            y: Math.floor( local_position.y * self.line_size )
        };
    }

    /**
     * Convert canvas to local position
     */
    function canvas_to_local_position( canvas_position ){
        return {
            x: Math.floor( canvas_position.x / self.column_size ),
            y: Math.floor( canvas_position.y / self.line_size )
        };
    }

    /**
     * Convert local grid to zone position
     */
    function local_to_zone_position( local_position ){
        return {
            x: local_position.x - self.border_column_count,
            y: local_position.y - self.border_line_count
        };
    }

    /**
     * Convert zone to local grid position
     */
    function zone_to_local_position( zone_position ) {
        return {
            x: zone_position.x + self.border_column_count,
            y: zone_position.y + self.border_line_count
        };
    }

    /**
     * Get relative zone position
     */
    function relative_position( zone_position, zone ) {
        // FIXME: implement relative zone position
        return { x: 0, y: 0 };
    }

    /*
     * draw zone grid
     */
    this.draw_grid = function() {
        // apply grid canvas on the real canvas
        var grid_ctx;
        var canvas;
        var ctx = self.context;

        // create grid if none exist
        if ( self.grid_canvas == null ) {
            self.grid_canvas = document.createElement('canvas');
            self.grid_canvas.width = self.real_canvas.width;
            self.grid_canvas.height = self.real_canvas.height;
            canvas = self.grid_canvas;
            grid_ctx = canvas.getContext("2d");

            //console.log("drawing/draw_grid: before lines");

            var w_max = self.column_count + (2 * self.border_column_count);
            for (var w=0; w <= w_max; w++){
                var local_pos = { 'x': w, 'y': 0 };
                var canvas_pos = local_to_canvas_position( local_pos );
                grid_ctx.moveTo(canvas_pos.x, 0);
                grid_ctx.lineTo(canvas_pos.x, canvas.height);
            }
            var h_max = self.line_count + (2 * self.border_line_count);
            for (var h=0; h <= h_max; h++){
                var local_pos = { 'x': 0, 'y': h };
                var canvas_pos = local_to_canvas_position( local_pos );
                grid_ctx.moveTo(0, canvas_pos.y);
                grid_ctx.lineTo(canvas.width, canvas_pos.y);
            }
            grid_ctx.lineWidth = DRAWING_GRID_WIDTH;
            grid_ctx.strokeStyle = DRAWING_GRID_COLOR;
            grid_ctx.stroke();

            grid_ctx.beginPath();
            var local_tl = {
                x : self.border_column_count,
                y : self.border_line_count
            };
            var canvas_tl =  local_to_canvas_position( local_tl );
            canvas_tl.w = Math.floor( self.column_count * self.column_size );
            canvas_tl.h = Math.floor( self.line_count * self.line_size );

            grid_ctx.lineWidth = DRAWING_BOUNDARIES_WIDTH;
            grid_ctx.strokeStyle = DRAWING_BOUNDARIES_COLOR;
            grid_ctx.strokeRect( canvas_tl.x, canvas_tl.y, canvas_tl.w, canvas_tl.h );
        }
        ctx.drawImage( self.grid_canvas, 0, 0);
    }

    /**
     * Repaint zone drawing
     */
    this.update_paint = function() {

        for (var x = 0 ; x < self.column_count ; x++ ){
            for (var y = 0; y < self.line_count ; y++ ) {
                var zone_pos = { 'x': x, 'y': y };
                var color = _zone.pixel_get( zone_pos );
                var local_pos = zone_to_local_position( zone_pos );
                // console.log("drawing/update_paint: zone_pos =", zone_pos.to_json() );
                self.pixel_draw( local_pos, color );
            }
        }

    }

    /**
     *
     */
    this.update_size = function() {
        var real_canvas = self.real_canvas;
        var win = {
            w: $(window).width(),
            h : $(window).height()
        };

        real_canvas.style.position = 'absolute';
        if (win.w > win.h) {
            real_canvas.width = win.h - 2;
            real_canvas.height = win.h - 2;
        } else {
            real_canvas.width = win.w - 2;
            real_canvas.height = win.w - 2;
        }
        real_canvas.style.top = '1px';
        real_canvas.style.left = Math.floor((win.w - real_canvas.width) / 2) + 'px';

        // console.log("drawing/update_size: window.width = " + [ $(window).width(), $(window).height() ] );

        // console.log("drawing/update_size: real_canvas.width = " + real_canvas.width);
        self.column_size = real_canvas.width / (self.column_count + (self.border_column_count * 2));
        self.line_size = real_canvas.height / (self.line_count + (self.border_line_count * 2));

        // console.log("drawing/update_size: column_size = " + self.column_size);

        self.grid_canvas = null;

        var ctx = real_canvas.getContext("2d");
        ctx.fillStyle = '#000';
        ctx.fillRect(0, 0, real_canvas.width, real_canvas.height);

        self.draw_grid();
    };

    /**
     * Handle mouse event
     */
    this.mouseup = function( event_obj ) { self.pencil_up( event_obj ); }
    this.touchup = function( event_obj ) {
        self.pencil_up( event_obj );
    }

    this.pencil_up = function( event_obj ) {
        self.move.enable = false;
    };

    /**
     * Handle mouse event
     */
    this.mousedown = function( event_obj ) { self.pencil_down( event_obj ); }
    this.touchdown = function( event_obj ) { self.pencil_down( event_obj ); }

    this.pencil_down = function( event_obj ) {
        self.move.enable = true;
        self.mousemove( event_obj );
    };

    /**
     * Handle mouse event
     */
    this.mousemove = function( event_obj ) { self.pencil_move( event_obj ); }
    this.touchmove = function( event_obj ) {
        self.pencil_move( event_obj );
        event_obj.preventDefault();
    }

    this.pencil_move = function( event_obj ) {
        var ctx = self.context;
        var canvas = self.real_canvas;

        if (self.move.enable) {
            var canvas_pos = { x: event_obj.mouseX, y: event_obj.mouseY };
            var local_pos = canvas_to_local_position( canvas_pos );
            var zone_pos = local_to_zone_position( local_pos );
            //console.log( "drawing/mousemove: canvas pos : %s", canvas_pos.to_json() );
            //console.log( "drawing/mousemove:local pos : %s", local_pos.to_json() );
            //console.log( "drawing/mousemove:zone pos : %s", zone_pos.to_json() );

            // FIXME: detect target zone
            // target_zone = local_to_target_ f( zone_pos )
            var bound = _zone.is_bound( zone_pos );
            // console.log( "drawing/mousemove: zone.is_bound = ", bound );

            if ( bound ) {
                self.pixel_set( local_pos, _color );
            } else {
                //FIXME: _color = _zone.pixel_get( zone_pos );
            }

        }
    };

    /**
     * change pixel at given position, on canvas only
     */
    this.pixel_draw = function( local_pos, color ) {
        var ctx = self.context;
        //console.log("drawing/pixel_draw local_pos = %s", local_pos.to_json() );
        var canvas_pos = local_to_canvas_position( local_pos );
        var rect = {
            x : canvas_pos.x + (0.1 * self.column_size),
            y : canvas_pos.y + (0.1 * self.column_size),
            w : self.column_size - ( 0.2 * self.column_size ),
            h : self.line_size - ( 0.2 * self.column_size )
        };
        //console.log("drawing/pixel_draw rect = %s", rect.to_json() );

        ctx.fillStyle = ZONE_BACKGROUND_COLOR;
        ctx.fillRect( rect.x, rect.y, rect.w, rect.h );

        ctx.fillStyle = color;
        ctx.fillRect( rect.x, rect.y, rect.w, rect.h );
    }

    /*
     * Set pixel at given position to given color
     */
    this.pixel_set = function( local_pos, color ) {
        var zone_pos;

        zone_pos = local_to_zone_position( local_pos );
        //console.log( "drawing/pixel_set: zone_pos = %s", zone_pos.to_json() );
        // record to zone
        _zone.pixel_set( zone_pos, color );
        // add to patch structure
        _zone.patch_record( zone_pos, color );
        // draw localy
        self.pixel_draw( local_pos, color );
    };

    /**
     * Pick color of pixel
     */
    this.pixel_get = function( local_pos ) {
        //FIXME: detect bound zone...
        //_return zone.pixel_get( pos );
    }

    /**
      * Change color
      */

    this.color_set = function( hexcolor ) {
        _color = hexcolor;
        // FIXME:
        console.log("drawing/color_set: requestion patch enqueue")
        _zone.patch_enqueue();
    }

    var canvas_event = function( event_obj ) {
        var canvas = self.real_canvas;

        event_obj.mouseX = event_obj.pageX - canvas.offsetLeft;
        event_obj.mouseY = event_obj.pageY - canvas.offsetTop;

        var func = self[event_obj.type];
        if (func) { func( event_obj ); }
        // console.log("clicked at %s,%s", mouseX, mouseY );
    };

    this.to_s = function() { JSON.stringify(this); };

    var _zone = new Zone( p_session.zone_column_count, p_session.zone_line_count );
    var _color = '#f00';

    this.patch = null;
    this.move = {
        enable : false
    }
    this.session = p_session;
    this.column_count = p_session.zone_column_count;
    this.line_count = p_session.zone_line_count;
    this.border_column_count = p_session.zone_column_count / 4;
    this.border_line_count = p_session.zone_column_count / 4;

    this.real_canvas = document.getElementById( p_canvas_id );

    this.grid_canvas = null;

    // size of zone's big pixels
    this.column_size = 1;
    this.line_size = 1;

    var _pull_timer = window.setInterval( this.pull_patches, DRAWING_REFRESH );
    var _push_timer = window.setInterval( this.push_patches, DRAWING_PUSH_REFRESH );
    var _enqueue_timer = window.setInterval( _zone.patch_enqueue, PATCH_LIFESPAN );

    this.context = this.real_canvas.getContext('2d');

    // plug some event handlers
    this.real_canvas.addEventListener( 'mousedown', canvas_event, false );
    this.real_canvas.addEventListener( 'touchstart', canvas_event, false );

    this.real_canvas.addEventListener( 'mouseup', canvas_event, false );
    this.real_canvas.addEventListener( 'touchstop', canvas_event, false );

    this.real_canvas.addEventListener( 'mousemove', canvas_event, false );
    this.real_canvas.addEventListener( 'touchmove', canvas_event, false );

    $(window).resize(function() {
        self.update_size();
        self.update_paint();
    });

    this.update_size();
    this.update_paint();
    // console.log("drawing_id = " + p_canvas_id);
}

<commit_msg>js drawing: Touch events finally work !<commit_after>
// vim: set ts=4 sw=4 et:
"use strict";

var DRAWING_REFRESH = 5000;
var DRAWING_PUSH_REFRESH = 5000;
var DRAWING_GRID_COLOR = '#444';
var DRAWING_GRID_WIDTH = 0.5;
var DRAWING_BOUNDARIES_COLOR = '#888';
var DRAWING_BOUNDARIES_WIDTH = 2;
var DRAWING_URL_UPDATE = "/api/drawing/update";
var DRAWING_URL_POST = "/api/drawing/post";

var POSITION_TYPE_DRAWING = 0;
var POSITION_TYPE_ZONE = 0;

function Drawing( p_session, p_canvas_id ){
    var self = this;

    /**
     *
     */
    this.pull_patches = function( callback ){
        $.ajax({
            // FIXME: request with previous user_id
            url: DRAWING_URL_UPDATE,
            dataType: "json",
            type: 'GET',
            context: self,
            success: function( response ){
                // FIXME: set cookie with user_id for next time
                console.log('drawing/update response : ' + JSON.stringify( response ) );

                if (callback){  callback( self ); }
            }
        });
    };

    /**
     *
     */
    this.push_patches = function( callback ){
        $.ajax({
            // FIXME: request with previous user_id
            url: DRAWING_URL_POST,
            dataType: "json",
            type: 'POST',
            data: JSON.stringify( { patches: [] }),
            context: self,
            success: function( response ){
                // FIXME: set cookie with user_id for next time
                console.log('drawing/post response : ' + JSON.stringify( response ) );

                if (callback){  callback( self ); }
            }
        });
        // FIXME: send content only if aggregate is not empty
    };

    /**
     * Convert local grid to canvas position
     */
    function local_to_canvas_position( local_position ) {
        return {
            x: Math.floor( local_position.x * self.column_size ),
            y: Math.floor( local_position.y * self.line_size )
        };
    }

    /**
     * Convert canvas to local position
     */
    function canvas_to_local_position( canvas_position ){
        return {
            x: Math.floor( canvas_position.x / self.column_size ),
            y: Math.floor( canvas_position.y / self.line_size )
        };
    }

    /**
     * Convert local grid to zone position
     */
    function local_to_zone_position( local_position ){
        return {
            x: local_position.x - self.border_column_count,
            y: local_position.y - self.border_line_count
        };
    }

    /**
     * Convert zone to local grid position
     */
    function zone_to_local_position( zone_position ) {
        return {
            x: zone_position.x + self.border_column_count,
            y: zone_position.y + self.border_line_count
        };
    }

    /**
     * Get relative zone position
     */
    function relative_position( zone_position, zone ) {
        // FIXME: implement relative zone position
        return { x: 0, y: 0 };
    }

    /*
     * draw zone grid
     */
    this.draw_grid = function() {
        // apply grid canvas on the real canvas
        var grid_ctx;
        var canvas;
        var ctx = self.context;

        // create grid if none exist
        if ( self.grid_canvas == null ) {
            self.grid_canvas = document.createElement('canvas');
            self.grid_canvas.width = self.real_canvas.width;
            self.grid_canvas.height = self.real_canvas.height;
            canvas = self.grid_canvas;
            grid_ctx = canvas.getContext("2d");

            //console.log("drawing/draw_grid: before lines");

            var w_max = self.column_count + (2 * self.border_column_count);
            for (var w=0; w <= w_max; w++){
                var local_pos = { 'x': w, 'y': 0 };
                var canvas_pos = local_to_canvas_position( local_pos );
                grid_ctx.moveTo(canvas_pos.x, 0);
                grid_ctx.lineTo(canvas_pos.x, canvas.height);
            }
            var h_max = self.line_count + (2 * self.border_line_count);
            for (var h=0; h <= h_max; h++){
                var local_pos = { 'x': 0, 'y': h };
                var canvas_pos = local_to_canvas_position( local_pos );
                grid_ctx.moveTo(0, canvas_pos.y);
                grid_ctx.lineTo(canvas.width, canvas_pos.y);
            }
            grid_ctx.lineWidth = DRAWING_GRID_WIDTH;
            grid_ctx.strokeStyle = DRAWING_GRID_COLOR;
            grid_ctx.stroke();

            grid_ctx.beginPath();
            var local_tl = {
                x : self.border_column_count,
                y : self.border_line_count
            };
            var canvas_tl =  local_to_canvas_position( local_tl );
            canvas_tl.w = Math.floor( self.column_count * self.column_size );
            canvas_tl.h = Math.floor( self.line_count * self.line_size );

            grid_ctx.lineWidth = DRAWING_BOUNDARIES_WIDTH;
            grid_ctx.strokeStyle = DRAWING_BOUNDARIES_COLOR;
            grid_ctx.strokeRect( canvas_tl.x, canvas_tl.y, canvas_tl.w, canvas_tl.h );
        }
        ctx.drawImage( self.grid_canvas, 0, 0);
    }

    /**
     * Repaint zone drawing
     */
    this.update_paint = function() {

        for (var x = 0 ; x < self.column_count ; x++ ){
            for (var y = 0; y < self.line_count ; y++ ) {
                var zone_pos = { 'x': x, 'y': y };
                var color = _zone.pixel_get( zone_pos );
                var local_pos = zone_to_local_position( zone_pos );
                // console.log("drawing/update_paint: zone_pos =", zone_pos.to_json() );
                self.pixel_draw( local_pos, color );
            }
        }

    }

    /**
     *
     */
    this.update_size = function() {
        var real_canvas = self.real_canvas;
        var win = {
            w: $(window).width(),
            h : $(window).height()
        };

        real_canvas.style.position = 'absolute';
        if (win.w > win.h) {
            real_canvas.width = win.h - 2;
            real_canvas.height = win.h - 2;
        } else {
            real_canvas.width = win.w - 2;
            real_canvas.height = win.w - 2;
        }
        real_canvas.style.top = '1px';
        real_canvas.style.left = Math.floor((win.w - real_canvas.width) / 2) + 'px';

        // console.log("drawing/update_size: window.width = " + [ $(window).width(), $(window).height() ] );

        // console.log("drawing/update_size: real_canvas.width = " + real_canvas.width);
        self.column_size = real_canvas.width / (self.column_count + (self.border_column_count * 2));
        self.line_size = real_canvas.height / (self.line_count + (self.border_line_count * 2));

        // console.log("drawing/update_size: column_size = " + self.column_size);

        self.grid_canvas = null;

        var ctx = real_canvas.getContext("2d");
        ctx.fillStyle = '#000';
        ctx.fillRect(0, 0, real_canvas.width, real_canvas.height);

        self.draw_grid();
    };

    /**
     * Handle mouse event
     */
    this.mouseup = function( event_obj ) { self.pencil_up( event_obj ); }
    this.touchstop = function( event_obj ) {
        event_obj.mouseX = event_obj.touches[0].pageX;
        event_obj.mouseY = event_obj.touches[0].pageY;
        self.pencil_up( event_obj );
    }

    this.pencil_up = function( event_obj ) {
        self.move.enable = false;
    };

    /**
     * Handle mouse event
     */
    this.mousedown = function( event_obj ) { self.pencil_down( event_obj ); }
    this.touchstart = function( event_obj ) {
        event_obj.mouseX = event_obj.touches[0].pageX;
        event_obj.mouseY = event_obj.touches[0].pageY;
        self.pencil_down( event_obj );
    }

    this.pencil_down = function( event_obj ) {
        self.move.enable = true;
        self.mousemove( event_obj );
    };

    /**
     * Handle mouse event
     */
    this.mousemove = function( event_obj ) { self.pencil_move( event_obj ); }
    this.touchmove = function( event_obj ) {
        event_obj.mouseX = event_obj.touches[0].pageX;
        event_obj.mouseY = event_obj.touches[0].pageY;
        self.pencil_move( event_obj );
        event_obj.preventDefault();
    }

    this.pencil_move = function( event_obj ) {
        var ctx = self.context;
        var canvas = self.real_canvas;

        if (self.move.enable) {
            var canvas_pos = { x: event_obj.mouseX, y: event_obj.mouseY };
            var local_pos = canvas_to_local_position( canvas_pos );
            var zone_pos = local_to_zone_position( local_pos );
            //console.log( "drawing/mousemove: canvas pos : %s", canvas_pos.to_json() );
            //console.log( "drawing/mousemove:local pos : %s", local_pos.to_json() );
            //console.log( "drawing/mousemove:zone pos : %s", zone_pos.to_json() );

            // FIXME: detect target zone
            // target_zone = local_to_target_ f( zone_pos )
            var bound = _zone.is_bound( zone_pos );
            // console.log( "drawing/mousemove: zone.is_bound = ", bound );

            if ( bound ) {
                self.pixel_set( local_pos, _color );
            } else {
                //FIXME: _color = _zone.pixel_get( zone_pos );
            }

        }
    };

    /**
     * change pixel at given position, on canvas only
     */
    this.pixel_draw = function( local_pos, color ) {
        var ctx = self.context;
        //console.log("drawing/pixel_draw local_pos = %s", local_pos.to_json() );
        var canvas_pos = local_to_canvas_position( local_pos );
        var rect = {
            x : canvas_pos.x + (0.1 * self.column_size),
            y : canvas_pos.y + (0.1 * self.column_size),
            w : self.column_size - ( 0.2 * self.column_size ),
            h : self.line_size - ( 0.2 * self.column_size )
        };
        //console.log("drawing/pixel_draw rect = %s", rect.to_json() );

        ctx.fillStyle = ZONE_BACKGROUND_COLOR;
        ctx.fillRect( rect.x, rect.y, rect.w, rect.h );

        ctx.fillStyle = color;
        ctx.fillRect( rect.x, rect.y, rect.w, rect.h );
    }

    /*
     * Set pixel at given position to given color
     */
    this.pixel_set = function( local_pos, color ) {
        var zone_pos;

        zone_pos = local_to_zone_position( local_pos );
        //console.log( "drawing/pixel_set: zone_pos = %s", zone_pos.to_json() );
        // record to zone
        _zone.pixel_set( zone_pos, color );
        // add to patch structure
        _zone.patch_record( zone_pos, color );
        // draw localy
        self.pixel_draw( local_pos, color );
    };

    /**
     * Pick color of pixel
     */
    this.pixel_get = function( local_pos ) {
        //FIXME: detect bound zone...
        //_return zone.pixel_get( pos );
    }

    /**
      * Change color
      */

    this.color_set = function( hexcolor ) {
        _color = hexcolor;
        // FIXME:
        console.log("drawing/color_set: requestion patch enqueue")
        _zone.patch_enqueue();
    }

    var canvas_event = function( event_obj ) {
        var canvas = self.real_canvas;

        event_obj.mouseX = event_obj.pageX - canvas.offsetLeft;
        event_obj.mouseY = event_obj.pageY - canvas.offsetTop;

        var func = self[event_obj.type];
        if (func) { func( event_obj ); }
        // console.log("clicked at %s,%s", mouseX, mouseY );
    };

    this.to_s = function() { JSON.stringify(this); };

    var _zone = new Zone( p_session.zone_column_count, p_session.zone_line_count );
    var _color = '#f00';

    this.patch = null;
    this.move = {
        enable : false
    }
    this.session = p_session;
    this.column_count = p_session.zone_column_count;
    this.line_count = p_session.zone_line_count;
    this.border_column_count = p_session.zone_column_count / 4;
    this.border_line_count = p_session.zone_column_count / 4;

    this.real_canvas = document.getElementById( p_canvas_id );

    this.grid_canvas = null;

    // size of zone's big pixels
    this.column_size = 1;
    this.line_size = 1;

    var _pull_timer = window.setInterval( this.pull_patches, DRAWING_REFRESH );
    var _push_timer = window.setInterval( this.push_patches, DRAWING_PUSH_REFRESH );
    var _enqueue_timer = window.setInterval( _zone.patch_enqueue, PATCH_LIFESPAN );

    this.context = this.real_canvas.getContext('2d');

    // plug some event handlers
    this.real_canvas.addEventListener( 'mousedown', canvas_event, false );
    this.real_canvas.addEventListener( 'touchstart', canvas_event, false );

    this.real_canvas.addEventListener( 'mouseup', canvas_event, false );
    this.real_canvas.addEventListener( 'touchstop', canvas_event, false );

    this.real_canvas.addEventListener( 'mousemove', canvas_event, false );
    this.real_canvas.addEventListener( 'touchmove', canvas_event, false );

    $(window).resize(function() {
        self.update_size();
        self.update_paint();
    });

    this.update_size();
    this.update_paint();
    // console.log("drawing_id = " + p_canvas_id);
}

<|endoftext|>