<commit_before>function parseComments(jsonString) {
  var container = $('#comments');

  try {
    var comments = JSON.parse(jsonString);
  }
  catch(error) {
    container.html('
' + error + '
');
    return;
  }

  container.html('');

  document.body.scrollTop = 0;

  for (var i = 0; i < comments.length; i++) {
    var comment = comments[i];
    var element = $('
' +
                      '
' +
                        '' +
                          comment.user +
                          '-' +
                        '' +
                        '' +
                          comment.created +
                        '' +
                      '
' +
                      comment.content +
                    '
');

    container.append(element);
  }
}

function didClickMinimizer(event) {
  var button = $(event.target);
  var commentContainer = button.parents('.commentContainer');

  if (button.html() === '-') {
    commentContainer.find('.meta').addClass('hidden');
    commentContainer.find('.comment').hide();
    minimizeNextComments(commentContainer, null);

    button.html('+');
  }
  else if (button.html() === '+') {
    commentContainer.find('.meta').removeClass('hidden');
    commentContainer.find('.comment').show();
    maximizeNextComments(commentContainer, null);

    button.html('-');
  }
}

function minimizeNextComments(startComment, initialMargin) {
  var startMargin = +startComment.attr('class').split('-')[1];

  if (initialMargin == null)
    initialMargin = startMargin;

  var nextComment = startComment.next();

  if (!nextComment.length)
    return;

  var nextMargin = +nextComment.attr('class').split('-')[1];

  if (nextMargin > initialMargin) {
    nextComment.hide();
    minimizeNextComments(nextComment, initialMargin);
  }
}

function maximizeNextComments(startComment, initialMargin) {
  var startMargin = +startComment.attr('class').split('-')[1];

  if (initialMargin == null)
    initialMargin = startMargin;

  var nextComment = startComment.next();

  if (!nextComment.length)
    return;

  var nextMargin  = +nextComment.attr('class').split('-')[1];

  if (nextMargin > initialMargin) {
    nextComment.show();
    maximizeNextComments(nextComment, initialMargin);
  }
}

$(document.body).ready(function() {

  var test = 0;
                       false && parseComments('[{"id":"5337179","content":"And with this one of the huge flaws of OAuth comes to play. OAuth just doesn\'t work with locally installed applications as it\'s impossible to hide anything there, but OAuth strongly relies on the client having some secret knowledge (the client token).
As long as all clients are equal when using the API, this might go well (minus some malicious clients), but once some clients start to be more equal than others - even more so as the service starts to get to be real jerks - then the whole system will fall down.<\\/p>
What we see here is twitter\'s secrets leaking out (though remember: That\'s more or less public data as it\'s technically impossible<\\/i> to hide that info - the server has to know) due to them being jerks giving their client preferential access.<\\/p>
What does this mean? For now, probably not much as I can imagine the bigger third-party-clients want to behave.<\\/p>
It might however make Twitter reconsider their policies.<\\/p>
If not, this is the beginning of a long cat and mouse game of twitter updating their keys and using heuristics to recognize their own client followed by twitter clients providing a way to change the client secret[1].<\\/p>
Though one thing is clear: Twitter will lose this game as the client secret has to be presented to the server.<\\/p>
Using SSL and certificate pinning, they can protect the secret from network monitors, but then the secret can still be extracted from the client, at which point, they might encrypt it in the client, at which point the attackers will disassemble the client to still extract the key.<\\/p>
It remains to be seen how far twitter is willing to go playing that game.<\\/p>
[1] even if the keys don\'t leak out, as long as twitter allows their users to create API clients, an editable client secret is a way for any<\\/i> twitter client to remain fully usable<\\/p><\\/font><\\/span>","created":"2 hours ago","margin":"0","user":"pilif"},{"id":"5337186","content":"It\'s not really fair to call this an OAuth flaw, since it\'s just another instance of the \'trusted client\' problem in security.
Essentially it\'s impossible to differentiate Twitter\'s own client on an untrusted platform since it will always be possible for \'malicious\' client to behave exactly like Twitter\'s own client.<\\/p><\\/font><\\/span>","created":"2 hours ago","margin":"40","user":"mtrimpe"},{"id":"5337531","content":"IMHO OAuth doesn\'t work for desktop applications because all the aspects where it tries to provide more security than traditional username\\/password authentication are easily circumvented on the desktop.
As such OAuth on the desktop IMHO is not much more than snake oil and does nothing aside of increasing the complexity for the implementer while providing next to zero additional security.<\\/p>
First is client authentication: OAuth tries to authenticate clients as well as users. On a desktop it\'s not possible to authenticate clients because whatever they do, the information can be extracted and simulated by a malicious client.<\\/p>
The other thing is that by now many desktop OAuth clients embed a webview for the authentication handshake. There is no way for the user to be sure that they are typing their credentials into the site they think they are. There is no browser chrome, there is usually no URL bar and even if there was, there is zero trust that the URL is actually showing the correct URL.<\\/p>
Worse: How many client apps are actually going through the trouble of checking SSL certificates (or even that SSL is on)?<\\/p>
Embedding a webview for an OAuth handshake provides (to the user) no additional security compared to just showing a username\\/password dialog.<\\/p>
The only way how I see this actually work is if the application opens the default browser for the handshake. But of course that will show a big-ass security warning when redirecting back to the local url protocol.<\\/p>
In consequence this means that the only method by which OAuth on the desktop could provide additional security is the one method that presents a security warning to the user. How ironic.<\\/p><\\/font><\\/span>","created":"29 minutes ago","margin":"80","user":"pilif"},{"id":"5337265","content":"OAuth has been called flawed by security experts because it\'s filled with trap holes for developers to fall into[1] and former developers who left the project[2].
1. http:\\/\\/homakov.blogspot.jp\\/2013\\/03\\/oauth1-oauth2-oauth.html<\\/a><\\/p>
2. http:\\/\\/en.wikipedia.org\\/wiki\\/OAuth#Controversy<\\/a><\\/p><\\/font><\\/span>","created":"1 hour ago","margin":"80","user":"qompiler"},{"id":"5337360","content":"Sure, but that\'s beside the point. OAuth does not attempt to deal with this problem. If you have the secret consumer token, then you are<\\/i> that consumer in the eyes of OAuth.<\\/font><\\/span>","created":"1 hour ago","margin":"120","user":"eli"},{"id":"5337191","content":"> OAuth just doesn\'t work with locally installed applications
That\'s not really true, Twitter just has a broken model where they want to authenticate the application<\\/i> as well as the user...<\\/p>
The rest of your comment is quite spot on, though. This is going to be a cat and mouse game for Twitter and I\'m not sure they can win.<\\/p><\\/font><\\/span>","created":"2 hours ago","margin":"40","user":"codeka"},{"id":"5337426","content":"> Twitter just has a broken model where they want to authenticate the application as well as the user...
Can you elaborate? As far as I understand how it works, this is the case by design in OAuth.<\\/p>
(edit: typo)<\\/p><\\/font><\\/span>","created":"58 minutes ago","margin":"80","user":"martius"},{"id":"5337229","content":"This is a surprise to absolutely no-one. That client keys & secrets were semi-public knowledge was obvious years ago, before I started working on OAuth at a much younger Twitter. The client key and secret is a rough trust metric for clients that are distributed publicly. Twitter can distribute new clients with new more-hidden secrets, and gain a bit more trust, for a while.
The place where the client id and secret actually offer real security is in the hosted scenario, where the secret is never distributed outside a trusted environment. Anyone who tells you different is wrong. The same applies to every single copy protection scheme (SSL\\/TLS, HDCP, DVD Region Coding, etc, etc, etc) and barring some mathematical breakthrough, this will always be true.<\\/p><\\/font><\\/span>","created":"2 hours ago","margin":"40","user":"romeda"},{"id":"5337564","content":"For some reason several of the commenters here are explaining this away as a protocol bug (specifically with OAuth) but the challenge isn\'t at all protocol specific. Rather, it\'s a hardship with all client\\/server apps, specifically in that trusting any client requires additional support from the platform (self-assertion or possession of a secret by the client alone is insufficient) and even then it\'s known hard problem.
This has been true of client\\/server apps for a very long time, well predating any particular protocol. I\'d be sincerely interested in any solutions that people come up with that don\'t depend on additional extrinsic platform capabilities.<\\/p><\\/font><\\/span>","created":"21 minutes ago","margin":"0","user":"dewitt"},{"id":"5337230","content":"Something I\'ve been pointing out about OAuth for ever<\\/i> is that it\'s a method for delegating authorization to agents who wish to act on behalf of the user. When it is the actual user him\\/herself who is acting, there\'s nothing wrong (and a lot of things right) with username\\/password authentication.<\\/font><\\/span>","created":"2 hours ago","margin":"0","user":"zacharyvoase"},{"id":"5337285","content":"OAuth prevents apps from nabbing passwords, though.<\\/font><\\/span>","created":"1 hour ago","margin":"40","user":"TazeTSchnitzel"},{"id":"5337363","content":"If the app is displaying its own internal web view for OAuth, it can load any page it wants in there and tell the user it\'s Twitter. It can even fake an address bar with a twitter.com URL if it wants. Then use the common phishing technique of \'oops, you must have entered your password wrong\' (the user didn\'t, but now the phisher has it), followed by a forward to the real site so the user suspects nothing.
But this is hypothetical. In reality there is little motivation for apps in an App Store-like environment, which survive on customer goodwill, to want to do this.<\\/p>
The user\'s security is probably not why Twitter chose OAuth.<\\/p><\\/font><\\/span>","created":"1 hour ago","margin":"80","user":"dmdeller"},{"id":"5337347","content":"Any token-based system will.
OAuth, having solved this problem, then goes on to create many more.<\\/p><\\/font><\\/span>","created":"1 hour ago","margin":"80","user":"yarrel"},{"id":"5337329","content":"For people who think this is going to cause drive-by Twitter hijacks, remember that Twitter stores the callback URL on their side for this very reason. Any web app impersonating these apps will fail at the callback stage.<\\/font><\\/span>","created":"1 hour ago","margin":"0","user":"Kudos"},{"id":"5337255","content":"If you ship a binary to a person’s computer and that binary has a secret embedded in it, that secret will eventually be discovered.
This has been discussed here before: http:\\/\\/news.ycombinator.com\\/item?id=4411696<\\/a><\\/p><\\/font><\\/span>","created":"1 hour ago","margin":"0","user":"mathias"},{"id":"5337116","content":"uh, this is not good. Why would someone post that under their own github account?<\\/font><\\/span>","created":"2 hours ago","margin":"0","user":"lukeholder"},{"id":"5337133","content":"It\'s more or less public knowledge. You can find it yourself by running \\"strings\\" on the Twitter app binary. Any attempts on Twitter\'s part to limit the disclosure of these tokens would almost certainly invoke the Streisand Effect.<\\/font><\\/span>","created":"2 hours ago","margin":"40","user":"cmelbye"},{"id":"5337178","content":"It would be possible to obfuscate a secret by storing it in several parts and combining them at run time. Still very far from secure, but this would require much more effort to extract the secret from the app.
Anyone: what is best practice here (Android and\\/or iOS)?<\\/p>
Edit:<\\/p>
Storing application secrets in Android\'s credential storage<\\/i> [1]. I have no idea how secure this actually is.<\\/p>
Should I obfuscate OAuth consumer secret stored by Android app?<\\/i> [2]<\\/p>
[1] http:\\/\\/nelenkov.blogspot.co.uk\\/2012\\/05\\/storing-application-s...<\\/a><\\/p>
[2] http:\\/\\/stackoverflow.com\\/questions\\/7121966\\/should-i-obfuscat...<\\/a><\\/p><\\/font><\\/span>","created":"2 hours ago","margin":"80","user":"andyjohnson0"},{"id":"5337631","content":"> It would be possible to obfuscate a secret by storing it in several parts and combining them at run time.<\\/i>
Then run `strings\' on virtual memory image of the offending process. Same difference.<\\/p><\\/font><\\/span>","created":"5 minutes ago","margin":"120","user":"dexen"},{"id":"5337188","content":"For Android, I suppose you could just run a Java bytecode obfuscator before converting the bytecode to Dalvik. There doesn\'t seem to be something comparable for iOS.
One simple solution is to set N-1 arrays to random data (hardcoded or generated at compile time) and set the last array to the real secret XOR random array #1 XOR random array #2 XOR ... XOR random array #N-1; this doesn\'t exactly stop a determined attacker, but it does stop \\"strings\\".<\\/p><\\/font><\\/span>","created":"2 hours ago","margin":"120","user":"JoachimSchipper"},{"id":"5337458","content":"Unless you know the device isn\'t rooted this doesn\'t really achieve very much. On a rooted device an \\"attacker\\" could have replaced the crede tial storage with something that will conveniently store the data unprotected.
It is helpful as a way of ensuring random applications don\'t get hold of the data, but not for keeping the data from a determined user.<\\/p><\\/font><\\/span>","created":"51 minutes ago","margin":"120","user":"vidarh"},{"id":"5337183","content":"Correct me if I\'m wrong here but I believe that then all one would need to do is stick an SSL intercepting proxy (such as http:\\/\\/mitmproxy.org\\/doc\\/ssl.html<\\/a>) in the middle and get the keys from there.<\\/font><\\/span>","created":"2 hours ago","margin":"120","user":"pandog"},{"id":"5337211","content":"That depends on how the secret is used by the client to authenticate with the remote service.
If the client just sends the secret as part of an authentication request, then a proxy would reveal it. But if some form of challenge\\/response [1] process is used, where the value sent is derived from the secret and an unpredictable challenge sent by the remote service, then as far as I know a proxy wouldn\'t help.<\\/p>
I don\'t know enough about the details of the Twitter\\/DropBox\\/etc APIs work to know if they use challenge-response.<\\/p>
[1] http:\\/\\/en.wikipedia.org\\/wiki\\/Challenge%E2%80%93response_auth...<\\/a><\\/p><\\/font><\\/span>","created":"2 hours ago","margin":"160","user":"andyjohnson0"},{"id":"5337190","content":"It\'s at least possible<\\/i> to accept only the keys from your real servers, which would stop this attack.
For what happens in the real world, see Georgiev et al.\'s \\"The most dangerous code in the world\\" at https:\\/\\/crypto.stanford.edu\\/~dabo\\/pubs\\/abstracts\\/ssl-client-...<\\/a> (spoiler: I described this paper in our internal knowledgebase as \\"very readable. Promises lots of facepalming and delivers in spades.\\")<\\/p><\\/font><\\/span>","created":"2 hours ago","margin":"160","user":"JoachimSchipper"},{"id":"5337371","content":"Only if the app uses the phone\'s certificate store, as opposed to a hard-coded one.<\\/font><\\/span>","created":"1 hour ago","margin":"160","user":"lucian1900"},{"id":"5337136","content":"Couldn\'t another app use these tokens and take advantage of lax api limits ?<\\/font><\\/span>","created":"2 hours ago","margin":"80","user":"lukeholder"},{"id":"5337148","content":"Yes. And that\'s the point of the disclosure.<\\/font><\\/span>","created":"2 hours ago","margin":"120","user":"pilif"},{"id":"5337181","content":"I think Apple will simply not permit applications that use these keys and are not official clients in the App Store. Looks like something that is pretty easy to automate.<\\/font><\\/span>","created":"2 hours ago","margin":"120","user":"jasiek"},{"id":"5337199","content":"I\'m not sure why Apple would play police for Twitter, though.<\\/font><\\/span>","created":"2 hours ago","margin":"160","user":"codeka"},{"id":"5337603","content":"Isn\'t Twitter integrated into Apple\'s mobile operating system? Such tight partnerships is plenty reason for them to play police for Twitter.<\\/font><\\/span>","created":"11 minutes ago","margin":"200","user":"cryptoz"},{"id":"5337203","content":"You presume that one would use the keys on iPhone. No reason you couldn\'t run them on a Linux box in AWS...<\\/font><\\/span>","created":"2 hours ago","margin":"160","user":"cbsmith"},{"id":"5337236","content":"How would Apple know that the app uses these keys? If they run something similar to strings then all you have to do is store the keys in some kind of obfuscated form.<\\/font><\\/span>","created":"2 hours ago","margin":"160","user":"arethuza"},{"id":"5337310","content":"Right but as soon as the press find out, and they will, that developer account will be banned. Most devs won\'t see it as worth the risk.<\\/font><\\/span>","created":"1 hour ago","margin":"200","user":"interpol_p"},{"id":"5337429","content":"What responsibility does Apple have to Twitter except the notification center widget?<\\/font><\\/span>","created":"57 minutes ago","margin":"160","user":"cathustler"},{"id":"5337151","content":"NekoTsui supports to change consumer key\\/secret.https:\\/\\/itunes.apple.com\\/app\\/nekotsui\\/id476924886<\\/a><\\/font><\\/span>","created":"2 hours ago","margin":"120","user":"matsuu"},{"id":"5337279","content":"On Android, the foss client Twidere let users change the tokens in the options. https:\\/\\/play.google.com\\/store\\/apps\\/details?id=org.mariotaku....<\\/a>
The Chrome app Hotot too. https:\\/\\/chrome.google.com\\/webstore\\/detail\\/hotot\\/cnfkkfleeioo...<\\/a><\\/p><\\/font><\\/span>","created":"1 hour ago","margin":"120","user":"mortalkastor"},{"id":"5337365","content":"Twitter did this to themselves. Without the limit, this information is worthless. It\'ll make sense for an app like Tweetro[1] to add custom token as a feature or easter egg.
1: http:\\/\\/www.theverge.com\\/2012\\/11\\/11\\/3631108\\/tweetro-user-toke...<\\/a><\\/p><\\/font><\\/span>","created":"1 hour ago","margin":"120","user":"soemarko"},{"id":"5337139","content":"Isn\'t this actually a great thing since it enables developers to develop Twitter clients that aren\'t dependent on tokens and approval from Twitter?<\\/font><\\/span>","created":"2 hours ago","margin":"40","user":"kristofferR"},{"id":"5337155","content":"You could only make an app that would explode if\\/when they decide to change keys<\\/font><\\/span>","created":"2 hours ago","margin":"80","user":"scorcher"},{"id":"5337180","content":"The app could also just download the latest extracted keys from your server when it experiences an authentication failure.<\\/font><\\/span>","created":"2 hours ago","margin":"120","user":"mtrimpe"},{"id":"5337170","content":"Along with any official client that is slightly out of date. Twitter might be hesitant to alienate their users.<\\/font><\\/span>","created":"2 hours ago","margin":"120","user":"Drakim"},{"id":"5337173","content":"I guess twitter, and any other client would just say - key revoked, you need to update the app for it to work again. It\'s an endless game of cat and mouse.<\\/font><\\/span>","created":"2 hours ago","margin":"120","user":"aidos"},{"id":"5337323","content":"Using the official key in an unofficial client sounds like a problem that will be solved with the legal system, not by increasing the burden on Twitter.<\\/font><\\/span>","created":"1 hour ago","margin":"160","user":"nanidin"},{"id":"5337166","content":"But they can\'t change those keys without isolating every installation until it\'s updated, right?<\\/font><\\/span>","created":"2 hours ago","margin":"120","user":"slig"},{"id":"5337391","content":"Indeed. Constantly changing keys would cause as many problems for users of the official client as it would for unofficial clients.
Twitter could make some way for the official client to fetch new keys from a server without a binary update, but then they\'d have to somehow protect that<\\/i> mechanism from third parties...<\\/p>
I suppose the next logical step would be to procedurally generate keys based on the date, and have only the algorithm (not the keys themselves) known to the official client. Not in any way insurmountable, but a little more difficult to crack.<\\/p><\\/font><\\/span>","created":"1 hour ago","margin":"160","user":"dmdeller"}]')

$('.minimizer').click(didClickMinimizer);

});<commit_msg>cleanup<commit_after>function parseComments(jsonString) {
  var container = $('#comments');

  try {
    var comments = JSON.parse(jsonString);
  }
  catch(error) {
    container.html('
' + error + '
');
    return;
  }

  container.html('');

  document.body.scrollTop = 0;

  for (var i = 0; i < comments.length; i++) {
    var comment = comments[i];
    var element = $('
' +
                      '
' +
                        '' +
                          comment.user +
                          '-' +
                        '' +
                        '' +
                          comment.created +
                        '' +
                      '
' +
                      comment.content +
                    '
');

    container.append(element);
  }

  $('.minimizer').click(didClickMinimizer);
}

function didClickMinimizer(event) {
  var button = $(event.target);
  var commentContainer = button.parents('.commentContainer');

  if (button.html() === '-') {
    commentContainer.find('.meta').addClass('hidden');
    commentContainer.find('.comment').hide();
    minimizeNextComments(commentContainer, null);

    button.html('+');
  }
  else if (button.html() === '+') {
    commentContainer.find('.meta').removeClass('hidden');
    commentContainer.find('.comment').show();
    maximizeNextComments(commentContainer, null);

    button.html('-');
  }
}

function minimizeNextComments(startComment, initialMargin) {
  var startMargin = +startComment.attr('class').split('-')[1];

  if (initialMargin == null)
    initialMargin = startMargin;

  var nextComment = startComment.next();

  if (!nextComment.length)
    return;

  var nextMargin = +nextComment.attr('class').split('-')[1];

  if (nextMargin > initialMargin) {
    nextComment.hide();
    minimizeNextComments(nextComment, initialMargin);
  }
}

function maximizeNextComments(startComment, initialMargin) {
  var startMargin = +startComment.attr('class').split('-')[1];

  if (initialMargin == null)
    initialMargin = startMargin;

  var nextComment = startComment.next();

  if (!nextComment.length)
    return;

  var nextMargin  = +nextComment.attr('class').split('-')[1];

  if (nextMargin > initialMargin) {
    nextComment.show();
    maximizeNextComments(nextComment, initialMargin);
  }
}<|endoftext|>


<commit_before>var canvasToBlob = require('blueimp-canvas-to-blob/js/canvas-to-blob');

exports.create = function (data, callback) {
  var image = new Image()
    , reader = new FileReader();

  if (typeof data === 'string') {
    image.src = data;
    return callback(null, image);
  }

  reader.onload = function (e) {
    image.src = e.target.result;
    callback(null, image);
  };

  reader.readAsDataURL(data);
};

exports.resize = function (image, width, height, callback) {
  var canvas = document.createElement('canvas')
    , context = canvas.getContext('2d')
    , resizedImage = new Image();

  if (image.width === width && image.height === height) {
    return callback(null, image);
  }

  resizedImage.onload = function () {
    callback(null, resizedImage);
  };

  canvas.width = width;
  canvas.height = height;
  context.drawImage(image, 0, 0, width, height);
  resizedImage.src = canvas.toDataURL('image/png');
};

exports.toBlob = function (image, callback) {
  var canvas = document.createElement('canvas')
    , context = canvas.getContext('2d');

  canvas.width = image.width;
  canvas.height = image.height;
  context.drawImage(image, 0, 0);
  canvas.toBlob(function (blob) {
    callback(null, blob);
  });
};

exports.transform = function (image, matrix, containerWidth, containerHeight, callback) {
  var canvas = document.createElement('canvas')
    , context = canvas.getContext('2d')
    , x = (image.width - image.width * matrix[0]) / 2
    , y = (image.height - image.height * matrix[3]) / 2
    , transformedImage = new Image();

  transformedImage.onload = function () {
    callback(null, transformedImage);
  };

  canvas.width = containerWidth;
  canvas.height = containerHeight;
  context.translate(x, y);
  context.transform.apply(context, matrix);
  context.drawImage(image, 0, 0);
  transformedImage.src = canvas.toDataURL('image/png');
};<commit_msg>naming, scale image<commit_after>var canvasToBlob = require('blueimp-canvas-to-blob/js/canvas-to-blob');

exports.create = function (data, callback) {
  var image = new Image()
    , reader = new FileReader();

  if (typeof data === 'string') {
    image.src = data;
    return callback(null, image);
  }

  reader.onload = function (e) {
    image.src = e.target.result;
    callback(null, image);
  };

  reader.readAsDataURL(data);
};

exports.resize = function (image, width, height, callback) {
  var canvas = document.createElement('canvas')
    , ctx = canvas.getContext('2d')
    , resizedImage = new Image();

  if (image.width === width && image.height === height) {
    return callback(null, image);
  }

  resizedImage.onload = function () {
    callback(null, resizedImage);
  };

  canvas.width = width;
  canvas.height = height;
  ctx.drawImage(image, 0, 0, width, height);
  resizedImage.src = canvas.toDataURL('image/png');
};

exports.toBlob = function (image, callback) {
  var canvas = document.createElement('canvas')
    , ctx = canvas.getContext('2d');

  canvas.width = image.width;
  canvas.height = image.height;
  ctx.drawImage(image, 0, 0);
  canvas.toBlob(function (blob) {
    callback(null, blob);
  });
};

exports.transform = function (image, matrix, canvasWidth, canvasHeight, callback) {
  var canvas = document.createElement('canvas')
    , ctx = canvas.getContext('2d')
    , x1 = (image.width - (image.width * matrix[0])) / 2
    , x2 = matrix[4] + x1
    , y1 = (image.height - (image.height * matrix[3])) / 2
    , y2 = matrix[5] + y1
    , transformedImage = new Image();

  transformedImage.onload = function () {
    callback(null, transformedImage);
  };

  canvas.width = canvasWidth;
  canvas.height = canvasHeight;
  ctx.translate(x1, y1);
  ctx.transform.apply(ctx, matrix);
  ctx.scale(2, 2);
  ctx.translate(x2, y2);
  ctx.drawImage(image, 0, 0);
  transformedImage.src = canvas.toDataURL('image/png');
};<|endoftext|>


<commit_before>(function () {
  'use strict';

  module.exports = ['Config', '$location', function (Config, $location) {

    function link(scope) {

      // Set key to watch for active class
      scope.item.active = false;

      /**
       * Check if passed href is a location of current page
       * @param  {String}  href - href of the link
       * @return {Boolean}
       */
      function isActive(href) {
        return '/' + href === $location.path();
      }

      /**
       * Assign checked expression to the item active key
       */
      function check() {
        scope.item.active = isActive(scope.item.href);
      }

      // Set watcher onRouteChangeSuccess and destroyer for it
      var destroyWatcher = scope.$on('$routeChangeSuccess', check);

      // Initial check for active link
      check();

      // Clean up
      scope.$on('$destroy', destroyWatcher);
    }

    return {
      templateUrl: Config.rootPath + 'shared/navigation/navigation-view.html',
      link: link,
      sope: {},
      replace: true
    };
  }];

}());<commit_msg>Remove scope isolated scope from navigation directive<commit_after>(function () {
  'use strict';

  module.exports = ['Config', '$location', function (Config, $location) {

    function link(scope) {

      // Set key to watch for active class
      scope.item.active = false;

      /**
       * Check if passed href is a location of current page
       * @param  {String}  href - href of the link
       * @return {Boolean}
       */
      function isActive(href) {
        return '/' + href === $location.path();
      }

      /**
       * Assign checked expression to the item active key
       */
      function check() {
        scope.item.active = isActive(scope.item.href);
      }

      // Set watcher onRouteChangeSuccess and destroyer for it
      var destroyWatcher = scope.$on('$routeChangeSuccess', check);

      // Initial check for active link
      check();

      // Clean up
      scope.$on('$destroy', destroyWatcher);
    }

    return {
      templateUrl: Config.rootPath + 'shared/navigation/navigation-view.html',
      link: link,
      replace: true
    };
  }];

}());<|endoftext|>


<commit_before>
import Ember from "ember";

export default Ember.Component.extend({

  tagName: 'div',
  classNames: ['ui', 'selection', 'dropdown', 'fluid'],
  classNameBindings: ['multiple', 'search'],

  content: null,
  value: null,
  optionValuePath: null,
  optionLabelPath: null,
  allowAdditions: null,

  bindAttributes: ['optionValuePath', 'optionLabelPath', 'value', 'content', 'allowAdditions'],

  actions: {},

  options: function () {
    var optionLabelPath = this.get('optionLabelPath');
    var optionValuePath = this.get('optionValuePath');
    var selectContent = this.get('content');

    return selectContent ? selectContent.map(function (item, index, list) {
      return {
        value: optionValuePath ? item.get(optionValuePath) : item,
        label: optionLabelPath ? item.get(optionLabelPath) : item
      };
    }, this) : [];

  }.property('content.@each'),

  setup: function () {
    this.$().dropdown({
      allowAdditions: this.allowAdditions,
      onChange: Ember.run.bind(this, this.onChange)
    }).dropdown('set exactly', this.value);

  }.on('didInsertElement'),

  // onValueChange: function () {
  //   if (typeof this.value === 'undefined') {
  //     return;
  //   }

  //   this.$().dropdown('set exactly', Ember.A(this.value.map(function (item, index, list) {
  //     return item.replace(',', '');
  //   })));

  // }.observes('value'),

  onChange: function (value, text, $choice) {

    // NOTE: This will have to be improved, can not assume ignoring value === undefined is okay
    // if ((this.value && this.value.toString() === value) || (this.value === undefined)) {
    //   return;
    // }

    if (this.optionValuePath) {
      value = this.get('content').findBy(this.optionValuePath, value);
    }

    if (this.get('multiple')) {
     value = value ? Ember.A(value.split(',')) : undefined;
    }

    this.set('value', value);

  }

});
<commit_msg>Use set selected instead of set exactly because of an issue with exactly and what might be scope issue<commit_after>
import Ember from "ember";

export default Ember.Component.extend({

  tagName: 'div',
  classNames: ['ui', 'selection', 'dropdown', 'fluid'],
  classNameBindings: ['multiple', 'search'],

  content: null,
  value: null,
  optionValuePath: null,
  optionLabelPath: null,
  allowAdditions: null,

  bindAttributes: ['optionValuePath', 'optionLabelPath', 'value', 'content', 'allowAdditions'],

  actions: {},

  options: function () {
    var optionLabelPath = this.get('optionLabelPath');
    var optionValuePath = this.get('optionValuePath');
    var selectContent = this.get('content');

    return selectContent ? selectContent.map(function (item, index, list) {
      return {
        value: optionValuePath ? item.get(optionValuePath) : item,
        label: optionLabelPath ? item.get(optionLabelPath) : item
      };
    }, this) : [];

  }.property('content.@each'),

  setup: function () {
    this.$().dropdown({
      allowAdditions: this.allowAdditions,
      onChange: Ember.run.bind(this, this.onChange)
    }).dropdown('set selected', this.value);

  }.on('didInsertElement'),

  // onValueChange: function () {
  //   if (typeof this.value === 'undefined') {
  //     return;
  //   }

  //   this.$().dropdown('set selected', Ember.A(this.value.map(function (item, index, list) {
  //     return item.replace(',', '');
  //   })));

  // }.observes('value'),

  onChange: function (value, text, $choice) {

    // NOTE: This will have to be improved, can not assume ignoring value === undefined is okay
    // if ((this.value && this.value.toString() === value) || (this.value === undefined)) {
    //   return;
    // }

    if (this.optionValuePath) {
      value = this.get('content').findBy(this.optionValuePath, value);
    }

    if (this.get('multiple')) {
     value = value ? Ember.A(value.split(',')) : undefined;
    }

    this.set('value', value);

  }

});
<|endoftext|>


<commit_before>'use strict';

angular.module('meanChatApp')
  .directive('chatWindow', function (messageService, messageFactory, Auth) {
    return {
      templateUrl: 'components/chatWindow/chatWindow.html',
      restrict: 'EA',
      link: function (scope, element, attrs) {
      	scope.message;
    	scope.userId = Auth.getCurrentUser()._id;

    	scope.clear = function(){
    		scope.message = "";
    	}

      	scope.submitMessage = function(){
      		console.log('submitting message: ');
      		//if the user isnt logged in or if the message is blank, return false..will add error messages later
      		if((scope.userId === undefined) || (scope.message === undefined) || (scope.message === '')){
      			return false;
      		}

      		//setup our message object
      		var messageObject = {
      			userId: scope.userId,
      			message: scope.message
      		}

      		//call the message factory and submit it to the backend
      		messageFactory.create(messageObject);
      		//set the message model back to an empty string, clearing the user input
      		scope.message = "";
      	}
      }
    };
  });
<commit_msg>removed console.log<commit_after>'use strict';

angular.module('meanChatApp')
  .directive('chatWindow', function (messageService, messageFactory, Auth) {
    return {
      templateUrl: 'components/chatWindow/chatWindow.html',
      restrict: 'EA',
      link: function (scope, element, attrs) {
      	scope.message;
    	scope.userId = Auth.getCurrentUser()._id;

    	scope.clear = function(){
    		scope.message = "";
    	}

      	scope.submitMessage = function(){
      		//if the user isnt logged in or if the message is blank, return false..will add error messages later
      		if((scope.userId === undefined) || (scope.message === undefined) || (scope.message === '')){
      			return false;
      		}

      		//setup our message object
      		var messageObject = {
      			userId: scope.userId,
      			message: scope.message
      		}

      		//call the message factory and submit it to the backend
      		messageFactory.create(messageObject);
      		//set the message model back to an empty string, clearing the user input
      		scope.message = "";
      	}
      }
    };
  });
<|endoftext|>


<commit_before>'use strict';

angular.module('familyThiefApp')
  .controller('InboxCtrl', function ($scope, $http, Auth, HelpRequest, $location) {

    // inject data of currently logged-in user into this controller
    $scope.user = Auth.getCurrentUser();
    // array of results initially set to empty
    $scope.contributions = [];

    //retrieve from database all objects with contributions to user's help request(s)
    $scope.contribution = function() {
      $http({
        url: "api/contributions/:id",
        method: "GET",
      }).success(function(data, status) {
        console.log(data);
        $scope.contributions.push(data);
      });
    };

    //loads contribution in new view when clicked
    $scope.loadContribution = function(id) {
      Auth.setContribution(id);  // sets the id of the contribution that the user is about to view
      $location.path('/contributionRecd');
    };

  });<commit_msg>switched api route in inbox.controller.js to api/mailbox<commit_after>'use strict';

angular.module('familyThiefApp')
  .controller('InboxCtrl', function ($scope, $http, Auth, HelpRequest, $location) {

    // inject data of currently logged-in user into this controller
    $scope.user = Auth.getCurrentUser();
    // array of results initially set to empty
    $scope.contributions = [];

    //retrieve from database all objects with contributions to user's help request(s)
    $scope.contribution = function() {
      $http({
        url: "api/mailbox",
        method: "GET",
      }).success(function(data, status) {
        console.log(data);
        $scope.contributions.push(data);
      });
    };

    //loads contribution in new view when clicked
    $scope.loadContribution = function(id) {
      Auth.setContribution(id);  // sets the id of the contribution that the user is about to view
      $location.path('/contributionRecd');
    };

  });<|endoftext|>


<commit_before>function fillProfileDataAndSave(name, email) {
  cy.getByTestId('Name').type(`{selectall}${name}`);
  cy.getByTestId('Email').type(`{selectall}${email}{enter}`);
  cy.contains('Saved.');
}

function fillChangePasswordAndSave(currentPassword, newPassword, repeatPassword) {
  cy.getByTestId('CurrentPassword').type(currentPassword);
  cy.getByTestId('NewPassword').type(newPassword);
  cy.getByTestId('RepeatPassword').type(`${repeatPassword}{enter}`);
}

describe('Edit Profile', () => {
  beforeEach(() => {
    cy.login();
    cy.visit('/users/me');
  });

  it('updates the user after Save', () => {
    fillProfileDataAndSave('Jian Yang', 'jian.yang@redash.io');
    cy.logout();
    cy.login('jian.yang@redash.io').its('status').should('eq', 200);
    cy.visit('/users/me');
    cy.contains('Jian Yang');
    fillProfileDataAndSave('Example Admin', 'admin@redash.io');
  });

  it('regenerates API Key', () => {
    cy.getByTestId('ApiKey').then(($apiKey) => {
      const previousApiKey = $apiKey.val();

      cy.getByTestId('RegenerateApiKey').click();
      cy.get('.ant-btn-primary').contains('Regenerate').click({ force: true });

      cy.getByTestId('ApiKey').should('not.eq', previousApiKey);
    });
  });

  it('renders the page and takes a screenshot', () => {
    cy.getByTestId('ApiKey').then(($apiKey) => {
      $apiKey.val('secret');
    });

    cy.getByTestId('Groups').should('contain', 'admin');

    cy.percySnapshot('User Profile');
  });

  context('changing password', () => {
    beforeEach(() => {
      cy.getByTestId('ChangePassword').click();
    });

    it('updates user password when password is correct', () => {
      fillChangePasswordAndSave('password', 'newpassword', 'newpassword');
      cy.contains('Saved.');
      cy.logout();
      cy.login(undefined, 'newpassword').its('status').should('eq', 200);
      cy.visit('/users/me');
      cy.getByTestId('ChangePassword').click();
      fillChangePasswordAndSave('newpassword', 'password', 'password');
      cy.contains('Saved.');
    });

    it('shows an error when current password is wrong', () => {
      fillChangePasswordAndSave('wrongpassword', 'newpassword', 'newpassword');
      cy.contains('Incorrect current password.');
    });
  });
});
<commit_msg>Fix Percy diff due to Api Key secret (#3654)<commit_after>function fillProfileDataAndSave(name, email) {
  cy.getByTestId('Name').type(`{selectall}${name}`);
  cy.getByTestId('Email').type(`{selectall}${email}{enter}`);
  cy.contains('Saved.');
}

function fillChangePasswordAndSave(currentPassword, newPassword, repeatPassword) {
  cy.getByTestId('CurrentPassword').type(currentPassword);
  cy.getByTestId('NewPassword').type(newPassword);
  cy.getByTestId('RepeatPassword').type(`${repeatPassword}{enter}`);
}

describe('Edit Profile', () => {
  beforeEach(() => {
    cy.login();
    cy.visit('/users/me');
  });

  it('updates the user after Save', () => {
    fillProfileDataAndSave('Jian Yang', 'jian.yang@redash.io');
    cy.logout();
    cy.login('jian.yang@redash.io').its('status').should('eq', 200);
    cy.visit('/users/me');
    cy.contains('Jian Yang');
    fillProfileDataAndSave('Example Admin', 'admin@redash.io');
  });

  it('regenerates API Key', () => {
    cy.getByTestId('ApiKey').then(($apiKey) => {
      const previousApiKey = $apiKey.val();

      cy.getByTestId('RegenerateApiKey').click();
      cy.get('.ant-btn-primary').contains('Regenerate').click({ force: true });

      cy.getByTestId('ApiKey').should('not.eq', previousApiKey);
    });
  });

  it('renders the page and takes a screenshot', () => {
    cy.getByTestId('Groups').should('contain', 'admin');
    cy.getByTestId('ApiKey').then(($apiKey) => {
      $apiKey.val('secret');
      cy.percySnapshot('User Profile');
    });
  });

  context('changing password', () => {
    beforeEach(() => {
      cy.getByTestId('ChangePassword').click();
    });

    it('updates user password when password is correct', () => {
      fillChangePasswordAndSave('password', 'newpassword', 'newpassword');
      cy.contains('Saved.');
      cy.logout();
      cy.login(undefined, 'newpassword').its('status').should('eq', 200);
      cy.visit('/users/me');
      cy.getByTestId('ChangePassword').click();
      fillChangePasswordAndSave('newpassword', 'password', 'password');
      cy.contains('Saved.');
    });

    it('shows an error when current password is wrong', () => {
      fillChangePasswordAndSave('wrongpassword', 'newpassword', 'newpassword');
      cy.contains('Incorrect current password.');
    });
  });
});
<|endoftext|>


<commit_before>timeRange = {
  config: {
    elemClass: 'timeRange'
  , barElemClass: 'timeRange__bar'
  , step: 100
  }
, events: {}
, position: 0
, max: 2000
, init: function () {
    var self = this
    self.element = document.querySelector('.' + self.config.elemClass)
    self.bar = document.querySelector('.' + self.config.barElemClass)
    if(self.bar && self.element) self.setListeners()
  }
, stepUp: function () {
    var self = this
    self.setAbstractPosition(self.position + self.config.step)
  }
, stepDown: function () {
    var self = this
    self.setAbstractPosition(self.position - self.config.step)
  }
, setAbstractPosition: function (newPosition) {
    var self = this
    newPosition = newPosition > self.max ? self.max : newPosition
    newPosition = newPosition < 0 ? 0 : newPosition
    self.position = newPosition
    self.updateElementPosition()
    self.events.change = new CustomEvent('changed', {
      detail: {position: self.position}
    })
    self.element.dispatchEvent(self.events.change)
  }
, setRealPosition: function (newPosition) {
    var self = this
  , abstractPos = 0
    abstractPos = Math.round( newPosition / ( self.element.clientWidth / self.max ) / self.config.step ) * self.config.step
    console.log(newPosition, abstractPos)
    self.setAbstractPosition(abstractPos)
  }
, updateElementPosition: function () {
    var self = this
    if(self.element) {
      self.bar.style.left = Math.round(self.element.clientWidth / self.max * self.position) + 'px'
    }
  }
, setListeners: function () {
    var self = this
    self.element.addEventListener('click', function (e) {
      self.setRealPosition(e.x)
    })
  }
}

Template.timeRange.rendered = function () {
  timeRange.init()
}<commit_msg>Fix eventbubbeling bug<commit_after>timeRange = {
  config: {
    elemClass: 'timeRange'
  , barElemClass: 'timeRange__bar'
  , step: 100
  }
, events: {}
, position: 0
, max: 2000
, init: function () {
    var self = this
    self.element = document.querySelector('.' + self.config.elemClass)
    self.bar = document.querySelector('.' + self.config.barElemClass)
    if(self.bar && self.element) self.setListeners()
  }
, stepUp: function () {
    var self = this
    self.setAbstractPosition(self.position + self.config.step)
  }
, stepDown: function () {
    var self = this
    self.setAbstractPosition(self.position - self.config.step)
  }
, setAbstractPosition: function (newPosition) {
    var self = this
    newPosition = newPosition > self.max ? self.max : newPosition
    newPosition = newPosition < 0 ? 0 : newPosition
    self.position = newPosition
    self.updateElementPosition()
    self.events.change = new CustomEvent('changed', {
      detail: {position: self.position}
    })
    self.element.dispatchEvent(self.events.change)
  }
, setRealPosition: function (newPosition) {
    var self = this
  , abstractPos = 0
    abstractPos = Math.round( newPosition / ( self.element.clientWidth / self.max ) / self.config.step ) * self.config.step
    console.log(newPosition, abstractPos)
    self.setAbstractPosition(abstractPos)
  }
, updateElementPosition: function () {
    var self = this
    if(self.element) {
      self.bar.style.left = Math.round(self.element.clientWidth / self.max * self.position) + 'px'
    }
  }
, setListeners: function () {
    var self = this
    self.element.addEventListener('click', function (e) {
      if(e.toElement === self.element) self.setRealPosition(e.x)
    })
  }
}

Template.timeRange.rendered = function () {
  timeRange.init()
}<|endoftext|>


<commit_before>import React from 'react';
import { Card, CardHeader, CardMedia, CardTitle } from 'material-ui/Card';

const ServerThumbnail = ({
  server,
  media
}) => (
  <Card className="thumb">

      <CardHeader title={server.name} subtitle={server.description} />
      {media && (
        <CardMedia
          overlay={(
            <CardTitle
              title={media.title}
              subtitle={media.artist}
            />
          )}
        >

        </CardMedia>
      )}

  </Card>
);

export default ServerThumbnail;
<commit_msg>client: Improve background contrast for current song titles.<commit_after>import React from 'react';
import { Card, CardHeader, CardMedia, CardTitle } from 'material-ui/Card';

const overlayContentStyle = {
  background: 'rgba(0, 0, 0, 0.75)',
};

const ServerThumbnail = ({
  server,
  media
}) => (
  <Card className="thumb">

      <CardHeader title={server.name} subtitle={server.description} />
      {media && (
        <CardMedia
          overlay={(
            <CardTitle
              title={media.title}
              subtitle={media.artist}
            />
          )}
          overlayContentStyle={overlayContentStyle}
        >

        </CardMedia>
      )}

  </Card>
);

export default ServerThumbnail;
<|endoftext|>


<commit_before>
// CSS
import styles from './style.css';

import React from 'react';
import PureComponent from 'react-pure-render/component';

import config from '../../../config';
import IconButton from '../../IconButton';

class ControlBtn extends PureComponent {

    constructor() {
        super();
        this._onDeleteSurveyClick = this._onDeleteSurveyClick.bind(this);
        this._onCopySurveyClick = this._onCopySurveyClick.bind(this);
        this._onAddSurveyClick = this._onAddSurveyClick.bind(this);
        this._onPreviewSurveyClick = this._onPreviewSurveyClick.bind(this);
        this._onReportSurveyClick = this._onReportSurveyClick.bind(this);
    }

    render() {
        const { selectedSurveys, selectedUser } = this.props;
        const btnsData = [
            { id: 'createBtn', string: 'Create Survey', img: '', func: this._onAddSurveyClick },
            { id: 'shareBtn', string: 'Share', img: 'share', func: this._onPreviewSurveyClick },
            { id: 'reportBtn', string: 'Report', img: 'report', func: this._onReportSurveyClick },
            { id: 'copyBtn', string: 'Duplicate', img: 'delete', func: this._onCopySurveyClick },
            { id: 'delBtn', string: 'Delete', img: 'delete', func: this._onDeleteSurveyClick }
        ];
        let btns = [];
        btnsData.forEach((btn, idx) => {
            if ((idx === 0 && !selectedUser.hasOwnProperty('accountid'))
                || (idx !== 0 && selectedSurveys !== '')) {
                btns.push(
                    <IconButton
                        key={idx}
                        id={btn.id}
                        string={btn.string}
                        i18nKey={false}
                        img={btn.img}
                        onClick={btn.func}
                    />);
            }
        });

        return (

{btns}

        );
    }

    _onAddSurveyClick() {
        const { editSubjectActions } = this.props;
        editSubjectActions.openEdit(true);
    }

    _onDeleteSurveyClick() {
        const { surveysActions } = this.props;
        surveysActions.deleteSurvey();
    }

    _onCopySurveyClick() {
        const { surveysActions } = this.props;
        surveysActions.copySurvey();
    }

    _onReportSurveyClick() {
        const { surveysActions } = this.props;
        surveysActions.exportSurvey();
    }

    _onPreviewSurveyClick() {
        const { account, selectedSurveys, selectedUser } = this.props;
        const url = `${config.baseURL}/feedback/index.html`;
        // selected user account or user's account
        const accountid = selectedUser.hasOwnProperty('accountid') ?
            selectedUser.accountid : account.accountid;
        window.open(`${url}?accountid=${accountid}&surveyid=${selectedSurveys}`, '_blank');
    }
}

export default ControlBtn;
<commit_msg>Use report image for duplicate button temporarily<commit_after>
// CSS
import styles from './style.css';

import React from 'react';
import PureComponent from 'react-pure-render/component';

import config from '../../../config';
import IconButton from '../../IconButton';

class ControlBtn extends PureComponent {

    constructor() {
        super();
        this._onDeleteSurveyClick = this._onDeleteSurveyClick.bind(this);
        this._onCopySurveyClick = this._onCopySurveyClick.bind(this);
        this._onAddSurveyClick = this._onAddSurveyClick.bind(this);
        this._onPreviewSurveyClick = this._onPreviewSurveyClick.bind(this);
        this._onReportSurveyClick = this._onReportSurveyClick.bind(this);
    }

    render() {
        const { selectedSurveys, selectedUser } = this.props;
        const btnsData = [
            { id: 'createBtn', string: 'Create Survey', img: '', func: this._onAddSurveyClick },
            { id: 'shareBtn', string: 'Share', img: 'share', func: this._onPreviewSurveyClick },
            { id: 'reportBtn', string: 'Report', img: 'report', func: this._onReportSurveyClick },
            { id: 'copyBtn', string: 'Duplicate', img: 'report', func: this._onCopySurveyClick },
            { id: 'delBtn', string: 'Delete', img: 'delete', func: this._onDeleteSurveyClick }
        ];
        let btns = [];
        btnsData.forEach((btn, idx) => {
            if ((idx === 0 && !selectedUser.hasOwnProperty('accountid'))
                || (idx !== 0 && selectedSurveys !== '')) {
                btns.push(
                    <IconButton
                        key={idx}
                        id={btn.id}
                        string={btn.string}
                        i18nKey={false}
                        img={btn.img}
                        onClick={btn.func}
                    />);
            }
        });

        return (

{btns}

        );
    }

    _onAddSurveyClick() {
        const { editSubjectActions } = this.props;
        editSubjectActions.openEdit(true);
    }

    _onDeleteSurveyClick() {
        const { surveysActions } = this.props;
        surveysActions.deleteSurvey();
    }

    _onCopySurveyClick() {
        const { surveysActions } = this.props;
        surveysActions.copySurvey();
    }

    _onReportSurveyClick() {
        const { surveysActions } = this.props;
        surveysActions.exportSurvey();
    }

    _onPreviewSurveyClick() {
        const { account, selectedSurveys, selectedUser } = this.props;
        const url = `${config.baseURL}/feedback/index.html`;
        // selected user account or user's account
        const accountid = selectedUser.hasOwnProperty('accountid') ?
            selectedUser.accountid : account.accountid;
        window.open(`${url}?accountid=${accountid}&surveyid=${selectedSurveys}`, '_blank');
    }
}

export default ControlBtn;
<|endoftext|>


<commit_before>import React from "react";
import Moment from "moment";
import { connect } from "react-redux";
import { Switch, Route, Redirect } from "react-router-dom";
import { push } from "react-router-redux";
import { LinkContainer } from "react-router-bootstrap";
import { Nav, NavItem, Badge } from "react-bootstrap";
import { getReference } from "../../actions";
import { LoadingPlaceholder, Icon, ViewHeader, Flex, FlexItem } from "../../../base";

import EditReference from "./Edit";
import ReferenceManage from "./Manage";
import ReferenceUsers from "./Users";
import ReferenceGroups from "./Groups";
import ReferenceOTUs from "../../../otus/components/List";
import ReferenceIndexList from "../../../indexes/components/List";
import SourceTypes from "../../../administration/components/General/SourceTypes";
import InternalControl from "../../../administration/components/General/InternalControl";

const ReferenceSettings = () => (

        <SourceTypes />
        <InternalControl />
        <ReferenceUsers />
        <ReferenceGroups />

);

class ReferenceDetail extends React.Component {

    componentDidMount () {
        this.props.onGetReference(this.props.match.params.refId);
    }

    render = () => {

        if (this.props.detail === null || this.props.detail.id !== this.props.match.params.refId) {
            return <LoadingPlaceholder />;
        }

        const { name, id, remotes_from, created_at, user } = this.props.detail;

        let headerIcon;

        if (this.props.pathname === `/refs/${id}/manage`) {
            headerIcon = remotes_from
                ? (
                    <Icon
                        bsStyle="default"
                        name="lock"
                        pullRight
                        style={{fontSize: "65%"}}
                    />
                )
                : (
                    <Icon
                        bsStyle="warning"
                        name="pencil-alt"
                        tip="Edit"
                        onClick={this.props.onEdit}
                        pullRight
                        style={{fontSize: "65%"}}
                    />
                );
        }

        return (

                <ViewHeader title={`${name} - References`}>
                    <Flex alignItems="flex-end">
                        <FlexItem grow={1}>
                            {name}
                        </FlexItem>
                        {headerIcon}
                    </Flex>

                        Created {Moment(created_at).calendar()} by {user.id}

                </ViewHeader>

                    <LinkContainer to={`/refs/${id}/manage`}>
                        <NavItem>Manage</NavItem>
                    </LinkContainer>
                    <LinkContainer to={`/refs/${id}/otus`}>
                        <NavItem>OTUs <Badge>{this.props.detail.otu_count}</Badge></NavItem>
                    </LinkContainer>
                    <LinkContainer to={`/refs/${id}/indexes`}>
                        <NavItem>Indexes</NavItem>
                    </LinkContainer>
                    <LinkContainer to={`/refs/${id}/settings`}>
                        <NavItem>Settings</NavItem>
                    </LinkContainer>

                <Switch>
                    <Redirect from="/refs/:refId" to={`/refs/${id}/manage`} exact />
                    <Route path="/refs/:refId/manage" component={ReferenceManage} />
                    <Route path="/refs/:refId/otus" component={ReferenceOTUs} />
                    <Route path="/refs/:refId/indexes" component={ReferenceIndexList} />
                    <Route path="/refs/:refId/settings" component={ReferenceSettings} />
                </Switch>

                <EditReference />

        );
    };
}

const mapStateToProps = state => ({
    detail: state.references.detail,
    pathname: state.router.location.pathname
});

const mapDispatchToProps = dispatch => ({

    onGetReference: (refId) => {
        dispatch(getReference(refId));
    },

    onEdit: () => {
        dispatch(push({...window.location, state: {editReference: true}}));
    }

});

export default connect(mapStateToProps, mapDispatchToProps)(ReferenceDetail);
<commit_msg>Hide source types for remote references<commit_after>import React from "react";
import Moment from "moment";
import { connect } from "react-redux";
import { Switch, Route, Redirect } from "react-router-dom";
import { push } from "react-router-redux";
import { LinkContainer } from "react-router-bootstrap";
import { Nav, NavItem, Badge } from "react-bootstrap";
import { getReference } from "../../actions";
import { LoadingPlaceholder, Icon, ViewHeader, Flex, FlexItem } from "../../../base";

import EditReference from "./Edit";
import ReferenceManage from "./Manage";
import ReferenceUsers from "./Users";
import ReferenceGroups from "./Groups";
import ReferenceOTUs from "../../../otus/components/List";
import ReferenceIndexList from "../../../indexes/components/List";
import SourceTypes from "../../../administration/components/General/SourceTypes";
import InternalControl from "../../../administration/components/General/InternalControl";

const ReferenceSettings = ({ isRemote }) => (

        {isRemote ? null : <SourceTypes />}
        <InternalControl />
        <ReferenceUsers />
        <ReferenceGroups />

);

class ReferenceDetail extends React.Component {

    componentDidMount () {
        this.props.onGetReference(this.props.match.params.refId);
    }

    render = () => {

        if (this.props.detail === null || this.props.detail.id !== this.props.match.params.refId) {
            return <LoadingPlaceholder />;
        }

        const { name, id, remotes_from, created_at, user } = this.props.detail;

        let headerIcon;

        if (this.props.pathname === `/refs/${id}/manage`) {
            headerIcon = remotes_from
                ? (
                    <Icon
                        bsStyle="default"
                        name="lock"
                        pullRight
                        style={{fontSize: "65%"}}
                    />
                )
                : (
                    <Icon
                        bsStyle="warning"
                        name="pencil-alt"
                        tip="Edit"
                        onClick={this.props.onEdit}
                        pullRight
                        style={{fontSize: "65%"}}
                    />
                );
        }

        return (

                <ViewHeader title={`${name} - References`}>
                    <Flex alignItems="flex-end">
                        <FlexItem grow={1}>
                            {name}
                        </FlexItem>
                        {headerIcon}
                    </Flex>

                        Created {Moment(created_at).calendar()} by {user.id}

                </ViewHeader>

                    <LinkContainer to={`/refs/${id}/manage`}>
                        <NavItem>Manage</NavItem>
                    </LinkContainer>
                    <LinkContainer to={`/refs/${id}/otus`}>
                        <NavItem>OTUs <Badge>{this.props.detail.otu_count}</Badge></NavItem>
                    </LinkContainer>
                    <LinkContainer to={`/refs/${id}/indexes`}>
                        <NavItem>Indexes</NavItem>
                    </LinkContainer>
                    <LinkContainer to={`/refs/${id}/settings`}>
                        <NavItem>Settings</NavItem>
                    </LinkContainer>

                <Switch>
                    <Redirect from="/refs/:refId" to={`/refs/${id}/manage`} exact />
                    <Route path="/refs/:refId/manage" component={ReferenceManage} />
                    <Route path="/refs/:refId/otus" component={ReferenceOTUs} />
                    <Route path="/refs/:refId/indexes" component={ReferenceIndexList} />
                    <Route path="/refs/:refId/settings" render={() => <ReferenceSettings isRemote={remotes_from} />} />
                </Switch>

                <EditReference />

        );
    };
}

const mapStateToProps = state => ({
    detail: state.references.detail,
    pathname: state.router.location.pathname
});

const mapDispatchToProps = dispatch => ({

    onGetReference: (refId) => {
        dispatch(getReference(refId));
    },

    onEdit: () => {
        dispatch(push({...window.location, state: {editReference: true}}));
    }

});

export default connect(mapStateToProps, mapDispatchToProps)(ReferenceDetail);
<|endoftext|>


<commit_before>app.controller('LoginCtrl', function($scope, $location, Auth) {
    $scope.user = {
        email: '',
        password: '',
        name:''
    };

    $scope.login = function() {
        Auth.login($scope.user)
            .then(function(){
                $location.path('/dashboard');
            });
    };
    $scope.register = function() {
        Auth.register($scope.user)
            .then(function(){
                $location.path('/dashboard');
            });
    };
});

//gonna test including this in this js file, will cleanup to standards after

$("#register").load($(this).attr("href"),function(){
    var flag_register = 0;
    var registration = document.getElementById("register");
    var login = document.getElementById("login");
    var authform = document.getElementById("auth");

    var reg_info = document.getElementsByName("name")[0];

    function register() {
        flag_register = 1;
        reg_info.className = "in";
        reg_info.required = true;
        registration.innerHTML = "Just Kidding";
        login.innerHTML = "Register";
        authform.attr("ng-submit","register()");
    }

    function unregister() {
        flag_register = 0;
        reg_info.className = "in hide";
        reg_info.required = false;
        reg_info.value = "";
        registration.innerHTML = "Register";
        login.innerHTML = "Login";
        authform.attr("ng-submit","login()");
    }

    registration.addEventListener('click', function() {
        flag_register == 0 ? register(): unregister() ;

    }, false);
};
<commit_msg>hopefull last (actually)<commit_after>app.controller('LoginCtrl', function($scope, $location, Auth) {
    $scope.user = {
        email: '',
        password: '',
        name:''
    };

    $scope.login = function() {
        Auth.login($scope.user)
            .then(function(){
                $location.path('/dashboard');
            });
    };
    $scope.register = function() {
        Auth.register($scope.user)
            .then(function(){
                $location.path('/dashboard');
            });
    };
});

//gonna test including this in this js file, will cleanup to standards after

$("#register").load($(this).attr("href"),function(){
    var flag_register = 0;
    var registration = document.getElementById("register");
    var login = document.getElementById("login");
    var authform = document.getElementById("auth");

    var reg_info = document.getElementsByName("name")[0];

    function register() {
        flag_register = 1;
        reg_info.className = "in";
        reg_info.required = true;
        registration.innerHTML = "Just Kidding";
        login.innerHTML = "Register";
        authform.attr("ng-submit","register()");
    }

    function unregister() {
        flag_register = 0;
        reg_info.className = "in hide";
        reg_info.required = false;
        reg_info.value = "";
        registration.innerHTML = "Register";
        login.innerHTML = "Login";
        authform.attr("ng-submit","login()");
    }

    registration.addEventListener('click', function() {
        flag_register == 0 ? register(): unregister() ;

    }, false);
});
<|endoftext|>


<commit_before>/* eslint spaced-comment: 0 */
/* eslint no-unused-vars: 0 */
/* eslint no-redeclare: 0 */
const assert = require('chai').assert;

/// title: Gittaca
/// type: arcade-mode

/// categories:
/// algorithms
/// dynamic programming
/// longest common subsequence

/// difficulty: 8

/// benchmark:
diff(benchmark[0], benchmark[1]);

/// images:
/// public/img/challenges/pexels-photo-127556.jpeg

/// description:
///
/// With the arrival of precision genetic editing tools such as CRISPR/cas9 and the emerging field of nanoengineering, you wish to create a version control system for tracking modifications of genetic data. Instead of line-based diffing, your algorithm will be nucleotide (character) based. Knowing that most genomes are in the ballpark of billions of DNA base pairs (Gbp), diffing complete genomes cannot be completed in a reasonable amount of time or space given current technology levels. Therefore, you decide to focus on diffing genetic data at the gene level and lower in the ballpark of hundred thousands base pairs (Mbp). Since you just are just beginning, you test small strings first.
/// Implement an algorithm that will output the diff of two provided strings
/// Sample input
/// diff('GTAGACA', 'GATTACA');
/// Sample outputs (multiple answers possible)
/// G +A T +T A -G -A C A
/// G -T A +T +T -G A C A
/// G -T A -G +T +T A C A

/// challengeSeed:
function diff (oldStr, newStr) {
  // Good luck!
  return true;
}

/// solutions:
function createLCSTable (str1, str2, l1, l2) {
  const arr = new Array(l1 + 1).fill(0).map(() => new Array(l2 + 1).fill(0));
  for (let row = 1; row <= l1; row++) {
    for (let col = 1; col <= l2; col++) {
      if (str1[row - 1] === str2[col - 1]) {
        arr[row][col] = arr[row - 1][col - 1] + 1;
      }
      else arr[row][col] = Math.max(arr[row - 1][col], arr[row][col - 1]);
    }
  }
  return arr;
}

function generateDiff (oldStr, newStr, lcsTable, diffArr) {
  const osl = oldStr.length;
  const nsl = newStr.length;

  if (osl > 0 && nsl > 0 && oldStr[osl - 1] === newStr[nsl - 1]) {
    diffArr.unshift(oldStr[osl - 1]);
    generateDiff(oldStr.slice(0, -1), newStr.slice(0, -1), lcsTable, diffArr);
  }
  else if (nsl > 0 && (osl === 0 || lcsTable[osl][nsl - 1] >= lcsTable[osl - 1][nsl])) {
    diffArr.unshift(`+${newStr[nsl - 1]}`);
    generateDiff(oldStr, newStr.slice(0, -1), lcsTable, diffArr);
  }
  else if (osl > 0 && (nsl === 0 || lcsTable[osl][nsl - 1] < lcsTable[osl - 1][nsl])) {
    diffArr.unshift(`-${oldStr[osl - 1]}`);
    generateDiff(oldStr.slice(0, -1), newStr, lcsTable, diffArr);
  }
}

function diff (oldStr, newStr) {
  const lcsTable = createLCSTable(oldStr, newStr, oldStr.length, newStr.length);
  const diffArr = [];
  generateDiff(oldStr, newStr, lcsTable, diffArr);

  return diffArr.join(' ');
}

/// tail:
const testCase1 = ['GTTG', 'GTTAC'];
const tc1Answers = [['G', 'T', 'T', '+A', '+C', '-G'], ['G', 'T', 'T', '-G', '+A', '+C']].map(arr => arr.join(' '));
const testCase2 = ['GTCAGTCA', 'TGTGCAGC'];
const tc2Answers = ['+T', 'G', 'T', '+G', 'C', 'A', 'G', '-T', 'C', '-A'].join(' ');
const benchmark = [
  'GTCAGCATCGATCGGTCCAATCGACAAATAAAGATCGGTGGGCGATTAGAGCGTGGC',
  'TCGATTCTGCCCTCCATAGGTAGACACATTATATCGATCGTGGTGCGTTGCCTAGTAGT'
];

/// tests:
assert(typeof diff === 'function', 'message: diff is a function.');
assert.include(tc1Answers, diff(testCase1[0], testCase1[1]), 'message: diff("GTTG", "GTTAC") should return either "G T T +A +C -G" or "G T T -G +A +C"');
assert.equal(tc2Answers, diff(testCase2[0], testCase2[1]), 'message: diff("GTCAGTCA", "TGTGCAGC") should return "+T G T +G C A G -T C -A"');
<commit_msg>update tests<commit_after>/* eslint spaced-comment: 0 */
/* eslint no-unused-vars: 0 */
/* eslint no-redeclare: 0 */
const assert = require('chai').assert;

/// title: Gittaca
/// type: arcade-mode

/// categories:
/// algorithms
/// dynamic programming
/// longest common subsequence

/// difficulty: 8

/// benchmark:
diff(benchmark[0], benchmark[1]);

/// images:
/// public/img/challenges/pexels-photo-127556.jpeg

/// description:
///
/// With the arrival of precision genetic editing tools such as CRISPR/cas9 and the emerging field of nanoengineering, you wish to create a version control system for tracking modifications of genetic data. Instead of line-based diffing, your algorithm will be nucleotide (character) based. Knowing that most genomes are in the ballpark of billions of DNA base pairs (Gbp), diffing complete genomes cannot be completed in a reasonable amount of time or space given current technology levels. Therefore, you decide to focus on diffing genetic data at the gene level and lower in the ballpark of hundred thousands base pairs (Mbp). Since you just are just beginning, you test small strings first.
/// Implement an algorithm that will output the diff of two provided strings
/// Sample input
/// diff('GTAGACA', 'GATTACA');
/// Sample outputs (multiple answers possible)
/// G +A T +T A -G -A C A
/// G -T A +T +T -G A C A
/// G -T A -G +T +T A C A

/// challengeSeed:
function diff (oldStr, newStr) {
  // Good luck!
  return true;
}

/// solutions:
function createLCSTable (str1, str2, l1, l2) {
  const arr = new Array(l1 + 1).fill(0).map(() => new Array(l2 + 1).fill(0));
  for (let row = 1; row <= l1; row++) {
    for (let col = 1; col <= l2; col++) {
      if (str1[row - 1] === str2[col - 1]) {
        arr[row][col] = arr[row - 1][col - 1] + 1;
      }
      else arr[row][col] = Math.max(arr[row - 1][col], arr[row][col - 1]);
    }
  }
  return arr;
}

function generateDiff (oldStr, newStr, lcsTable, diffArr) {
  const osl = oldStr.length;
  const nsl = newStr.length;

  if (osl > 0 && nsl > 0 && oldStr[osl - 1] === newStr[nsl - 1]) {
    diffArr.unshift(oldStr[osl - 1]);
    generateDiff(oldStr.slice(0, -1), newStr.slice(0, -1), lcsTable, diffArr);
  }
  else if (nsl > 0 && (osl === 0 || lcsTable[osl][nsl - 1] >= lcsTable[osl - 1][nsl])) {
    diffArr.unshift(`+${newStr[nsl - 1]}`);
    generateDiff(oldStr, newStr.slice(0, -1), lcsTable, diffArr);
  }
  else if (osl > 0 && (nsl === 0 || lcsTable[osl][nsl - 1] < lcsTable[osl - 1][nsl])) {
    diffArr.unshift(`-${oldStr[osl - 1]}`);
    generateDiff(oldStr.slice(0, -1), newStr, lcsTable, diffArr);
  }
}

function diff (oldStr, newStr) {
  const lcsTable = createLCSTable(oldStr, newStr, oldStr.length, newStr.length);
  const diffArr = [];
  generateDiff(oldStr, newStr, lcsTable, diffArr);

  return diffArr.join(' ');
}

/// tail:
const testCase1 = ['GTTG', 'GTTAC'];
const tc1Answers = [['G', 'T', 'T', '+A', '+C', '-G'], ['G', 'T', 'T', '-G', '+A', '+C']].map(arr => arr.join(' '));
const testCase2 = ['AACAGTGT', 'AGTGCAGC'];
const tc2Answers = [
  ['A', '-A', '-C', '-A', 'G', 'T', 'G', '-T', '+C', '+A', '+G', '+C'],
  ['A', '-A', '-C', '-A', 'G', 'T', 'G', '+C', '+A', '+G', '+C', '-T']
].map(arr => arr.join(' '));
const benchmark = [
  'TGTAGAAGTGTCAGCATCGATCGGTCCAATCGACAAATAAAGGTTTCAACACTGTTCTCTTAGGAATCGGTGGGCGATTAGAGCGTGGC',
  'TCGATTCAAAATTGCCCTCCATAGGTAGACACATTATATCGATCGTGGTGCGTTGCCTAGTGTCACATCATAGGTAATTGCAGTAGAGT'
];

/// tests:
assert(typeof diff === 'function', 'message: diff is a function.');
assert.include(tc1Answers, diff(testCase1[0], testCase1[1]), 'message: diff("GTTG", "GTTAC") should return either "G T T +A +C -G" or "G T T -G +A +C"');
assert.include(tc2Answers, diff(testCase2[0], testCase2[1]), 'message: diff("AACAGTGT", "AGTGCAGC") should return either "A -A -C -A G T G -T +C +A +G +C" or "A -A -C -A G T G +C +A +G +C -T"');
<|endoftext|>


<commit_before>// Copyright 2016, University of Colorado Boulder

/**
 *
 *
 * @author Sam Reid (PhET Interactive Simulations)
 * @author Matt Pennington (PhET Interactive Simulations)
 */
/* eslint-env node */
'use strict';

// modules
var copyDirectory = require( './copyDirectory' );
var ChipperStringUtils = require( '../../common/ChipperStringUtils' );
var ChipperConstants = require( '../../common/ChipperConstants' );
// var generatePhETIOAPIDocs = require( './generatePhETIOAPIDocs' );

// constants
var DEDICATED_REPO_WRAPPER_PREFIX = 'phet-io-wrapper-';

// phet-io internal files to be consolidated into 1 file and publicly served as a minified phet-io library
var LIB_FILES = [
  '../query-string-machine/js/QueryStringMachine.js',
  '../phet-io-wrappers/common/js/SimIFrameClient.js',
  '../phet-io-wrappers/common/js/WrapperTypes.js',
  '../phet-io-wrappers/common/js/assert.js',
  '../phet-io-wrappers/common/js/WrapperUtils.js' ];

var LIB_DIR = ChipperConstants.BUILD_DIR + '/lib';
var LIB_OUTPUT_FILE = 'phet-io.js';
var LIB_COPYRIGHT_HEADER = '// Copyright 2002-2017, University of Colorado Boulder\n' +
                           '// This PhET-iO file requires a license\n' +
                           '// USE WITHOUT A LICENSE AGREEMENT IS STRICTLY PROHIBITED.\n' +
                           '// For licensing, please contact phethelp@colorado.edu';

// All libraries and third party files that are used by phet-io wrappers, and need to be copied over for a build
var CONTRIB_FILES = [
  '../sherpa/lib/lodash-4.17.4.min.js',
  '../sherpa/lib/font-awesome-4.5.0',
  '../sherpa/lib/jquery-2.1.0.min.js',
  '../sherpa/lib/jquery-ui-1.8.24.min.js',
  '../sherpa/lib/d3-4.2.2.js'
];
var CONTRIB_DIR = ChipperConstants.BUILD_DIR + '/contrib';

module.exports = function( grunt, buildConfig, done ) {

  // The filter that we run every phet-io wrapper file through to transform dev content into built content. This mainly
  // involves lots of hard coded copy replace of template strings and marker values.
  var filterWrapper = function( abspath, contents ) {
    var originalContents = contents + '';

    if ( abspath.indexOf( '.js' ) >= 0 || abspath.indexOf( '.html' ) >= 0 ) {
      contents = ChipperStringUtils.replaceAll( contents, '{{SIMULATION_NAME}}', buildConfig.name );
      contents = ChipperStringUtils.replaceAll( contents, '{{SIMULATION_VERSION}}', buildConfig.version );
      contents = ChipperStringUtils.replaceAll( contents, 'isBuilt: false', 'isBuilt: true' );
    }
    if ( abspath.indexOf( '.html' ) >= 0 ) {

      // change the paths of sherpa files to point to the contrib/ folder
      CONTRIB_FILES.forEach( function( filePath ) {
        var filePathParts = filePath.split( '/' );

        // If the file is in a dedicated wrapper repo, then it is one level higher in the dir tree, and needs 1 less set of dots
        // see https://github.com/phetsims/phet-io-wrappers/issues/17 for more info. This is hopefully a temporary workaround
        var eliminateExtraDotsForDedicatedWrappers = abspath.indexOf( DEDICATED_REPO_WRAPPER_PREFIX ) >= 0;

        var contribFileName = 'contrib/' + filePathParts[ filePathParts.length - 1 ];

        var pathToContrib = eliminateExtraDotsForDedicatedWrappers ? '../../' + contribFileName : '../' + contribFileName;

        contents = ChipperStringUtils.replaceAll( contents, filePath, pathToContrib );
      } );

      /*
       * Remove individual common phet-io code imports because they are all in phetio.js
       */
      // This returns the whole line that contains this substring, so it can be removed
      var firstQueryStringLine = ChipperStringUtils.firstLineThatContains( contents, 'QueryStringMachine.js">' );
      // Don't remove the import if it is coming from the phet-io website, only if it is a relative path in requirejs mode.
      if ( firstQueryStringLine && firstQueryStringLine.indexOf( 'phet-io.colorado.edu' ) === -1 ) {
        // Replace queryStringMachine with an empty line because 'phetio.js' has it already.
        contents = ChipperStringUtils.replaceAll( contents, firstQueryStringLine, '' );
      }
      var firstWrapperUtilsLine = ChipperStringUtils.firstLineThatContains( contents, 'WrapperUtils.js">' );
      if ( firstWrapperUtilsLine && firstWrapperUtilsLine.indexOf( 'phet-io.colorado.edu' ) === -1 ) {
        // Replace queryStringMachine with an empty line because 'phetio.js' has it already.
        contents = ChipperStringUtils.replaceAll( contents, firstWrapperUtilsLine, '' );
      }
      var firstAssertLine = ChipperStringUtils.firstLineThatContains( contents, 'assert.js">' );
      if ( firstAssertLine && firstAssertLine.indexOf( 'phet-io.colorado.edu' ) === -1 ) {
        // Replace queryStringMachine with an empty line because 'phetio.js' has it already.
        contents = ChipperStringUtils.replaceAll( contents, firstAssertLine, '' );
      }
      var firstIFrameClientLine = ChipperStringUtils.firstLineThatContains( contents, 'SimIFrameClient.js">' );
      if ( firstIFrameClientLine && firstIFrameClientLine.indexOf( 'phet-io.colorado.edu' ) === -1 ) {
        // Replace queryStringMachine with an empty line because 'phetio.js' has it already.
        contents = ChipperStringUtils.replaceAll( contents, firstIFrameClientLine, '' );
      }
      var firstWrapperTypeLine = ChipperStringUtils.firstLineThatContains( contents, 'WrapperTypes.js">' );
      if ( firstWrapperTypeLine && firstWrapperTypeLine.indexOf( 'phet-io.colorado.edu' ) === -1 ) {
        // Replace queryStringMachine with an empty line because 'phetio.js' has it already.
        contents = ChipperStringUtils.replaceAll( contents, firstWrapperTypeLine, '' );
      }

      // For info about phetio.js, see the end of this file
      contents = ChipperStringUtils.replaceAll( contents,
        '<!--{{phet-io.js}}-->',
        '<script type="text/javascript" src="../../lib/' + LIB_OUTPUT_FILE + '"></script>'
      );
      contents = ChipperStringUtils.replaceAll( contents,
        '<!--{{GOOGLE_ANALYTICS.js}}-->',
        '<script type="text/javascript" src="/assets/js/phet-io-ga.js"></script>'
      );
      contents = ChipperStringUtils.replaceAll( contents,
        '<!--{{FAVICON.ico}}-->',
        '<link rel="shortcut icon" href="/assets/favicon.ico"/>'
      );

      // phet-io-wrappers/common will be in the top level of wrappers/ in the build directory
      contents = ChipperStringUtils.replaceAll( contents, 'phet-io-wrappers/common/', 'common/' );
    }
    if ( contents !== originalContents ) {
      return contents;
    }
    else {
      return null; // signify no change (helps for images)
    }
  };

  // Load the master list of all wrappers
  var wrappers = grunt.file.read( '../chipper/data/wrappers' ).toString().split( /\r?\n/ );

  // Files and directories from wrapper folders that we don't want to copy
  var wrappersBlacklist = [ '.git', 'README.md', '.gitignore', 'node_modules', 'package.json', 'build' ];

  var libFileNames = LIB_FILES.map( function( filePath ) {
    var parts = filePath.split( '/' );
    return parts[ parts.length - 1 ];
  } );

  // Don't copy over the files that are in the lib file, this way we can catch wrapper bugs that are not pointing to the lib.
  var fullBlacklist = wrappersBlacklist.concat( libFileNames );

  // Make sure to copy the phet-io-wrappers common wrapper code too.
  wrappers.push( 'phet-io-wrappers/common' );
  wrappers.forEach( function( wrapper ) {

    var wrapperParts = wrapper.split( '/' );

    // either take the last path part, or take the first (repo name) and remove the wrapper prefix
    var wrapperName = wrapperParts.length > 1 ? wrapperParts[ wrapperParts.length - 1 ] : wrapperParts[ 0 ].replace( DEDICATED_REPO_WRAPPER_PREFIX, '' );

    // Copy the wrapper into the build dir /wrappers/, exclude the blacklist, and minify the js code
    copyDirectory( grunt, '../' + wrapper, ChipperConstants.BUILD_DIR + '/wrappers/' + wrapperName, filterWrapper, {
      blacklist: fullBlacklist,
      minifyJS: true
    } );
  } );

  // Copy over the dev guide and the needed dependencies
  handleDevGuide( grunt );

  // Create the lib file that is minified and publicly available under the /lib folder of the build
  handleLib( grunt, filterWrapper );

  // Create the contrib folder and add to it third party libraries used by wrappers.
  handleContrib( grunt );

  // Generate API Documentation
  if ( grunt.option( 'phetioDocs' ) ) {
    // generatePhETIOAPIDocs( grunt, buildConfig, done );
  }
};

/**
 * Given the list of lib files, apply a filter function to them. Then minify them and consolidate into a single string.
 * Finally write them to the build dir with a license prepended. See https://github.com/phetsims/phet-io/issues/353

 * @param grunt
 * @param {Function} filter - the filter function used when copying over the dev guide, to fix relative paths and such
 *                            has arguments like "function(abspath, contents)"
 */
var handleLib = function( grunt, filter ) {

  // TODO: chipper#101 eek, this is scary! we are importing from the node_modules dir. ideally we should just have uglify-js installed once in sherpa?
  var uglify = require( '../../../node_modules/uglify-js/tools/node' );// eslint-disable-line require-statement-match

  grunt.file.mkdir( LIB_DIR );

  var consolidated = '';
  LIB_FILES.forEach( function( libFile ) {
    var contents = grunt.file.read( libFile );

    var filteredContents = filter && filter( libFile, contents );

    // The filter should return null if nothing changes
    consolidated += filteredContents ? filteredContents : contents;
  } );

  var minified = uglify.minify( consolidated, {
    fromString: true,
    mangle: true,
    output: {
      inline_script: true, // escape </script
      beautify: false
    },
    compress: {
      global_defs: {}
    }
  } ).code;

  grunt.file.write( LIB_DIR + '/' + LIB_OUTPUT_FILE, LIB_COPYRIGHT_HEADER + '\n\n' + minified );
};

/**
 * Copy the appropriate resources and files to the build folder needed for the develeopment guide.
 * @param grunt
 * @param {Function} filter - the filter function used when copying over the dev guide, to fix relative paths and such
 *                            has arguments like "function(abspath, contents)"
 */
var handleDevGuide = function( grunt, filter ) {
  var devguideHTML = grunt.file.read( '../phet-io-website/root/devguide/index.html' );
  devguideHTML = ChipperStringUtils.replaceAll( devguideHTML, '../assets/bootstrap-3.3.6-dist/css/bootstrap.min.css', 'https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css' );
  devguideHTML = ChipperStringUtils.replaceAll( devguideHTML, '../assets/bootstrap-3.3.6-dist/js/bootstrap.min.js', 'https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js' );
  devguideHTML = ChipperStringUtils.replaceAll( devguideHTML, '../assets/font-awesome-4.5.0/css/font-awesome.min.css', 'https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.5.0/css/font-awesome.min.css' );
  devguideHTML = ChipperStringUtils.replaceAll( devguideHTML, '../assets/js/jquery-1.12.3.min.js', 'https://code.jquery.com/jquery-1.12.3.min.js' );
  devguideHTML = ChipperStringUtils.replaceAll( devguideHTML, '../assets/css/', './css/' );
  devguideHTML = ChipperStringUtils.replaceAll( devguideHTML, '../assets/js/', './js/' );
  devguideHTML = ChipperStringUtils.replaceAll( devguideHTML, '../assets/highlight.js-9.1.0/styles/tomorrow-night-bright.css', 'https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.1.0/styles/tomorrow-night-bright.min.css' );
  devguideHTML = ChipperStringUtils.replaceAll( devguideHTML, '../assets/highlight.js-9.1.0/highlight.js', 'https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.1.0/highlight.min.js' );
  devguideHTML = ChipperStringUtils.replaceAll( devguideHTML, '../assets/favicon.ico', './favicon.ico' );

  // Remove the navbar and footer div tags
  var firstNavbarLine = ChipperStringUtils.firstLineThatContains( devguideHTML, 'id="navbar"' );
  devguideHTML = firstNavbarLine ? ChipperStringUtils.replaceAll( devguideHTML, firstNavbarLine, '' ) : devguideHTML;

  var firstFooterLine = ChipperStringUtils.firstLineThatContains( devguideHTML, 'id="footer"' );
  devguideHTML = firstFooterLine ? ChipperStringUtils.replaceAll( devguideHTML, firstFooterLine, '' ) : devguideHTML;

  grunt.file.write( ChipperConstants.BUILD_DIR + '/docs/devguide.html', devguideHTML );
  copyDirectory( grunt, '../phet-io-website/root/assets/css', ChipperConstants.BUILD_DIR + '/docs/css', filter );
  grunt.file.copy( '../phet-io-website/root/assets/js/phet-io.js', './' + ChipperConstants.BUILD_DIR + '/docs/js/phet-io.js' );
  grunt.file.copy( '../phet-io-website/root/assets/js/phet-io-ga.js', './' + ChipperConstants.BUILD_DIR + '/docs/js/phet-io-ga.js' );
  grunt.file.copy( '../phet-io-website/root/assets/favicon.ico', './' + ChipperConstants.BUILD_DIR + '/docs/favicon.ico' );

};

/**
 * Copy all of the third party libraries from sherpa to the build directory under the 'contrib' folder.
 * @param grunt
 */
var handleContrib = function( grunt ) {
  CONTRIB_FILES.forEach( function( filePath ) {
    var filePathParts = filePath.split( '/' );

    var fileName = filePathParts[ filePathParts.length - 1 ];

    grunt.file.copy( filePath, './' + CONTRIB_DIR + '/' + fileName );

  } );

};<commit_msg>some js files have phet-io-wrappers/common path, https://github.com/phetsims/phet-io-wrappers/issues/17<commit_after>// Copyright 2016, University of Colorado Boulder

/**
 *
 *
 * @author Sam Reid (PhET Interactive Simulations)
 * @author Matt Pennington (PhET Interactive Simulations)
 */
/* eslint-env node */
'use strict';

// modules
var copyDirectory = require( './copyDirectory' );
var ChipperStringUtils = require( '../../common/ChipperStringUtils' );
var ChipperConstants = require( '../../common/ChipperConstants' );
// var generatePhETIOAPIDocs = require( './generatePhETIOAPIDocs' );

// constants
var DEDICATED_REPO_WRAPPER_PREFIX = 'phet-io-wrapper-';

// phet-io internal files to be consolidated into 1 file and publicly served as a minified phet-io library
var LIB_FILES = [
  '../query-string-machine/js/QueryStringMachine.js',
  '../phet-io-wrappers/common/js/SimIFrameClient.js',
  '../phet-io-wrappers/common/js/WrapperTypes.js',
  '../phet-io-wrappers/common/js/assert.js',
  '../phet-io-wrappers/common/js/WrapperUtils.js' ];

var LIB_DIR = ChipperConstants.BUILD_DIR + '/lib';
var LIB_OUTPUT_FILE = 'phet-io.js';
var LIB_COPYRIGHT_HEADER = '// Copyright 2002-2017, University of Colorado Boulder\n' +
                           '// This PhET-iO file requires a license\n' +
                           '// USE WITHOUT A LICENSE AGREEMENT IS STRICTLY PROHIBITED.\n' +
                           '// For licensing, please contact phethelp@colorado.edu';

// All libraries and third party files that are used by phet-io wrappers, and need to be copied over for a build
var CONTRIB_FILES = [
  '../sherpa/lib/lodash-4.17.4.min.js',
  '../sherpa/lib/font-awesome-4.5.0',
  '../sherpa/lib/jquery-2.1.0.min.js',
  '../sherpa/lib/jquery-ui-1.8.24.min.js',
  '../sherpa/lib/d3-4.2.2.js'
];
var CONTRIB_DIR = ChipperConstants.BUILD_DIR + '/contrib';

module.exports = function( grunt, buildConfig, done ) {

  // The filter that we run every phet-io wrapper file through to transform dev content into built content. This mainly
  // involves lots of hard coded copy replace of template strings and marker values.
  var filterWrapper = function( abspath, contents ) {
    var originalContents = contents + '';

    if ( abspath.indexOf( '.js' ) >= 0 || abspath.indexOf( '.html' ) >= 0 ) {
      contents = ChipperStringUtils.replaceAll( contents, '{{SIMULATION_NAME}}', buildConfig.name );
      contents = ChipperStringUtils.replaceAll( contents, '{{SIMULATION_VERSION}}', buildConfig.version );
      contents = ChipperStringUtils.replaceAll( contents, 'isBuilt: false', 'isBuilt: true' );

      // phet-io-wrappers/common will be in the top level of wrappers/ in the build directory
      contents = ChipperStringUtils.replaceAll( contents, 'phet-io-wrappers/common/', 'common/' );
    }
    if ( abspath.indexOf( '.html' ) >= 0 ) {

      // change the paths of sherpa files to point to the contrib/ folder
      CONTRIB_FILES.forEach( function( filePath ) {
        var filePathParts = filePath.split( '/' );

        // If the file is in a dedicated wrapper repo, then it is one level higher in the dir tree, and needs 1 less set of dots
        // see https://github.com/phetsims/phet-io-wrappers/issues/17 for more info. This is hopefully a temporary workaround
        var eliminateExtraDotsForDedicatedWrappers = abspath.indexOf( DEDICATED_REPO_WRAPPER_PREFIX ) >= 0;

        var contribFileName = 'contrib/' + filePathParts[ filePathParts.length - 1 ];

        var pathToContrib = eliminateExtraDotsForDedicatedWrappers ? '../../' + contribFileName : '../' + contribFileName;

        contents = ChipperStringUtils.replaceAll( contents, filePath, pathToContrib );
      } );

      /*
       * Remove individual common phet-io code imports because they are all in phetio.js
       */
      // This returns the whole line that contains this substring, so it can be removed
      var firstQueryStringLine = ChipperStringUtils.firstLineThatContains( contents, 'QueryStringMachine.js">' );
      // Don't remove the import if it is coming from the phet-io website, only if it is a relative path in requirejs mode.
      if ( firstQueryStringLine && firstQueryStringLine.indexOf( 'phet-io.colorado.edu' ) === -1 ) {
        // Replace queryStringMachine with an empty line because 'phetio.js' has it already.
        contents = ChipperStringUtils.replaceAll( contents, firstQueryStringLine, '' );
      }
      var firstWrapperUtilsLine = ChipperStringUtils.firstLineThatContains( contents, 'WrapperUtils.js">' );
      if ( firstWrapperUtilsLine && firstWrapperUtilsLine.indexOf( 'phet-io.colorado.edu' ) === -1 ) {
        // Replace queryStringMachine with an empty line because 'phetio.js' has it already.
        contents = ChipperStringUtils.replaceAll( contents, firstWrapperUtilsLine, '' );
      }
      var firstAssertLine = ChipperStringUtils.firstLineThatContains( contents, 'assert.js">' );
      if ( firstAssertLine && firstAssertLine.indexOf( 'phet-io.colorado.edu' ) === -1 ) {
        // Replace queryStringMachine with an empty line because 'phetio.js' has it already.
        contents = ChipperStringUtils.replaceAll( contents, firstAssertLine, '' );
      }
      var firstIFrameClientLine = ChipperStringUtils.firstLineThatContains( contents, 'SimIFrameClient.js">' );
      if ( firstIFrameClientLine && firstIFrameClientLine.indexOf( 'phet-io.colorado.edu' ) === -1 ) {
        // Replace queryStringMachine with an empty line because 'phetio.js' has it already.
        contents = ChipperStringUtils.replaceAll( contents, firstIFrameClientLine, '' );
      }
      var firstWrapperTypeLine = ChipperStringUtils.firstLineThatContains( contents, 'WrapperTypes.js">' );
      if ( firstWrapperTypeLine && firstWrapperTypeLine.indexOf( 'phet-io.colorado.edu' ) === -1 ) {
        // Replace queryStringMachine with an empty line because 'phetio.js' has it already.
        contents = ChipperStringUtils.replaceAll( contents, firstWrapperTypeLine, '' );
      }

      // For info about phetio.js, see the end of this file
      contents = ChipperStringUtils.replaceAll( contents,
        '<!--{{phet-io.js}}-->',
        '<script type="text/javascript" src="../../lib/' + LIB_OUTPUT_FILE + '"></script>'
      );
      contents = ChipperStringUtils.replaceAll( contents,
        '<!--{{GOOGLE_ANALYTICS.js}}-->',
        '<script type="text/javascript" src="/assets/js/phet-io-ga.js"></script>'
      );
      contents = ChipperStringUtils.replaceAll( contents,
        '<!--{{FAVICON.ico}}-->',
        '<link rel="shortcut icon" href="/assets/favicon.ico"/>'
      );
    }
    if ( contents !== originalContents ) {
      return contents;
    }
    else {
      return null; // signify no change (helps for images)
    }
  };

  // Load the master list of all wrappers
  var wrappers = grunt.file.read( '../chipper/data/wrappers' ).toString().split( /\r?\n/ );

  // Files and directories from wrapper folders that we don't want to copy
  var wrappersBlacklist = [ '.git', 'README.md', '.gitignore', 'node_modules', 'package.json', 'build' ];

  var libFileNames = LIB_FILES.map( function( filePath ) {
    var parts = filePath.split( '/' );
    return parts[ parts.length - 1 ];
  } );

  // Don't copy over the files that are in the lib file, this way we can catch wrapper bugs that are not pointing to the lib.
  var fullBlacklist = wrappersBlacklist.concat( libFileNames );

  // Make sure to copy the phet-io-wrappers common wrapper code too.
  wrappers.push( 'phet-io-wrappers/common' );
  wrappers.forEach( function( wrapper ) {

    var wrapperParts = wrapper.split( '/' );

    // either take the last path part, or take the first (repo name) and remove the wrapper prefix
    var wrapperName = wrapperParts.length > 1 ? wrapperParts[ wrapperParts.length - 1 ] : wrapperParts[ 0 ].replace( DEDICATED_REPO_WRAPPER_PREFIX, '' );

    // Copy the wrapper into the build dir /wrappers/, exclude the blacklist, and minify the js code
    copyDirectory( grunt, '../' + wrapper, ChipperConstants.BUILD_DIR + '/wrappers/' + wrapperName, filterWrapper, {
      blacklist: fullBlacklist,
      minifyJS: true
    } );
  } );

  // Copy over the dev guide and the needed dependencies
  handleDevGuide( grunt );

  // Create the lib file that is minified and publicly available under the /lib folder of the build
  handleLib( grunt, filterWrapper );

  // Create the contrib folder and add to it third party libraries used by wrappers.
  handleContrib( grunt );

  // Generate API Documentation
  if ( grunt.option( 'phetioDocs' ) ) {
    // generatePhETIOAPIDocs( grunt, buildConfig, done );
  }
};

/**
 * Given the list of lib files, apply a filter function to them. Then minify them and consolidate into a single string.
 * Finally write them to the build dir with a license prepended. See https://github.com/phetsims/phet-io/issues/353

 * @param grunt
 * @param {Function} filter - the filter function used when copying over the dev guide, to fix relative paths and such
 *                            has arguments like "function(abspath, contents)"
 */
var handleLib = function( grunt, filter ) {

  // TODO: chipper#101 eek, this is scary! we are importing from the node_modules dir. ideally we should just have uglify-js installed once in sherpa?
  var uglify = require( '../../../node_modules/uglify-js/tools/node' );// eslint-disable-line require-statement-match

  grunt.file.mkdir( LIB_DIR );

  var consolidated = '';
  LIB_FILES.forEach( function( libFile ) {
    var contents = grunt.file.read( libFile );

    var filteredContents = filter && filter( libFile, contents );

    // The filter should return null if nothing changes
    consolidated += filteredContents ? filteredContents : contents;
  } );

  var minified = uglify.minify( consolidated, {
    fromString: true,
    mangle: true,
    output: {
      inline_script: true, // escape </script
      beautify: false
    },
    compress: {
      global_defs: {}
    }
  } ).code;

  grunt.file.write( LIB_DIR + '/' + LIB_OUTPUT_FILE, LIB_COPYRIGHT_HEADER + '\n\n' + minified );
};

/**
 * Copy the appropriate resources and files to the build folder needed for the develeopment guide.
 * @param grunt
 * @param {Function} filter - the filter function used when copying over the dev guide, to fix relative paths and such
 *                            has arguments like "function(abspath, contents)"
 */
var handleDevGuide = function( grunt, filter ) {
  var devguideHTML = grunt.file.read( '../phet-io-website/root/devguide/index.html' );
  devguideHTML = ChipperStringUtils.replaceAll( devguideHTML, '../assets/bootstrap-3.3.6-dist/css/bootstrap.min.css', 'https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css' );
  devguideHTML = ChipperStringUtils.replaceAll( devguideHTML, '../assets/bootstrap-3.3.6-dist/js/bootstrap.min.js', 'https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js' );
  devguideHTML = ChipperStringUtils.replaceAll( devguideHTML, '../assets/font-awesome-4.5.0/css/font-awesome.min.css', 'https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.5.0/css/font-awesome.min.css' );
  devguideHTML = ChipperStringUtils.replaceAll( devguideHTML, '../assets/js/jquery-1.12.3.min.js', 'https://code.jquery.com/jquery-1.12.3.min.js' );
  devguideHTML = ChipperStringUtils.replaceAll( devguideHTML, '../assets/css/', './css/' );
  devguideHTML = ChipperStringUtils.replaceAll( devguideHTML, '../assets/js/', './js/' );
  devguideHTML = ChipperStringUtils.replaceAll( devguideHTML, '../assets/highlight.js-9.1.0/styles/tomorrow-night-bright.css', 'https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.1.0/styles/tomorrow-night-bright.min.css' );
  devguideHTML = ChipperStringUtils.replaceAll( devguideHTML, '../assets/highlight.js-9.1.0/highlight.js', 'https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.1.0/highlight.min.js' );
  devguideHTML = ChipperStringUtils.replaceAll( devguideHTML, '../assets/favicon.ico', './favicon.ico' );

  // Remove the navbar and footer div tags
  var firstNavbarLine = ChipperStringUtils.firstLineThatContains( devguideHTML, 'id="navbar"' );
  devguideHTML = firstNavbarLine ? ChipperStringUtils.replaceAll( devguideHTML, firstNavbarLine, '' ) : devguideHTML;

  var firstFooterLine = ChipperStringUtils.firstLineThatContains( devguideHTML, 'id="footer"' );
  devguideHTML = firstFooterLine ? ChipperStringUtils.replaceAll( devguideHTML, firstFooterLine, '' ) : devguideHTML;

  grunt.file.write( ChipperConstants.BUILD_DIR + '/docs/devguide.html', devguideHTML );
  copyDirectory( grunt, '../phet-io-website/root/assets/css', ChipperConstants.BUILD_DIR + '/docs/css', filter );
  grunt.file.copy( '../phet-io-website/root/assets/js/phet-io.js', './' + ChipperConstants.BUILD_DIR + '/docs/js/phet-io.js' );
  grunt.file.copy( '../phet-io-website/root/assets/js/phet-io-ga.js', './' + ChipperConstants.BUILD_DIR + '/docs/js/phet-io-ga.js' );
  grunt.file.copy( '../phet-io-website/root/assets/favicon.ico', './' + ChipperConstants.BUILD_DIR + '/docs/favicon.ico' );

};

/**
 * Copy all of the third party libraries from sherpa to the build directory under the 'contrib' folder.
 * @param grunt
 */
var handleContrib = function( grunt ) {
  CONTRIB_FILES.forEach( function( filePath ) {
    var filePathParts = filePath.split( '/' );

    var fileName = filePathParts[ filePathParts.length - 1 ];

    grunt.file.copy( filePath, './' + CONTRIB_DIR + '/' + fileName );

  } );

};<|endoftext|>