<commit_before>// use fetch to retrieve the products and pass them to init
// report any errors that occur in the fetch operation
// once the products have been successfully loaded and formatted as a JSON object
// using response.json(), run the initialize() function
fetch('products.json').then(function(response) {
  return response.json();
}).then(function(json) {
  let products = json;
  initialize(products);
}).catch(function(err) {
  console.log('Fetch problem: ' + err.message);
});

// sets up the app logic, declares required variables, contains all the other functions
function initialize(products) {
  // grab the UI elements that we need to manipulate
  const category = document.querySelector('#category');
  const searchTerm = document.querySelector('#searchTerm');
  const searchBtn = document.querySelector('button');
  const main = document.querySelector('main');

  // keep a record of what the last category and search term entered were
  let lastCategory = category.value;
  // no search has been made yet
  let lastSearch = '';

  // these contain the results of filtering by category, and search term
  // finalGroup will contain the products that need to be displayed after
  // the searching has been done. Each will be an array containing objects.
  // Each object will represent a product
  let categoryGroup;
  let finalGroup;

  // To start with, set finalGroup to equal the entire products database
  // then run updateDisplay(), so ALL products are displayed initially.
  finalGroup = products;
  updateDisplay();

  // Set both to equal an empty array, in time for searches to be run
  categoryGroup = [];
  finalGroup = [];

  // when the search button is clicked, invoke selectCategory() to start
  // a search running to select the category of products we want to display
  searchBtn.onclick = selectCategory;

  function selectCategory(e) {
    // Use preventDefault() to stop the form submitting — that would ruin
    // the experience
    e.preventDefault();

    // Set these back to empty arrays, to clear out the previous search
    categoryGroup = [];
    finalGroup = [];

    // if the category and search term are the same as they were the last time a
    // search was run, the results will be the same, so there is no point running
    // it again — just return out of the function
    if(category.value === lastCategory && searchTerm.value.trim() === lastSearch) {
      return;
    } else {
      // update the record of last category and search term
      lastCategory = category.value;
      lastSearch = searchTerm.value.trim();
      // In this case we want to select all products, then filter them by the search
      // term, so we just set categoryGroup to the entire JSON object, then run selectProducts()
      if(category.value === 'All') {
        categoryGroup = products;
        selectProducts();
      // If a specific category is chosen, we need to filter out the products not in that
      // category, then put the remaining products inside categoryGroup, before running
      // selectProducts()
      } else {
        // the values in the <option> elements are uppercase, whereas the categories
        // store in the JSON (under "type") are lowercase. We therefore need to convert
        // to lower case before we do a comparison
        let lowerCaseType = category.value.toLowerCase();
        for(let i = 0; i < products.length ; i++) {
          // If a product's type property is the same as the chosen category, we want to
          // dispay it, so we push it onto the categoryGroup array
          if(products[i].type === lowerCaseType) {
            categoryGroup.push(products[i]);
          }
        }

        // Run selectProducts() after the filtering has bene done
        selectProducts();
      }
    }
  }

  // selectProducts() Takes the group of products selected by selectCategory(), and further
  // filters them by the tnered search term (if one has bene entered)
  function selectProducts() {
    // If no search term has been entered, just make the finalGroup array equal to the categoryGroup
    // array — we don't want to filter the products further — then run updateDisplay().
    if(searchTerm.value.trim() === '') {
      finalGroup = categoryGroup;
      updateDisplay();
    } else {
      // Make sure the search term is converted to lower case before comparison. We've kept the
      // product names all lower case to keep things simple
      let lowerCaseSearchTerm = searchTerm.value.trim().toLowerCase();
      // For each product in categoryGroup, see if the search term is contained inside the product name
      // (if the indexOf() result doesn't return -1, it means it is) — if it is, then push the product
      // onto the finalGroup array
      for(let i = 0; i < categoryGroup.length ; i++) {
        if(categoryGroup[i].name.indexOf(lowerCaseSearchTerm) !== -1) {
          finalGroup.push(categoryGroup[i]);
        }
      }

      // run updateDisplay() after this second round of filtering has been done
      updateDisplay();
    }

  }

  // start the process of updating the display with the new set of products
  function updateDisplay() {
    // remove the previous contents of the <main> element
    while (main.firstChild) {
      main.removeChild(main.firstChild);
    }

    // if no products match the search term, display a "No results to display" message
    if(finalGroup.length === 0) {
      const para = document.createElement('p');
      para.textContent = 'No results to display!';
      main.appendChild(para);
    // for each product we want to display, pass its product object to fetchBlob()
    } else {
      for(let i = 0; i < finalGroup.length; i++) {
        fetchBlob(finalGroup[i]);
      }
    }
  }

  // fetchBlob uses fetch to retrieve the image for that product, and then sends the
  // resulting image display URL and product object on to showProduct() to finally
  // display it
  function fetchBlob(product) {
    // construct the URL path to the image file from the product.image property
    let url = 'images/' + product.image;
    // Use fetch to fetch the image, and convert the resulting response to a blob
    // Again, if any errors occur we report them in the console.
    fetch(url).then(function(response) {
        return response.blob();
    }).then(function(blob) {
      // Convert the blob to an object URL — this is basically an temporary internal URL
      // that points to an object stored inside the browser
      let objectURL = URL.createObjectURL(blob);
      // invoke showProduct
      showProduct(objectURL, product);
    });
  }

  // Display a product inside the <main> element
  function showProduct(objectURL, product) {
    // create <section>, <h2>, <p>, and <img> elements
    const section = document.createElement('section');
    const heading = document.createElement('h2');
    const para = document.createElement('p');
    const image = document.createElement('img');

    // give the <section> a classname equal to the product "type" property so it will display the correct icon
    section.setAttribute('class', product.type);

    // Give the <h2> textContent equal to the product "name" property, but with the first character
    // replaced with the uppercase version of the first character
    heading.textContent = product.name.replace(product.name.charAt(0), product.name.charAt(0).toUpperCase());

    // Give the <p> textContent equal to the product "price" property, with a $ sign in front
    // toFixed(2) is used to fix the price at 2 decimal places, so for example 1.40 is displayed
    // as 1.40, not 1.4.
    para.textContent = '$' + product.price.toFixed(2);

    // Set the src of the <img> element to the ObjectURL, and the alt to the product "name" property
    image.src = objectURL;
    image.alt = product.name;

    // append the elements to the DOM as appropriate, to add the product to the UI
    main.appendChild(section);
    section.appendChild(heading);
    section.appendChild(para);
    section.appendChild(image);
  }
}
<commit_msg>Fix typo<commit_after>// use fetch to retrieve the products and pass them to init
// report any errors that occur in the fetch operation
// once the products have been successfully loaded and formatted as a JSON object
// using response.json(), run the initialize() function
fetch('products.json').then(function(response) {
  return response.json();
}).then(function(json) {
  let products = json;
  initialize(products);
}).catch(function(err) {
  console.log('Fetch problem: ' + err.message);
});

// sets up the app logic, declares required variables, contains all the other functions
function initialize(products) {
  // grab the UI elements that we need to manipulate
  const category = document.querySelector('#category');
  const searchTerm = document.querySelector('#searchTerm');
  const searchBtn = document.querySelector('button');
  const main = document.querySelector('main');

  // keep a record of what the last category and search term entered were
  let lastCategory = category.value;
  // no search has been made yet
  let lastSearch = '';

  // these contain the results of filtering by category, and search term
  // finalGroup will contain the products that need to be displayed after
  // the searching has been done. Each will be an array containing objects.
  // Each object will represent a product
  let categoryGroup;
  let finalGroup;

  // To start with, set finalGroup to equal the entire products database
  // then run updateDisplay(), so ALL products are displayed initially.
  finalGroup = products;
  updateDisplay();

  // Set both to equal an empty array, in time for searches to be run
  categoryGroup = [];
  finalGroup = [];

  // when the search button is clicked, invoke selectCategory() to start
  // a search running to select the category of products we want to display
  searchBtn.onclick = selectCategory;

  function selectCategory(e) {
    // Use preventDefault() to stop the form submitting — that would ruin
    // the experience
    e.preventDefault();

    // Set these back to empty arrays, to clear out the previous search
    categoryGroup = [];
    finalGroup = [];

    // if the category and search term are the same as they were the last time a
    // search was run, the results will be the same, so there is no point running
    // it again — just return out of the function
    if(category.value === lastCategory && searchTerm.value.trim() === lastSearch) {
      return;
    } else {
      // update the record of last category and search term
      lastCategory = category.value;
      lastSearch = searchTerm.value.trim();
      // In this case we want to select all products, then filter them by the search
      // term, so we just set categoryGroup to the entire JSON object, then run selectProducts()
      if(category.value === 'All') {
        categoryGroup = products;
        selectProducts();
      // If a specific category is chosen, we need to filter out the products not in that
      // category, then put the remaining products inside categoryGroup, before running
      // selectProducts()
      } else {
        // the values in the <option> elements are uppercase, whereas the categories
        // store in the JSON (under "type") are lowercase. We therefore need to convert
        // to lower case before we do a comparison
        let lowerCaseType = category.value.toLowerCase();
        for(let i = 0; i < products.length ; i++) {
          // If a product's type property is the same as the chosen category, we want to
          // display it, so we push it onto the categoryGroup array
          if(products[i].type === lowerCaseType) {
            categoryGroup.push(products[i]);
          }
        }

        // Run selectProducts() after the filtering has been done
        selectProducts();
      }
    }
  }

  // selectProducts() Takes the group of products selected by selectCategory(), and further
  // filters them by the tiered search term (if one has been entered)
  function selectProducts() {
    // If no search term has been entered, just make the finalGroup array equal to the categoryGroup
    // array — we don't want to filter the products further — then run updateDisplay().
    if(searchTerm.value.trim() === '') {
      finalGroup = categoryGroup;
      updateDisplay();
    } else {
      // Make sure the search term is converted to lower case before comparison. We've kept the
      // product names all lower case to keep things simple
      let lowerCaseSearchTerm = searchTerm.value.trim().toLowerCase();
      // For each product in categoryGroup, see if the search term is contained inside the product name
      // (if the indexOf() result doesn't return -1, it means it is) — if it is, then push the product
      // onto the finalGroup array
      for(let i = 0; i < categoryGroup.length ; i++) {
        if(categoryGroup[i].name.indexOf(lowerCaseSearchTerm) !== -1) {
          finalGroup.push(categoryGroup[i]);
        }
      }

      // run updateDisplay() after this second round of filtering has been done
      updateDisplay();
    }

  }

  // start the process of updating the display with the new set of products
  function updateDisplay() {
    // remove the previous contents of the <main> element
    while (main.firstChild) {
      main.removeChild(main.firstChild);
    }

    // if no products match the search term, display a "No results to display" message
    if(finalGroup.length === 0) {
      const para = document.createElement('p');
      para.textContent = 'No results to display!';
      main.appendChild(para);
    // for each product we want to display, pass its product object to fetchBlob()
    } else {
      for(let i = 0; i < finalGroup.length; i++) {
        fetchBlob(finalGroup[i]);
      }
    }
  }

  // fetchBlob uses fetch to retrieve the image for that product, and then sends the
  // resulting image display URL and product object on to showProduct() to finally
  // display it
  function fetchBlob(product) {
    // construct the URL path to the image file from the product.image property
    let url = 'images/' + product.image;
    // Use fetch to fetch the image, and convert the resulting response to a blob
    // Again, if any errors occur we report them in the console.
    fetch(url).then(function(response) {
        return response.blob();
    }).then(function(blob) {
      // Convert the blob to an object URL — this is basically an temporary internal URL
      // that points to an object stored inside the browser
      let objectURL = URL.createObjectURL(blob);
      // invoke showProduct
      showProduct(objectURL, product);
    });
  }

  // Display a product inside the <main> element
  function showProduct(objectURL, product) {
    // create <section>, <h2>, <p>, and <img> elements
    const section = document.createElement('section');
    const heading = document.createElement('h2');
    const para = document.createElement('p');
    const image = document.createElement('img');

    // give the <section> a classname equal to the product "type" property so it will display the correct icon
    section.setAttribute('class', product.type);

    // Give the <h2> textContent equal to the product "name" property, but with the first character
    // replaced with the uppercase version of the first character
    heading.textContent = product.name.replace(product.name.charAt(0), product.name.charAt(0).toUpperCase());

    // Give the <p> textContent equal to the product "price" property, with a $ sign in front
    // toFixed(2) is used to fix the price at 2 decimal places, so for example 1.40 is displayed
    // as 1.40, not 1.4.
    para.textContent = '$' + product.price.toFixed(2);

    // Set the src of the <img> element to the ObjectURL, and the alt to the product "name" property
    image.src = objectURL;
    image.alt = product.name;

    // append the elements to the DOM as appropriate, to add the product to the UI
    main.appendChild(section);
    section.appendChild(heading);
    section.appendChild(para);
    section.appendChild(image);
  }
}
<|endoftext|>


<commit_before>/*
******************************************************
* @realHellToupee
* Built by artist Sofa King Awesome
* v1 ship 12.26.2015
* Notice me senpai!
******************************************************
*/

var Twit = require('twit');
var fs = require('fs');
var http = require('http');

/*
******************************************************
* Setting up web frontend for heroku
******************************************************
*/

http.createServer(function (request, response) {

  var filePath = './index.html';
  var contentType = 'text/html';

  response.writeHead(200, { 'Content-Type': contentType });
  response.end(content, 'utf-8');

}).listen(5000);

/*
******************************************************
* Initial twitter setup
******************************************************
*/

// declare variables to hold twitter app secrets
// file pathing done for OSX / Linux, may need to change for Windows
// perhaps using the path module in Node.js could make this cross-platform easier

/* scrub secrets from localstore
var consumer_key = fs.readFileSync('./consumerkey.txt', 'utf-8');
var consumer_secret = fs.readFileSync('./consumersecret.txt', 'utf-8');
var access_token = fs.readFileSync('./accesstoken.txt', 'utf-8');
var access_token_secret = fs.readFileSync('./accesstokensecret.txt', 'utf-8');
*/

// scrub secrets from environment variables for cloud deploy
var consumer_key = process.env.CONSUMERKEY;
var consumer_secret = process.env.CONSUMERSECRET;
var access_token = process.env.ACCESSTOKEN;
var access_token_secret = process.env.ACCESSTOKENSECRET;

// setup Twit with our secrets
var T = new Twit({
  consumer_key: consumer_key,
  consumer_secret: consumer_secret,
  access_token: access_token,
  access_token_secret: access_token_secret
});
var text; // to hold tweet text
var runCounter = 0; // to measure overall runs

/*
******************************************************
* BEGIN two minute refresh loop
******************************************************
*/

function harvestTweets(){
  runCounter++;
  var stream = T.stream('statuses/filter', { track: '@realDonaldTrump,Trump' })

  stream.on('tweet', function (tweet) {
    text = tweet['text'];
    var httpcount = 0;
    var shortcount = 0;

   // reject short tweets, ask for another
   if (text.length < 50){
    shortcount++;
    console.log("skipping b/c short: " + shortcount + '\n'); // debug statement
    return;
  }

   // reject tweets that have hyperlinks - was rejecting too many, turned it off
   /*
   if (text.indexOf('http') > -1){
      httpcount++;
      console.log(text);
      console.log("skipping http: " + httpcount + '\n');
      return;
   }
   */

   // test for strings of interest
   var containsFlag = -1;
   containsFlag += text.indexOf('trump') + text.indexOf('Trump') + text.indexOf('donald') + text.indexOf('Donald');
   if (containsFlag < 0){
    return;
  }

   // regex logic to swap words
      // 'Trump' and all variations => 'WhISIS'
      // 'Make America Great Again' and all variations => 'Notice me, Senpai!'
  else{
    console.log('Unedited tweet ' + runCounter + ': ' + text);
    stream.stop(); // shutdown stream API, we've got what we need

    // strip @'s to avoid non-consensual replies
    var patt = /@/g;
    text = text.replace(patt, '');

    // change & to &
    patt = /&/g;
    text = text.replace(patt, '&');

    // replace all trumps (not sure how to replace across a newline, e.g. "This text has Mr. \n Donald Trump")
    // this list is ordered in precedence
    patt = /realDonaldTrump/gi;
    text = text.replace(patt, 'WhISIS');

    patt = /Mr. Donald Trump/gi;
    text = text.replace(patt, 'WhISIS');

    patt = /The Donald/gi;
    text = text.replace(patt, 'WhISIS');

    patt = /Donald Trump/gi;
    text = text.replace(patt, 'WhISIS');

    patt = /trump/gi;
    text = text.replace(patt, 'WhISIS');

    patt = /make america great again/gi;
    text = text.replace(patt, 'Notice me, Senpai!');

    patt = /make america great/gi;
    text = text.replace(patt, 'Notice me, Senpai!');

    patt = /MakeAmericaGreatAgain/gi;
    text = text.replace(patt, 'Notice me, Senpai!');

    patt = /MakeAmericaGreat/gi;
    text = text.replace(patt, 'Notice me, Senpai!');

    console.log('Edited tweet ' + runCounter + ': ' + text);

    // post the tweet

    T.post('statuses/update', { status: text }, function(err, data, response) {
      console.log(data); // for some reason taking this line out makes the post not work...
      if (typeof err !== 'undefined'){
        console.log ('######### Error Posting Tweet ######### ' + err + '\n');
      }
    });

    return;
  }

});
}

harvestTweets();
setInterval(harvestTweets, 120000);

/*
******************************************************
* END two minute refresh loop
******************************************************
*/

/*
******************************************************
* Favorite RT's of Me (Every 5 hours)
******************************************************
*/

function favRTs () {
  T.get('statuses/retweets_of_me', {}, function (e,r) {
    for(var i=0;i<r.length;i++) {
      T.post('favorites/create/'+r[i].id_str,{},function(){});
    }
    console.log('harvested some RTs');
  });
}

setInterval(function() {
  try {
    favRTs();
  }
  catch (e) {
    console.log(e);
  }
},60000*60*5);

/*
******************************************************
* to do
******************************************************
*/

/*

To do:
-X how will we post the message back to twitter? (REST API?)
-X sometimes it just hangs and never gets a tweet...what's happening then? we need a timeout escape if no tweet is received
  where it closes and re-opens the stream - we'll just wait for the next 2 minute interval, opening a new stream connection
  closes the old one
-X how will we implement the two minute refresh rule? - see setInterval function in comments below
- how often should we scrub from trump's own twitter account? once every hour? should we signal this tweet as a WhISIS original?
    (perhaps a tweet before it says "Up next - a WhISIS original!")
- what should we do if the tweet is too long? (need a safety check for 140 chars)
- how are we going to deal with escape characters like: \'   and    \n  ?

// date function not needed if using streaming API
// useful for REST API searching
// scrub today's date
var today = new Date();
var dd = today.getDate();
var mm = today.getMonth()+1; //January is 0!
var yyyy = today.getFullYear();

if(dd<10){
  dd='0'+dd
    }
if(mm<10){
  mm='0'+mm
    }

var today = yyyy + '-' + mm + '-' + dd;

*/
<commit_msg>deleted whitespace so i can redeploy to heroku with updated config var<commit_after>/*
******************************************************
* @realHellToupee
* Built by artist Sofa King Awesome
* v1 ship 12.26.2015
* Notice me senpai!
******************************************************
*/

var Twit = require('twit');
var fs = require('fs');
var http = require('http');

/*
******************************************************
* Setting up web frontend for heroku
******************************************************
*/

http.createServer(function (request, response) {

  var filePath = './index.html';
  var contentType = 'text/html';
  response.writeHead(200, { 'Content-Type': contentType });
  response.end(content, 'utf-8');

}).listen(5000);

/*
******************************************************
* Initial twitter setup
******************************************************
*/

// declare variables to hold twitter app secrets
// file pathing done for OSX / Linux, may need to change for Windows
// perhaps using the path module in Node.js could make this cross-platform easier

/* scrub secrets from localstore
var consumer_key = fs.readFileSync('./consumerkey.txt', 'utf-8');
var consumer_secret = fs.readFileSync('./consumersecret.txt', 'utf-8');
var access_token = fs.readFileSync('./accesstoken.txt', 'utf-8');
var access_token_secret = fs.readFileSync('./accesstokensecret.txt', 'utf-8');
*/

// scrub secrets from environment variables for cloud deploy
var consumer_key = process.env.CONSUMERKEY;
var consumer_secret = process.env.CONSUMERSECRET;
var access_token = process.env.ACCESSTOKEN;
var access_token_secret = process.env.ACCESSTOKENSECRET;

// setup Twit with our secrets
var T = new Twit({
  consumer_key: consumer_key,
  consumer_secret: consumer_secret,
  access_token: access_token,
  access_token_secret: access_token_secret
});
var text; // to hold tweet text
var runCounter = 0; // to measure overall runs

/*
******************************************************
* BEGIN two minute refresh loop
******************************************************
*/

function harvestTweets(){
  runCounter++;
  var stream = T.stream('statuses/filter', { track: '@realDonaldTrump,Trump' })

  stream.on('tweet', function (tweet) {
    text = tweet['text'];
    var httpcount = 0;
    var shortcount = 0;

   // reject short tweets, ask for another
   if (text.length < 50){
    shortcount++;
    console.log("skipping b/c short: " + shortcount + '\n'); // debug statement
    return;
  }

   // reject tweets that have hyperlinks - was rejecting too many, turned it off
   /*
   if (text.indexOf('http') > -1){
      httpcount++;
      console.log(text);
      console.log("skipping http: " + httpcount + '\n');
      return;
   }
   */

   // test for strings of interest
   var containsFlag = -1;
   containsFlag += text.indexOf('trump') + text.indexOf('Trump') + text.indexOf('donald') + text.indexOf('Donald');
   if (containsFlag < 0){
    return;
  }

   // regex logic to swap words
      // 'Trump' and all variations => 'WhISIS'
      // 'Make America Great Again' and all variations => 'Notice me, Senpai!'
  else{
    console.log('Unedited tweet ' + runCounter + ': ' + text);
    stream.stop(); // shutdown stream API, we've got what we need

    // strip @'s to avoid non-consensual replies
    var patt = /@/g;
    text = text.replace(patt, '');

    // change & to &
    patt = /&/g;
    text = text.replace(patt, '&');

    // replace all trumps (not sure how to replace across a newline, e.g. "This text has Mr. \n Donald Trump")
    // this list is ordered in precedence
    patt = /realDonaldTrump/gi;
    text = text.replace(patt, 'WhISIS');

    patt = /Mr. Donald Trump/gi;
    text = text.replace(patt, 'WhISIS');

    patt = /The Donald/gi;
    text = text.replace(patt, 'WhISIS');

    patt = /Donald Trump/gi;
    text = text.replace(patt, 'WhISIS');

    patt = /trump/gi;
    text = text.replace(patt, 'WhISIS');

    patt = /make america great again/gi;
    text = text.replace(patt, 'Notice me, Senpai!');

    patt = /make america great/gi;
    text = text.replace(patt, 'Notice me, Senpai!');

    patt = /MakeAmericaGreatAgain/gi;
    text = text.replace(patt, 'Notice me, Senpai!');

    patt = /MakeAmericaGreat/gi;
    text = text.replace(patt, 'Notice me, Senpai!');

    console.log('Edited tweet ' + runCounter + ': ' + text);

    // post the tweet

    T.post('statuses/update', { status: text }, function(err, data, response) {
      console.log(data); // for some reason taking this line out makes the post not work...
      if (typeof err !== 'undefined'){
        console.log ('######### Error Posting Tweet ######### ' + err + '\n');
      }
    });

    return;
  }

});
}

harvestTweets();
setInterval(harvestTweets, 120000);

/*
******************************************************
* END two minute refresh loop
******************************************************
*/

/*
******************************************************
* Favorite RT's of Me (Every 5 hours)
******************************************************
*/

function favRTs () {
  T.get('statuses/retweets_of_me', {}, function (e,r) {
    for(var i=0;i<r.length;i++) {
      T.post('favorites/create/'+r[i].id_str,{},function(){});
    }
    console.log('harvested some RTs');
  });
}

setInterval(function() {
  try {
    favRTs();
  }
  catch (e) {
    console.log(e);
  }
},60000*60*5);

/*
******************************************************
* to do
******************************************************
*/

/*

To do:
-X how will we post the message back to twitter? (REST API?)
-X sometimes it just hangs and never gets a tweet...what's happening then? we need a timeout escape if no tweet is received
  where it closes and re-opens the stream - we'll just wait for the next 2 minute interval, opening a new stream connection
  closes the old one
-X how will we implement the two minute refresh rule? - see setInterval function in comments below
- how often should we scrub from trump's own twitter account? once every hour? should we signal this tweet as a WhISIS original?
    (perhaps a tweet before it says "Up next - a WhISIS original!")
- what should we do if the tweet is too long? (need a safety check for 140 chars)
- how are we going to deal with escape characters like: \'   and    \n  ?

// date function not needed if using streaming API
// useful for REST API searching
// scrub today's date
var today = new Date();
var dd = today.getDate();
var mm = today.getMonth()+1; //January is 0!
var yyyy = today.getFullYear();

if(dd<10){
  dd='0'+dd
    }
if(mm<10){
  mm='0'+mm
    }

var today = yyyy + '-' + mm + '-' + dd;

*/
<|endoftext|>


<commit_before>/**
 *  Entry point for dashboard
 */

$(function() {

  var Dashboard = cdb.core.View.extend({

    el: document.body,

    events: {
      'click .paginator a': 'pageChange'
    },

    initialize: function() {
      _.bindAll(this, 'pageChange')
      this._initModels();
      this._initViews();

    },

    _initModels: function() {
      this.user = new cdb.admin.User(this.options.user_data);
      this.tables = new cdb.admin.Tables();
      this.tags = new cdb.admin.Tags();
      this.first_time = true;

      // The user model has to update every time the table model
      this.tables.bind('add remove reset', function(){
        if (!this.first_time)
          this.user.fetch();

        this.first_time = false;
      }, this);
      this.tables.bind('error', function(e) {
        cdb.log.info("error", e);
      });
    },

    _initViews: function() {
      // Table list
      this.tableList = new cdb.admin.dashboard.TableList({
        el: this.$('#tablelist'),
        model: this.tables,
        config: this.options.config,
        user: this.user
      });

      // User data
      this.user_stats = new cdb.admin.dashboard.UserStats({
        el: this.$('div.subheader'),
        model: this.user
      });

      this.user_stats.render();

      // Choose sceneario
      this.scenario = new cdb.admin.dashboard.Scenario({
        el: this.$el,
        model: this.user
      });

      // Search form
      var search_form = this.search_form = new cdb.ui.common.SearchView({
        el: this.$('header ul li.search')
      });

      // User menu
      var user_menu = this.user_menu = new cdb.admin.DropdownMenu({
        target:         $('a.account'),
        host:           config.account_host,
        username:       username,
        template_base:  'common/views/settings_item'
      });
      cdb.god.bind("closeDialogs", user_menu.hide, user_menu);
      this.$el.append(this.user_menu.render().el);

      // Background Importer
      var bkg_importer = this.bkg_importer = new cdb.ui.common.BackgroundImporter({
        template_base: 'common/views/background_importer'
      });
      this.$el.append(this.bkg_importer.render().el);

      // Paginator
      var paginator = this.paginator = new cdb.admin.dashboard.DashboardPaginator({
        el: this.$("div.paginator"),
        model: this.tables
      });

      // Tables tags
      var tagsView = this.tagsView = new cdb.admin.dashboard.TagsView({
        el: this.$('aside div.content ul:eq(0)'),
        tables: this.tables,
        model: this.tags
      });

      // Tipsy
      this.$el.find("a.tooltip").tipsy({gravity: 's', fade:true, live:true});

      // Create new table view
      var createTable = this.createTable = new cdb.admin.dashboard.CreateTable({
        el:       this.$el,
        importer: this.bkg_importer,
        tables:   this.tables,
        model:    this.user
      });

      // global click
      enableClickOut(this.$el);
    },

    pageChange: function(ev) {
      if($(ev.currentTarget).attr('href') != location.hash) {
        this.$('.content ul').html('

');
      }
    },

  });

  var DashboardRouter = Backbone.Router.extend({

    routes: {
      '':                 'index',
      'page/:p':          'paginate',
      'tag/:tag/:p':      'searchTag',
      'search/:query/:p': 'searchQuery'
    },

    initialize: function() {
      window.dashboard.tables.options.bind("change", this.update ,this);
    },

    update: function() {
      var hash = window.location.hash.split("/");
      hash[hash.length - 1] = window.dashboard.tables.options.get("page");
      this.navigate(hash.join("/"));
    },

    index: function() {
      window.dashboard.tables.options.set({
        "tag_name"  : "",
        "q"         : "",
        "page"      : 1
      });
    },
    searchTag: function(tag,p) {
      window.dashboard.tables.options.set({
        "tag_name"  : tag,
        "page"      : p,
        "q"         : ""
      });
    },
    searchQuery: function(query,p) {
      window.dashboard.tables.options.set({
        "tag_name"  : "",
        "page"      : p,
        "q"         : query
      });
    },
    paginate: function(p) {
      window.dashboard.tables.options.set({
        "tag_name"  : "",
        "page"      : p,
        "q"         : ""
      });
    }
  });

  cdb.init(function() {
    cdb.templates.namespace = 'cartodb/';
    var dashboard = new Dashboard({
      user_data: user_data,
      config: config
    });

    // Expose to debug
    window.dashboard = dashboard;

    // Routing
    var router = new DashboardRouter();
    Backbone.history.start();

    // No route => tables fetch
    if (window.dashboard.tables.options.get("tag_name") === "" &&
      window.dashboard.tables.options.get("q") === "") {
      window.dashboard.tables.fetch();
    }
  });

});
<commit_msg>conditional options.el added for testability<commit_after>/**
 *  Entry point for dashboard
 */

$(function() {

  var Dashboard = cdb.core.View.extend({

    el: document.body,

    events: {
      'click .paginator a': 'pageChange'
    },

    initialize: function() {
      if(options.el) {
        this.el = options.el;
      }
      _.bindAll(this, 'pageChange')
      this._initModels();
      this._initViews();

    },

    _initModels: function() {
      this.user = new cdb.admin.User(this.options.user_data);
      this.tables = new cdb.admin.Tables();
      this.tags = new cdb.admin.Tags();
      this.first_time = true;

      // The user model has to update every time the table model
      this.tables.bind('add remove reset', function(){
        if (!this.first_time)
          this.user.fetch();

        this.first_time = false;
      }, this);
      this.tables.bind('error', function(e) {
        cdb.log.info("error", e);
      });
    },

    _initViews: function() {
      // Table list
      this.tableList = new cdb.admin.dashboard.TableList({
        el: this.$('#tablelist'),
        model: this.tables,
        config: this.options.config,
        user: this.user
      });

      // User data
      this.user_stats = new cdb.admin.dashboard.UserStats({
        el: this.$('div.subheader'),
        model: this.user
      });

      this.user_stats.render();

      // Choose sceneario
      this.scenario = new cdb.admin.dashboard.Scenario({
        el: this.$el,
        model: this.user
      });

      // Search form
      var search_form = this.search_form = new cdb.ui.common.SearchView({
        el: this.$('header ul li.search')
      });

      // User menu
      var user_menu = this.user_menu = new cdb.admin.DropdownMenu({
        target:         $('a.account'),
        host:           config.account_host,
        username:       username,
        template_base:  'common/views/settings_item'
      });
      cdb.god.bind("closeDialogs", user_menu.hide, user_menu);
      this.$el.append(this.user_menu.render().el);

      // Background Importer
      var bkg_importer = this.bkg_importer = new cdb.ui.common.BackgroundImporter({
        template_base: 'common/views/background_importer'
      });
      this.$el.append(this.bkg_importer.render().el);

      // Paginator
      var paginator = this.paginator = new cdb.admin.dashboard.DashboardPaginator({
        el: this.$("div.paginator"),
        model: this.tables
      });

      // Tables tags
      var tagsView = this.tagsView = new cdb.admin.dashboard.TagsView({
        el: this.$('aside div.content ul:eq(0)'),
        tables: this.tables,
        model: this.tags
      });

      // Tipsy
      this.$el.find("a.tooltip").tipsy({gravity: 's', fade:true, live:true});

      // Create new table view
      var createTable = this.createTable = new cdb.admin.dashboard.CreateTable({
        el:       this.$el,
        importer: this.bkg_importer,
        tables:   this.tables,
        model:    this.user
      });

      // global click
      enableClickOut(this.$el);
    },

    pageChange: function(ev) {
      if($(ev.currentTarget).attr('href') != location.hash) {
        this.$('.content ul').html('

');
      }
    },

  });

  var DashboardRouter = Backbone.Router.extend({

    routes: {
      '':                 'index',
      'page/:p':          'paginate',
      'tag/:tag/:p':      'searchTag',
      'search/:query/:p': 'searchQuery'
    },

    initialize: function() {
      window.dashboard.tables.options.bind("change", this.update ,this);
    },

    update: function() {
      var hash = window.location.hash.split("/");
      hash[hash.length - 1] = window.dashboard.tables.options.get("page");
      this.navigate(hash.join("/"));
    },

    index: function() {
      window.dashboard.tables.options.set({
        "tag_name"  : "",
        "q"         : "",
        "page"      : 1
      });
    },
    searchTag: function(tag,p) {
      window.dashboard.tables.options.set({
        "tag_name"  : tag,
        "page"      : p,
        "q"         : ""
      });
    },
    searchQuery: function(query,p) {
      window.dashboard.tables.options.set({
        "tag_name"  : "",
        "page"      : p,
        "q"         : query
      });
    },
    paginate: function(p) {
      window.dashboard.tables.options.set({
        "tag_name"  : "",
        "page"      : p,
        "q"         : ""
      });
    }
  });

  cdb.init(function() {
    cdb.templates.namespace = 'cartodb/';
    var dashboard = new Dashboard({
      user_data: user_data,
      config: config
    });

    // Expose to debug
    window.dashboard = dashboard;

    // Routing
    var router = new DashboardRouter();
    Backbone.history.start();

    // No route => tables fetch
    if (window.dashboard.tables.options.get("tag_name") === "" &&
      window.dashboard.tables.options.get("q") === "") {
      window.dashboard.tables.fetch();
    }
  });

});
<|endoftext|>


<commit_before>cdb.admin.overlays.Text = cdb.geo.ui.Text.extend({

  className: "text overlay snap",

  template_name: 'table/views/overlays/text',

  events: {

    "mouseenter .text":  "_onMouseEnter",

    "click .close":      "_close",
    "click .content":    "_onClickEdit",
    "click .text":       "_onClickEdit",
    "dblclick .content": "_onDblClick",
    "dblclick .text":    "_onDblClick",

    "keyup .text":       "_onKeyUp",
    "paste .text":       "_onPaste"

  },

  form_data: [{
    name: 'Text',
    form: {
      'font-size':  { type: 'simple_number', value: 12, min: 5, max: 50, inc: 2, disable_triggering: true },
      'color':      { type: 'color', value: '#FFF', extra: { tick: "left", picker_horizontal_position: "left", picker_vertical_position: "down" }},
      'font-family-name': {
        type: 'select',
        value: "Helvetica",
        extra: ["Helvetica", "Droid Sans", "Vollkorn", "Roboto", "Open Sans", "Lato", "Graduate", "Gravitas One", "Old Standard TT"]

      },
      'text-align':      { type: 'text_align', value: 'left' }
    }
  }, {
    name: 'Box',
    form: {
      'box-color':   { type: 'color', value: '#000', extra: { tick: "left", picker_horizontal_position: "left", picker_vertical_position: "down" }},
      'box-opacity': { type: 'simple_opacity', value: .7, min:0, max:1, inc: .1, disable_triggering: true },
      'box-padding': { type: 'simple_number_with_label', value: 10, min: 5, max: 200, inc: 1, label: "P", disable_triggering: true }
    }
  }, {
    name: 'Max Width',
    form: {
      'box-width':  { type: 'simple_number', value: 300, min: 50, max: 2000, inc: 10, disable_triggering: true },
    }
  }],

  initialize: function() {

    _.bindAll(this, "_close", "_onChangeMode", "_onKeyDown");

    this.template = this.getTemplate(this.template_name);

    this._setupModels();

  },

  // Setup the internal and custom model
  _setupModels: function() {

    var self  = this;
    var extra = this.model.get("extra");

    this.model.set({ text: extra.text }, { silent: true });

    var applyStyle = function() {
      self._applyStyle(true);
    };

    // Binding
    this.model.bind('remove',   this.hide, this);

    this.model.bind('change:style',   applyStyle,            this);
    this.model.bind('change:text',    this._setText,         this);
    this.model.bind('change:display', this._onChangeDisplay, this);
    this.model.bind('change:extra',   this._onChangeExtra,   this);
    this.model.bind('change:selected', this._onChangeSelected, this);

    // Internal model to store the editing state
    this.editModel = new cdb.core.Model({ mode: "" });
    this.editModel.bind('change:mode', this._onChangeMode, this);

    this.add_related_model(this.editModel);

  },

  // Element events
  _onKeyUp: function(e) {

    if (this.timeout) {
      clearTimeout(this.timeout);
    }

    this.model.set({ text: this.$text.html() }, { silent: true });

  },

  _onClickEdit: function(e) {

    this.killEvent(e);

    cdb.god.trigger("closeOverlayDropdown");

    $(document).bind('keydown', this._onKeyDown);

    this._savePosition(false);

    this.trigger("clickEdit", this.model, this.form_data);

    this.model.set("selected", true);

  },

  _onKeyDown: function(e) {

    var selected = this.model.get("selected");

    if (selected) {

      var editable = this.editModel.get("mode") == "editable";
      var focus    = this.$(".overlay_text").is(":focus");

      // hitting the backspace removes the overlay
      if (e.keyCode === 8 && (!editable || !focus)) {
        this.killEvent(e);
        this._close();
      }
    }

    if (e.keyCode === 27) { // esc key
      this.editModel.set("mode", "");
    }

  },

  _onPaste: function(e) {

    var self = this;

    setTimeout(function() {

      var text = cdb.Utils.stripHTML(self.model.get("text"));
      self.model.set("text", text)

    }, 200);

  },

  _onDblClick: function(e) {

    this.killEvent(e);
    this._savePosition(true);

  },

  _savePosition: function(editable) {

    var extra = this.model.get("extra");

    var x     = this.model.get("x");
    var y     = this.model.get("y");

    var oldX  = this.$el.position().left;
    var oldY  = this.$el.position().top;

    var portrait_direction  = extra.portrait_dominant_side;
    var landscape_direction = extra.landscape_dominant_side;

    if (y === 0 && portrait_dominant_side === "bottom") oldY = y;
    if (x === 0 && landscape_dominant_side === "right") oldX = x;

    var moved = true;
      // If we didn't move the overlay
   if (oldX === x && y === oldY || x === 0 && landscape_dominant_side === "right" && y === oldY || y == 0 && portrait_dominant_side === "bottom" && oldX === x) {

      moved = false;

      this.$el.addClass("selected");

      if (editable) {
        this.editModel.set("mode", "editable");
        return;
      }

    }

    x = this.$el.position().left;
    y = this.$el.position().top;

    var width  = this.$el.width();
    var height = this.$el.height();

    var right  = $(".cartodb-map").width()  - x;
    var bottom = $(".cartodb-map").height() - y;

    var right_position          = right  - width;
    var bottom_position         = bottom - height;

    var map_width               = $(".cartodb-map").width();
    var map_height              = $(".cartodb-map").width();

    var left_percentage         = (x + (width/2))  / map_width  * 100;
    var top_percentage          = (y + (height/2)) / map_height * 100;

    var landscape_dominant_side = x <= right_position ? "left" : "right";
    var portrait_dominant_side  = y <= bottom_position ? "top" : "bottom";

    // Default positions
    extra.default_position        = false;
    extra.landscape_dominant_side = landscape_dominant_side;
    extra.portrait_dominant_side  = portrait_dominant_side;
    extra.top_percentage          = top_percentage;
    extra.left_percentage         = left_percentage;
    extra.right_position          = right_position;
    extra.bottom_position         = bottom_position;
    extra.right                   = right;
    extra.bottom                  = bottom;
    extra.width                   = width;
    extra.height                  = height;

    this.model.set({ extra: extra }, { silent: true});
    this.model.set({ x: x, y: y });

    if (moved) {
      this.model.save();
    }

  },

  _onMouseDown: function() { },

  _onMouseEnter: function() {

    this.$el.addClass("hover");

    if (this.editModel.get("mode") === "editable") {
      if (this.timeout) clearTimeout(this.timeout);
    }

  },

  show: function(animated) {

    this.$el.show();

    if (true) this.$el.addClass('animated bounceIn');

  },

  hide: function(callback) {

    var self = this;

    this.$el
    .removeClass('animated bounceIn')
    .addClass('animated bounceOut')
    .removeClass('selected');

    callback && _.isFunction(callback) && callback();

    $(document).unbind('keydown', this._onKeyDown);

    cdb.god.unbind("closeDialogs", this._onCloseDialogs, this);

    // Give it some time to complete the animation
    setTimeout(function() {
      self.clean();
    }, 550);

  },

  _close: function(e) {

    this.killEvent(e);

    var self = this;

    this.hide(function() {
      self.trigger("remove", self);
    });

  },

  _onChangeDisplay: function() {

    var display = this.model.get("display");

    if (display) {
      this.show();
    } else {
      this.$el.hide();
    }

  },

  _onChangeExtra: function() {

    var extra  = this.model.get("extra");
    extra.text = this.model.get("text");

    this.model.set({ extra: extra }, { silent: true });

  },

  /*
   * Applies style to the content of the widget
   */

  _applyStyle: function(save) {

    var style      = this.model.get("style");

    var boxColor   = style["box-color"];
    var boxOpacity = style["box-opacity"];
    var boxWidth   = style["box-width"];
    var boxPadding = style["box-padding"];

    var fontFamily = style["font-family-name"];

    this.$text.css(style);

    this.$(".content").css("padding", boxPadding);
    this.$text.css("font-size", style["font-size"] + "px");
    this.$el.css("z-index", style["z-index"]);

    var rgbaCol = 'rgba(' + parseInt(boxColor.slice(-6,-4),16)
    + ',' + parseInt(boxColor.slice(-4,-2),16)
    + ',' + parseInt(boxColor.slice(-2),16)
    +', ' + boxOpacity + ' )';

    this.$el.css("background-color", rgbaCol);
    this.$el.css("max-width", boxWidth);

    var fontFamilyClass = "";

    if      (fontFamily  == "Droid Sans")       fontFamilyClass = "droid";
    else if (fontFamily  == "Vollkorn")         fontFamilyClass = "vollkorn";
    else if (fontFamily  == "Open Sans")        fontFamilyClass = "open_sans";
    else if (fontFamily  == "Roboto")           fontFamilyClass = "roboto";
    else if (fontFamily  == "Lato")             fontFamilyClass = "lato";
    else if (fontFamily  == "Graduate")         fontFamilyClass = "graduate";
    else if (fontFamily  == "Gravitas One")     fontFamilyClass = "gravitas_one";
    else if (fontFamily  == "Old Standard TT")  fontFamilyClass = "old_standard_tt";
    this.$el.css("width", "auto");

    this.$el
    .removeClass("droid")
    .removeClass("vollkorn")
    .removeClass("roboto")
    .removeClass("open_sans")
    .removeClass("lato")
    .removeClass("graduate")
    .removeClass("gravitas_one")
    .removeClass("old_standard_tt");

    this.$el.addClass(fontFamilyClass);

    if (save) this.model.save();

  },

  _onChangeSelected: function() {

    var selected = this.model.get("selected");

    if (selected) {

      this.$el.addClass("selected");

    } else {

      this.$el.removeClass("selected");
      this._disableEditingMode();

    }

  },

  _onChangeMode: function() {

    var mode = this.editModel.get("mode");

    if (mode === "editable") {
      this._enableEditingMode();
    } else {
      this._disableEditingMode();
    }

  },

  _enableEditingMode: function() {

    this.$el
    .addClass("editable")
    .addClass("disabled");

    this.$text.attr("contenteditable", true).focus();

    var style = this.model.get("style");
    var width = style["box-width"];

    var text = this.model.get("text");

    this.$el.css("width", "auto");
    this.$el.css("max-width", width);
    this.$text.html(text);
    this.$(".hint").fadeIn(150);

  },

  _isEmptyText: function(text) {
    var regexp = new RegExp(/^(<div>)*(<br ?\/?>)*(<\/div>)*$/);

    if (text.trim() && !text.trim().match(regexp)) {
      return false;
    } else {
      return true;
    }
  },

  _disableEditingMode: function() {

    $(document).unbind('keydown', this._onKeyDown);

    var text = this._transformToMarkdown(this.model.get("text"));

    this.editModel.set("mode", "");

    if (!this._isEmptyText(text)) {

      var self = this;

      self.$(".hint").fadeOut(150, function() {

        self.$el
        .removeClass("editable")
        .removeClass("disabled");

        self.$text.attr("contenteditable", false);
        self.$el.css("width", "auto");

        setTimeout(function() {

          var width = self.$el.width();
          var extra = self.model.get("extra");

          extra.width = width;

          self.model.set({ extra: extra }, { silent: true });

          if (!self.model.isNew()) {
            self.model.save();
          }

          self.$text.html(text);

          self.$el.css("width", "auto");

        }, 100);

        if (!self.model.isNew()) {
          self.model.save();
        }

      });

    } else {
      this._close();
    }

  },

  _setText: function() {

    var text          = this.model.get("text");
    var rendered_text = this._transformToMarkdown(text);

    var extra = this.model.get("extra");

    extra.text          = text;
    extra.rendered_text = rendered_text

    this.model.set({ extra: extra }, { silent: true });

    if (rendered_text) {
      this.$text.html(rendered_text);
    }

  },

  _transformToMarkdown: function(text) {

    text = markdown.toHTML(text)

    text = text.replace(/</g, "<");
    text = text.replace(/>/g, ">");
    text = text.replace(/<p>/g, "");
    text = text.replace(/&nbsp;/g, " ");
    text = text.replace(/<\/p>/g, "");

    return text;

  },

  _place: function() {

    var mode  = this.editModel.get("mode");
    var extra = this.model.get("extra");

    if (mode === "editable" || !extra) {
      return;
    }

    var landscape_dominant_side = extra.landscape_dominant_side;
    var portrait_dominant_side  = extra.portrait_dominant_side;

    if (portrait_dominant_side === 'bottom') {

      this.$el.offset({
        bottom: extra.bottom_position
      });

      this.$el.css({
        top: "auto",
        bottom: extra.bottom_position
      });

    } else {

      this.$el.offset({
        top: this.model.get("y"),
        bottom: "auto"
      });

    }

    if (landscape_dominant_side === 'right') {

      this.$el.offset({
        right: extra.right_position
      });

      this.$el.css({
        left: "auto",
        right: extra.right_position
      });

    } else {

      this.$el.offset({
        left: this.model.get("x"),
        right: "auto"
      });

    }

  },

  _onCloseDialogs: function() {
    if (this.model.get("selected") !== undefined) this.model.set("selected", false);
  },

  render: function() {

    this._place();

    this.$el.append(this.template(this.model.attributes));

    this.$text = this.$(".content div.text");
    var text   = this._transformToMarkdown(this.model.get("text"));

    this.$text.html(text);

    this._applyStyle(false);
    this._onChangeExtra();

    this.$el.addClass(this.model.get("device"));

    cdb.god.unbind("closeDialogs", this._onCloseDialogs, this);
    cdb.god.bind("closeDialogs", this._onCloseDialogs, this);

    return this;

  }

});
<commit_msg>Uses $.ui.keyCode<commit_after>cdb.admin.overlays.Text = cdb.geo.ui.Text.extend({

  className: "text overlay snap",

  template_name: 'table/views/overlays/text',

  events: {

    "mouseenter .text":  "_onMouseEnter",

    "click .close":      "_close",
    "click .content":    "_onClickEdit",
    "click .text":       "_onClickEdit",
    "dblclick .content": "_onDblClick",
    "dblclick .text":    "_onDblClick",

    "keyup .text":       "_onKeyUp",
    "paste .text":       "_onPaste"

  },

  form_data: [{
    name: 'Text',
    form: {
      'font-size':  { type: 'simple_number', value: 12, min: 5, max: 50, inc: 2, disable_triggering: true },
      'color':      { type: 'color', value: '#FFF', extra: { tick: "left", picker_horizontal_position: "left", picker_vertical_position: "down" }},
      'font-family-name': {
        type: 'select',
        value: "Helvetica",
        extra: ["Helvetica", "Droid Sans", "Vollkorn", "Roboto", "Open Sans", "Lato", "Graduate", "Gravitas One", "Old Standard TT"]

      },
      'text-align':      { type: 'text_align', value: 'left' }
    }
  }, {
    name: 'Box',
    form: {
      'box-color':   { type: 'color', value: '#000', extra: { tick: "left", picker_horizontal_position: "left", picker_vertical_position: "down" }},
      'box-opacity': { type: 'simple_opacity', value: .7, min:0, max:1, inc: .1, disable_triggering: true },
      'box-padding': { type: 'simple_number_with_label', value: 10, min: 5, max: 200, inc: 1, label: "P", disable_triggering: true }
    }
  }, {
    name: 'Max Width',
    form: {
      'box-width':  { type: 'simple_number', value: 300, min: 50, max: 2000, inc: 10, disable_triggering: true },
    }
  }],

  initialize: function() {

    _.bindAll(this, "_close", "_onChangeMode", "_onKeyDown");

    this.template = this.getTemplate(this.template_name);

    this._setupModels();

  },

  // Setup the internal and custom model
  _setupModels: function() {

    var self  = this;
    var extra = this.model.get("extra");

    this.model.set({ text: extra.text }, { silent: true });

    var applyStyle = function() {
      self._applyStyle(true);
    };

    // Binding
    this.model.bind('remove',   this.hide, this);

    this.model.bind('change:style',   applyStyle,            this);
    this.model.bind('change:text',    this._setText,         this);
    this.model.bind('change:display', this._onChangeDisplay, this);
    this.model.bind('change:extra',   this._onChangeExtra,   this);
    this.model.bind('change:selected', this._onChangeSelected, this);

    // Internal model to store the editing state
    this.editModel = new cdb.core.Model({ mode: "" });
    this.editModel.bind('change:mode', this._onChangeMode, this);

    this.add_related_model(this.editModel);

  },

  // Element events
  _onKeyUp: function(e) {

    if (this.timeout) {
      clearTimeout(this.timeout);
    }

    this.model.set({ text: this.$text.html() }, { silent: true });

  },

  _onClickEdit: function(e) {

    this.killEvent(e);

    cdb.god.trigger("closeOverlayDropdown");

    $(document).bind('keydown', this._onKeyDown);

    this._savePosition(false);

    this.trigger("clickEdit", this.model, this.form_data);

    this.model.set("selected", true);

  },

  _onKeyDown: function(e) {

    var selected = this.model.get("selected");

    if (selected) {
      var editable = this.editModel.get("mode") == "editable";
      var focus    = this.$(".overlay_text").is(":focus");

      // hitting the backspace removes the overlay
      if (e.keyCode === $.ui.keyCode.BACKSPACE && (!editable || !focus)) {
        this.killEvent(e);
        this._close();
      }
    }

    if (e.keyCode === $.ui.keyCode.ESCAPE) {
      this.editModel.set("mode", "");
    }

  },

  _onPaste: function(e) {

    var self = this;

    setTimeout(function() {

      var text = cdb.Utils.stripHTML(self.model.get("text"));
      self.model.set("text", text)

    }, 200);

  },

  _onDblClick: function(e) {

    this.killEvent(e);
    this._savePosition(true);

  },

  _savePosition: function(editable) {

    var extra = this.model.get("extra");

    var x     = this.model.get("x");
    var y     = this.model.get("y");

    var oldX  = this.$el.position().left;
    var oldY  = this.$el.position().top;

    var portrait_direction  = extra.portrait_dominant_side;
    var landscape_direction = extra.landscape_dominant_side;

    if (y === 0 && portrait_dominant_side === "bottom") oldY = y;
    if (x === 0 && landscape_dominant_side === "right") oldX = x;

    var moved = true;
      // If we didn't move the overlay
   if (oldX === x && y === oldY || x === 0 && landscape_dominant_side === "right" && y === oldY || y == 0 && portrait_dominant_side === "bottom" && oldX === x) {

      moved = false;

      this.$el.addClass("selected");

      if (editable) {
        this.editModel.set("mode", "editable");
        return;
      }

    }

    x = this.$el.position().left;
    y = this.$el.position().top;

    var width  = this.$el.width();
    var height = this.$el.height();

    var right  = $(".cartodb-map").width()  - x;
    var bottom = $(".cartodb-map").height() - y;

    var right_position          = right  - width;
    var bottom_position         = bottom - height;

    var map_width               = $(".cartodb-map").width();
    var map_height              = $(".cartodb-map").width();

    var left_percentage         = (x + (width/2))  / map_width  * 100;
    var top_percentage          = (y + (height/2)) / map_height * 100;

    var landscape_dominant_side = x <= right_position ? "left" : "right";
    var portrait_dominant_side  = y <= bottom_position ? "top" : "bottom";

    // Default positions
    extra.default_position        = false;
    extra.landscape_dominant_side = landscape_dominant_side;
    extra.portrait_dominant_side  = portrait_dominant_side;
    extra.top_percentage          = top_percentage;
    extra.left_percentage         = left_percentage;
    extra.right_position          = right_position;
    extra.bottom_position         = bottom_position;
    extra.right                   = right;
    extra.bottom                  = bottom;
    extra.width                   = width;
    extra.height                  = height;

    this.model.set({ extra: extra }, { silent: true});
    this.model.set({ x: x, y: y });

    if (moved) {
      this.model.save();
    }

  },

  _onMouseDown: function() { },

  _onMouseEnter: function() {

    this.$el.addClass("hover");

    if (this.editModel.get("mode") === "editable") {
      if (this.timeout) clearTimeout(this.timeout);
    }

  },

  show: function(animated) {

    this.$el.show();

    if (true) this.$el.addClass('animated bounceIn');

  },

  hide: function(callback) {

    var self = this;

    this.$el
    .removeClass('animated bounceIn')
    .addClass('animated bounceOut')
    .removeClass('selected');

    callback && _.isFunction(callback) && callback();

    $(document).unbind('keydown', this._onKeyDown);

    cdb.god.unbind("closeDialogs", this._onCloseDialogs, this);

    // Give it some time to complete the animation
    setTimeout(function() {
      self.clean();
    }, 550);

  },

  _close: function(e) {

    this.killEvent(e);

    var self = this;

    this.hide(function() {
      self.trigger("remove", self);
    });

  },

  _onChangeDisplay: function() {

    var display = this.model.get("display");

    if (display) {
      this.show();
    } else {
      this.$el.hide();
    }

  },

  _onChangeExtra: function() {

    var extra  = this.model.get("extra");
    extra.text = this.model.get("text");

    this.model.set({ extra: extra }, { silent: true });

  },

  /*
   * Applies style to the content of the widget
   */

  _applyStyle: function(save) {

    var style      = this.model.get("style");

    var boxColor   = style["box-color"];
    var boxOpacity = style["box-opacity"];
    var boxWidth   = style["box-width"];
    var boxPadding = style["box-padding"];

    var fontFamily = style["font-family-name"];

    this.$text.css(style);

    this.$(".content").css("padding", boxPadding);
    this.$text.css("font-size", style["font-size"] + "px");
    this.$el.css("z-index", style["z-index"]);

    var rgbaCol = 'rgba(' + parseInt(boxColor.slice(-6,-4),16)
    + ',' + parseInt(boxColor.slice(-4,-2),16)
    + ',' + parseInt(boxColor.slice(-2),16)
    +', ' + boxOpacity + ' )';

    this.$el.css("background-color", rgbaCol);
    this.$el.css("max-width", boxWidth);

    var fontFamilyClass = "";

    if      (fontFamily  == "Droid Sans")       fontFamilyClass = "droid";
    else if (fontFamily  == "Vollkorn")         fontFamilyClass = "vollkorn";
    else if (fontFamily  == "Open Sans")        fontFamilyClass = "open_sans";
    else if (fontFamily  == "Roboto")           fontFamilyClass = "roboto";
    else if (fontFamily  == "Lato")             fontFamilyClass = "lato";
    else if (fontFamily  == "Graduate")         fontFamilyClass = "graduate";
    else if (fontFamily  == "Gravitas One")     fontFamilyClass = "gravitas_one";
    else if (fontFamily  == "Old Standard TT")  fontFamilyClass = "old_standard_tt";
    this.$el.css("width", "auto");

    this.$el
    .removeClass("droid")
    .removeClass("vollkorn")
    .removeClass("roboto")
    .removeClass("open_sans")
    .removeClass("lato")
    .removeClass("graduate")
    .removeClass("gravitas_one")
    .removeClass("old_standard_tt");

    this.$el.addClass(fontFamilyClass);

    if (save) this.model.save();

  },

  _onChangeSelected: function() {

    var selected = this.model.get("selected");

    if (selected) {

      this.$el.addClass("selected");

    } else {

      this.$el.removeClass("selected");
      this._disableEditingMode();

    }

  },

  _onChangeMode: function() {

    var mode = this.editModel.get("mode");

    if (mode === "editable") {
      this._enableEditingMode();
    } else {
      this._disableEditingMode();
    }

  },

  _enableEditingMode: function() {

    this.$el
    .addClass("editable")
    .addClass("disabled");

    this.$text.attr("contenteditable", true).focus();

    var style = this.model.get("style");
    var width = style["box-width"];

    var text = this.model.get("text");

    this.$el.css("width", "auto");
    this.$el.css("max-width", width);
    this.$text.html(text);
    this.$(".hint").fadeIn(150);

  },

  _isEmptyText: function(text) {
    var regexp = new RegExp(/^(<div>)*(<br ?\/?>)*(<\/div>)*$/);

    if (text.trim() && !text.trim().match(regexp)) {
      return false;
    } else {
      return true;
    }
  },

  _disableEditingMode: function() {

    $(document).unbind('keydown', this._onKeyDown);

    var text = this._transformToMarkdown(this.model.get("text"));

    this.editModel.set("mode", "");

    if (!this._isEmptyText(text)) {

      var self = this;

      self.$(".hint").fadeOut(150, function() {

        self.$el
        .removeClass("editable")
        .removeClass("disabled");

        self.$text.attr("contenteditable", false);
        self.$el.css("width", "auto");

        setTimeout(function() {

          var width = self.$el.width();
          var extra = self.model.get("extra");

          extra.width = width;

          self.model.set({ extra: extra }, { silent: true });

          if (!self.model.isNew()) {
            self.model.save();
          }

          self.$text.html(text);

          self.$el.css("width", "auto");

        }, 100);

        if (!self.model.isNew()) {
          self.model.save();
        }

      });

    } else {
      this._close();
    }

  },

  _setText: function() {

    var text          = this.model.get("text");
    var rendered_text = this._transformToMarkdown(text);

    var extra = this.model.get("extra");

    extra.text          = text;
    extra.rendered_text = rendered_text

    this.model.set({ extra: extra }, { silent: true });

    if (rendered_text) {
      this.$text.html(rendered_text);
    }

  },

  _transformToMarkdown: function(text) {

    text = markdown.toHTML(text)

    text = text.replace(/</g, "<");
    text = text.replace(/>/g, ">");
    text = text.replace(/<p>/g, "");
    text = text.replace(/&nbsp;/g, " ");
    text = text.replace(/<\/p>/g, "");

    return text;

  },

  _place: function() {

    var mode  = this.editModel.get("mode");
    var extra = this.model.get("extra");

    if (mode === "editable" || !extra) {
      return;
    }

    var landscape_dominant_side = extra.landscape_dominant_side;
    var portrait_dominant_side  = extra.portrait_dominant_side;

    if (portrait_dominant_side === 'bottom') {

      this.$el.offset({
        bottom: extra.bottom_position
      });

      this.$el.css({
        top: "auto",
        bottom: extra.bottom_position
      });

    } else {

      this.$el.offset({
        top: this.model.get("y"),
        bottom: "auto"
      });

    }

    if (landscape_dominant_side === 'right') {

      this.$el.offset({
        right: extra.right_position
      });

      this.$el.css({
        left: "auto",
        right: extra.right_position
      });

    } else {

      this.$el.offset({
        left: this.model.get("x"),
        right: "auto"
      });

    }

  },

  _onCloseDialogs: function() {
    if (this.model.get("selected") !== undefined) this.model.set("selected", false);
  },

  render: function() {

    this._place();

    this.$el.append(this.template(this.model.attributes));

    this.$text = this.$(".content div.text");
    var text   = this._transformToMarkdown(this.model.get("text"));

    this.$text.html(text);

    this._applyStyle(false);
    this._onChangeExtra();

    this.$el.addClass(this.model.get("device"));

    cdb.god.unbind("closeDialogs", this._onCloseDialogs, this);
    cdb.god.bind("closeDialogs", this._onCloseDialogs, this);

    return this;

  }

});
<|endoftext|>


<commit_before>'use strict';

var util = require('util'),
  XModule = require('../../../core/XModule'),
  ltx = require('ltx'),
  JID = require('node-xmpp-core').JID,
  NS = require('../namespace'),
  Iq = require('node-xmpp-core').Stanza.Iq,
  pubutil = require('../Util'),
  logger = require('../../../core/Logger')('xep-0060:subscription');

var Subscription = function (storage, options) {
  XModule.call(this);

  // storage
  this.storage = storage;

  // pubsub options
  this.options = options;
};

util.inherits(Subscription, XModule);

Subscription.prototype.name = 'XEP-0060: Subscription';

Subscription.prototype.match = function (stanza) {
  // owner requests
  return (
    stanza.is('iq') &&
    stanza.getChild('pubsub', NS.PUBSUB) &&
    stanza.getChild('pubsub', NS.PUBSUB).getChild('subscribe')
  ) ||
  // configure requests
  (
    stanza.is('iq') &&
    stanza.getChild('pubsub', NS.PUBSUB) &&
    stanza.getChild('pubsub', NS.PUBSUB).getChild('unsubscribe')
  );
};

/**
 * @description sends the history to the given jid
 * @param node pub sub node
 * @param jid reciever of the histroy events
 *
 * TODO make dependend on node default
 * @see http://xmpp.org/extensions/xep-0060.html#subscriber-subscribe-last
 */
Subscription.prototype.sendHistory = function (node, jid) {
  logger.debug('send history');
  var self = this;
  node.getEvents().then(function (events) {
    // logger.debug(JSON.stringify(events));
    if (events && events.length && events.length > 0) {

      events.forEach(function (evt) {
        // logger.debug(JSON.stringify(evt));
        try {
          // extract message
          var el = ltx.parse(evt.content);

          // send to  jid
          el.attrs.to = jid;

          // route message
          self.send(el, null);

        } catch (err) {
          logger.error(err);
        }
      });
    }
  }).catch(function (err) {
    logger.error(err);
  });
};

/**
 * @description subscribes a new jid
 * @param stanza full pubsub message stanza
 * @param pubsub already extracted pubsub child node
 * @see http://xmpp.org/extensions/xep-0060.html#subscriber-subscribe
 */
Subscription.prototype.handleSubscribe = function (user, node, stanza, subcribeEl) {
  logger.debug('handleSubscribe');
  var self = this;

  var fromJid = new JID(stanza.attrs.from);
  var subscriberJid = new JID(subcribeEl.attrs.jid);

  // check that jids match
  if (!fromJid.bare().equals(subscriberJid.bare())) {
    logger.error('subscriptions jids do not match : ' + fromJid.toString() + ' != ' + subscriberJid.toString());
    // this is a wrong stanza
    this.sendError(stanza, pubutil.Error.BadRequest);
    return;
  }

  logger.debug('Subscribe ' + subscriberJid.toString() + ' to ' + node.name);

  // store new subscriber
  node.subscribe(user, {
    affiliation: self.storage.ChannelSub.Affiliation.Member,
    substate: self.storage.ChannelSub.SubState.Subscribed
  }).then(function () {
    // Success Case, send confirmation
    var msg = new Iq({
      from: self.options.domain,
      to: stanza.attrs.from,
      id: stanza.attrs.id,
      type: 'result'
    });

    //var humanname = node.getConfiguration(NodeConfig.PUBSUB_NODE_Title);

    msg.c('pubsub', {
      'xmlns': NS.PUBSUB
    }).c('subscription', {
      'node': subcribeEl.attrs.node,
      'jid': subcribeEl.attrs.jid,
      'subscription': 'subscribed'
    });

    // send subscribe response
    self.send(msg);

    // send old itemes to new subscriber
    // TODO: only send history if persistence is activated
    self.sendHistory(node, subcribeEl.attrs.jid);

  }).
  catch(
    function (err) {
      // error
      logger.error(err);
      self.sendError(stanza, pubutil.Error.BadRequest);
    }
  );
};

/**
 * @description closes a subscription
 * @param stanza full pubsub message stanza
 * @param pubsub already extracted pubsub child node
 * @see http://xmpp.org/extensions/xep-0060.html#subscriber-unsubscribe
 */
Subscription.prototype.handleUnsubscribe = function (user, node, stanza, unsubscribeEl) {
  logger.debug('handleUnsubscribe');
  var self = this;

  var fromJid = new JID(stanza.attrs.from);
  var subscriberJid = new JID(unsubscribeEl.attrs.jid);

  // check that jids match
  if (!fromJid.bare().equals(subscriberJid)) {
    // this is a wrong stanza
    this.sendError(stanza, pubutil.Error.BadRequest);
    return;
  }

  // unregister subscriber
  logger.debug('user ' + fromJid.bare().toString() + ' unsubscribes the node ' + node.name);
  node.unsubscribe(user).then(
    function () {
      self.sendSuccess(stanza);
    }).
  catch(
    function (err) {
      logger.warn(err);
      self.sendError(stanza, pubutil.Error.NotSubscribed);
    });
};

Subscription.prototype.handle = function (stanza) {
  var pubsub = stanza.getChild('pubsub', NS.PUBSUB);

  // detect method
  var method, pubsubEl = null;
  if (pubsub.getChild('subscribe')) {
    method = 'subscribe';
    pubsubEl = pubsub.getChild('subscribe');
  } else if (pubsub.getChild('unsubscribe')) {
    method = 'unsubscribe';
    pubsubEl = pubsub.getChild('unsubscribe');
  }

  // process
  var self = this;
  var fromJid = new JID(stanza.attrs.from).bare().toString();
  this.storage.findOrCreateUser(fromJid).spread(function (user, created) { // jshint ignore:line
    // detect node name
    var nodename = pubsubEl.attrs.node;
    logger.debug('nodename: ' + nodename);

    // okay, we have the user
    switch (method) {
    case 'subscribe':
      // todo use options
      pubutil.findChannel(self.storage, nodename, user, self.options.autoCreateChannel).then(function (node) {
        logger.debug('subscribe: ' + JSON.stringify(node));
        if (node) {
          self.handleSubscribe(user, node, stanza, pubsubEl);
        } else {
          self.sendError(stanza, pubutil.Error.NotFound);
        }
      });

      break;
    case 'unsubscribe':
      pubutil.findChannel(self.storage, nodename, user, false).then(function (node) {
        logger.debug('unsubscribe: ' + JSON.stringify(node));
        if (node) {
          self.handleUnsubscribe(user, node, stanza, pubsubEl);
        } else {
          self.sendError(stanza, pubutil.Error.NotFound);
        }
      });
      break;
    default:
      self.sendError(stanza, pubutil.Error.NotFound);
    }

  });

  return true;
};

module.exports = Subscription;
<commit_msg>improvement: refactor subscription handling<commit_after>'use strict';

var util = require('util'),
  XModule = require('../../../core/XModule'),
  ltx = require('ltx'),
  JID = require('node-xmpp-core').JID,
  NS = require('../namespace'),
  Iq = require('node-xmpp-core').Stanza.Iq,
  pubutil = require('../Util'),
  logger = require('../../../core/Logger')('xep-0060:subscription');

var Subscription = function (storage, options) {
  XModule.call(this);

  // storage
  this.storage = storage;

  // pubsub options
  this.options = options;
};

util.inherits(Subscription, XModule);

Subscription.prototype.name = 'XEP-0060: Subscription';

Subscription.prototype.match = function (stanza) {
  // owner requests
  return (
    stanza.is('iq') &&
    stanza.getChild('pubsub', NS.PUBSUB) &&
    stanza.getChild('pubsub', NS.PUBSUB).getChild('subscribe')
  ) ||
  // configure requests
  (
    stanza.is('iq') &&
    stanza.getChild('pubsub', NS.PUBSUB) &&
    stanza.getChild('pubsub', NS.PUBSUB).getChild('unsubscribe')
  );
};

/**
 * @description sends the history to the given jid
 * @param node pub sub node
 * @param jid reciever of the histroy events
 *
 * TODO make dependend on node default
 * @see http://xmpp.org/extensions/xep-0060.html#subscriber-subscribe-last
 */
Subscription.prototype.sendHistory = function (node, jid) {
  logger.debug('send history');
  var self = this;
  node.getEvents().then(function (events) {
    // logger.debug(JSON.stringify(events));
    if (events && events.length && events.length > 0) {

      events.forEach(function (evt) {
        // logger.debug(JSON.stringify(evt));
        try {
          // extract message
          var el = ltx.parse(evt.content);

          // send to  jid
          el.attrs.to = jid;

          // route message
          self.send(el, null);

        } catch (err) {
          logger.error(err);
        }
      });
    }
  }).catch(function (err) {
    logger.error(err);
  });
};

/**
 * @description subscribes a new jid
 * @param stanza full pubsub message stanza
 * @param pubsub already extracted pubsub child node
 * @see http://xmpp.org/extensions/xep-0060.html#subscriber-subscribe
 */
Subscription.prototype.handleSubscribe = function (stanza, subcribeEl) {
  logger.debug('handleSubscribe');

  var self = this;
  var storage = this.storage;

  var fromJid = new JID(stanza.attrs.from).bare();
  var subscriberJid = new JID(subcribeEl.attrs.jid);

  // check that jids match
  if (!fromJid.equals(subscriberJid.bare())) {
    logger.error('subscriptions jids do not match : ' + fromJid.toString() + ' != ' + subscriberJid.toString());
    // this is a wrong stanza
    this.sendError(stanza, pubutil.Error.BadRequest);
    return;
  }

  // detect node name
  var nodename = subcribeEl.attrs.node;
  logger.debug('nodename: ' + nodename);

  var user, node = null;

  this.storage.findUser(fromJid.toString()).then(function (u) {
    user = u;
    return pubutil.findChannel(storage, nodename, user, self.options.autoCreateChannel)
  }).then(function (n) {
    node = n;

    if (!node) {
      throw new Error ('node-not-found');
    }

    logger.debug('Subscribe ' + subscriberJid.toString() + ' to ' + node.name);

    // store new subscriber
    return node.subscribe(user, {
      affiliation: self.storage.ChannelSub.Affiliation.Member,
      substate: self.storage.ChannelSub.SubState.Subscribed
    })
  }).then(function () {
    // Success Case, send confirmation
    var msg = new Iq({
      from: self.options.domain,
      to: stanza.attrs.from,
      id: stanza.attrs.id,
      type: 'result'
    });

    //var humanname = node.getConfiguration(NodeConfig.PUBSUB_NODE_Title);

    msg.c('pubsub', {
      'xmlns': NS.PUBSUB
    }).c('subscription', {
      'node': subcribeEl.attrs.node,
      'jid': subcribeEl.attrs.jid,
      'subscription': 'subscribed'
    });

    // send subscribe response
    self.send(msg);

    // send old itemes to new subscriber
    // TODO: only send history if persistence is activated
    self.sendHistory(node, subcribeEl.attrs.jid);

  }).catch(function(err){
    logger.error(err);
    if (err.message === 'node-not-found') {
      self.sendError(stanza, pubutil.Error.NotFound);
    } else {
      self.sendError(stanza, pubutil.Error.BadRequest);
    }
  });
};

/**
 * @description closes a subscription
 * @param stanza full pubsub message stanza
 * @param pubsub already extracted pubsub child node
 * @see http://xmpp.org/extensions/xep-0060.html#subscriber-unsubscribe
 */
Subscription.prototype.handleUnsubscribe = function (stanza, unsubscribeEl) {
  logger.debug('handleUnsubscribe');
  var self = this;
  var storage = this.storage;

  var fromJid = new JID(stanza.attrs.from).bare();
  var subscriberJid = new JID(unsubscribeEl.attrs.jid);

  // check that jids match
  if (!fromJid.equals(subscriberJid)) {
    // this is a wrong stanza
    this.sendError(stanza, pubutil.Error.BadRequest);
    return;
  }

  // detect node name
  var nodename = unsubscribeEl.attrs.node;
  logger.debug('nodename: ' + nodename);

  var user, node = null

  this.storage.findUser(fromJid.toString()).then(function (u) {
    user = u;
    return pubutil.findChannel(storage, nodename, user, false)
  }).then(function (n) {
    node = n;
    logger.debug('unsubscribe: ' + JSON.stringify(node));
    if (!node) {
      throw new Error ('node-not-found');
    }

    // check if sender is subsciber of channel
    return node.isSubscriber(user).catch(function(err){
      throw new Error('not-subscribed');
    })
  }).then(function () {
    // unregister subscriber
    logger.debug('user ' + fromJid.toString() + ' unsubscribes the node ' + node.name);
    return node.unsubscribe(user)
  }).then(function () {
    self.sendSuccess(stanza);
  }).catch(function (err) {
    logger.warn(err);

    if (err.message === 'node-not-found') {
      self.sendError(stanza, pubutil.Error.NotFound);
    } else if (err.message === 'not-subscribed') {
      self.sendError(stanza, pubutil.Error.NotSubscribed);
    }
    else {
      self.sendError(stanza, pubutil.Error.BadRequest);
    }

  });
};

Subscription.prototype.handle = function (stanza) {
  var pubsub = stanza.getChild('pubsub', NS.PUBSUB);

  // detect method
  var method, pubsubEl = null;
  if (pubsub.getChild('subscribe')) {
    method = 'subscribe';
    pubsubEl = pubsub.getChild('subscribe');
  } else if (pubsub.getChild('unsubscribe')) {
    method = 'unsubscribe';
    pubsubEl = pubsub.getChild('unsubscribe');
  }

  // okay, we have the user
  switch (method) {
  case 'subscribe':
    this.handleSubscribe(stanza, pubsubEl);
    break;
  case 'unsubscribe':
    this.handleUnsubscribe(stanza, pubsubEl);
    break;
  default:
    this.sendError(stanza, pubutil.Error.NotFound);
  }

  return true;
};

module.exports = Subscription;
<|endoftext|>


<commit_before>'use strict';

const helpers = require('../../helpers');

module.exports = function subscriptionStatus() {
  return (req, res, next) => {
    const text = req.rivescriptReplyText;

    if (helpers.isSubscriptionStatusStopMacro(text)) {
      return helpers.subscriptionStatusStop(req, res, 'less');
    }

    if (helpers.isSubscriptionStatusLessMacro(text)) {
      return helpers.subscriptionStatusLess(req, res, 'stop');
    }

    return next();
  };
};
<commit_msg>Cleanup<commit_after>'use strict';

const helpers = require('../../helpers');

module.exports = function subscriptionStatus() {
  return (req, res, next) => {
    const text = req.rivescriptReplyText;

    if (helpers.isSubscriptionStatusStopMacro(text)) {
      return helpers.subscriptionStatusStop(req, res);
    }

    if (helpers.isSubscriptionStatusLessMacro(text)) {
      return helpers.subscriptionStatusLess(req, res);
    }

    return next();
  };
};
<|endoftext|>


<commit_before>/* global moment */

import Ember from 'ember';
import TooltipEnabled from '../mixins/sl-tooltip-enabled';

/**
 * @module components
 * @class  sl-date-time
 */
export default Ember.Component.extend( TooltipEnabled, {

    // -------------------------------------------------------------------------
    // Dependencies

    // -------------------------------------------------------------------------
    // Attributes

    /**
     * The HTML tag type of the component's root element
     *
     * @property {Ember.String} tagName
     * @default  "time"
     */
    tagName: 'time',

    /**
     * Class names for the component's root element, <time>
     *
     * @property {Ember.Array} classNames
     */
    classNames: [ 'sl-datetime' ],

    // -------------------------------------------------------------------------
    // Actions

    // -------------------------------------------------------------------------
    // Events

    // -------------------------------------------------------------------------
    // Properties

    /**
     * Bindings for the date-time's attribute values
     *
     * @property {Ember.Array} attributeBindings
     */
    attributeBindings: [ 'datetime' ],

    /**
     * String name for the format to render inline; can be "date", "datetime",
     * or "relative"
     *
     * @property {Ember.String} format
     * @default  "datetime"
     */
    format: 'datetime',

    /**
     * String representing the full timezone name, as used by and interpreted by
     * Moment-timezone: http://momentjs.com/timezone/docs/#/using-timezones/
     *
     * @property {Ember.String} timezone
     * @default  null
     */
    timezone: null,

    /**
     * The bound value of the component's date value
     *
     * @property {date} value
     * @default  null
     */
    value: null,

    // -------------------------------------------------------------------------
    // Observers

    /**
     * Set up initial internal value
     *
     * @function initializeValue
     * @returns  {void}
     */
    initializeValue: function() {
        if ( !this.get( 'value' ) ) {
            this.set( 'value', new Date() );
        }
    }.on( 'didInsertElement' ),

    // -------------------------------------------------------------------------
    // Methods

    /**
     * The date-time's value formatted as a datetime string
     *
     * @function datetime
     * @observes timezoneString, value
     * @returns  {string}
     */
    datetime: function() {
        return moment( this.get( 'value' )).format( 'YYYY-MM-DD HH:mm ' ) + this.get( 'timezoneString' );
    }.property( 'timezoneString', 'value' ),

    /**
     * Formatted string based on value and supplied format
     *
     * @function formattedValue
     * @observes format, momentValue
     * @returns  {string}
     */
    formattedValue: function() {
        var momentValue     = this.get( 'momentValue' ),
            formattedString = '';

        switch ( this.get( 'format' ) ) {
            case 'date':
                formattedString = momentValue.format( 'YYYY-MM-DD' );
                break;

            case 'relative':
                formattedString = momentValue.fromNow();
                break;

            default:
            case 'datetime':
                formattedString = momentValue.format( 'dddd, MMMM Do YYYY, h:mm A' ) + ' ' + this.get( 'timezoneString' );
        }

        return formattedString;
    }.property( 'format', 'momentValue' ),

    /**
     * The component's current value wrapped in moment
     *
     * @function momentValue
     * @observes value
     * @returns  {object}
     */
    momentValue: function() {
        return moment( this.get( 'value' ) );
    }.property( 'value' ),

    /**
     * Formatted timezone string based on component's timezone value
     *
     * @function timezoneString
     * @observes timezone, momentValue
     * @returns  {string}
     */
    timezoneString: function() {
        return this.get( 'momentValue' ).tz( this.get( 'timezone' ) ).format( 'z' );
    }.property( 'timezone', 'momentValue' ),

    /**
     * The text to use for the component's tooltip
     *
     * @function title
     * @observes datetime
     * @returns  {string}
     */
    title: function() {
        return this.get( 'datetime' );
    }.property( 'datetime' )

});
<commit_msg>Make default `value` property inline<commit_after>/* global moment */

import Ember from 'ember';
import TooltipEnabled from '../mixins/sl-tooltip-enabled';

/**
 * @module components
 * @class  sl-date-time
 */
export default Ember.Component.extend( TooltipEnabled, {

    // -------------------------------------------------------------------------
    // Dependencies

    // -------------------------------------------------------------------------
    // Attributes

    /**
     * The HTML tag type of the component's root element
     *
     * @property {Ember.String} tagName
     * @default  "time"
     */
    tagName: 'time',

    /**
     * Class names for the component's root element, <time>
     *
     * @property {Ember.Array} classNames
     */
    classNames: [ 'sl-datetime' ],

    // -------------------------------------------------------------------------
    // Actions

    // -------------------------------------------------------------------------
    // Events

    // -------------------------------------------------------------------------
    // Properties

    /**
     * Bindings for the date-time's attribute values
     *
     * @property {Ember.Array} attributeBindings
     */
    attributeBindings: [ 'datetime' ],

    /**
     * String name for the format to render inline; can be "date", "datetime",
     * or "relative"
     *
     * @property {Ember.String} format
     * @default  "datetime"
     */
    format: 'datetime',

    /**
     * String representing the full timezone name, as used by and interpreted by
     * Moment-timezone: http://momentjs.com/timezone/docs/#/using-timezones/
     *
     * @property {Ember.String} timezone
     * @default  null
     */
    timezone: null,

    /**
     * The bound value of the component's date value
     *
     * @property {date} value
     * @default  (new Date)
     */
    value: new Date(),

    // -------------------------------------------------------------------------
    // Observers

    // -------------------------------------------------------------------------
    // Methods

    /**
     * The date-time's value formatted as a datetime string
     *
     * @function datetime
     * @observes timezoneString, value
     * @returns  {string}
     */
    datetime: function() {
        return moment( this.get( 'value' )).format( 'YYYY-MM-DD HH:mm ' ) + this.get( 'timezoneString' );
    }.property( 'timezoneString', 'value' ),

    /**
     * Formatted string based on value and supplied format
     *
     * @function formattedValue
     * @observes format, momentValue
     * @returns  {string}
     */
    formattedValue: function() {
        var momentValue     = this.get( 'momentValue' ),
            formattedString = '';

        switch ( this.get( 'format' ) ) {
            case 'date':
                formattedString = momentValue.format( 'YYYY-MM-DD' );
                break;

            case 'relative':
                formattedString = momentValue.fromNow();
                break;

            default:
            case 'datetime':
                formattedString = momentValue.format( 'dddd, MMMM Do YYYY, h:mm A' ) + ' ' + this.get( 'timezoneString' );
        }

        return formattedString;
    }.property( 'format', 'momentValue' ),

    /**
     * The component's current value wrapped in moment
     *
     * @function momentValue
     * @observes value
     * @returns  {object}
     */
    momentValue: function() {
        return moment( this.get( 'value' ) );
    }.property( 'value' ),

    /**
     * Formatted timezone string based on component's timezone value
     *
     * @function timezoneString
     * @observes timezone, momentValue
     * @returns  {string}
     */
    timezoneString: function() {
        return this.get( 'momentValue' ).tz( this.get( 'timezone' ) ).format( 'z' );
    }.property( 'timezone', 'momentValue' ),

    /**
     * The text to use for the component's tooltip
     *
     * @function title
     * @observes datetime
     * @returns  {string}
     */
    title: function() {
        return this.get( 'datetime' );
    }.property( 'datetime' )

});
<|endoftext|>