remove line breaks in paragraphs

remove line breaks in paragraphs

--- app/pages/lab/how-to-page.cjsx
+++ app/pages/lab/how-to-page.cjsx
@@ -16,35 +16,21 @@

       ### Getting Started

-      To get started building, go to [the Project Builder home page](./#/lab) and log in to your Zooniverse account,
-      then click the "Build a Project" button in the top right. Here you can see all of the projects you own and
-      collaborate on. Click on "Create a project" to start building.
+      To get started building, go to [the Project Builder home page](./#/lab) and log in to your Zooniverse account, then click the "Build a Project" button in the top right. Here you can see all of the projects you own and collaborate on. Click on "Create a project" to start building.

       [![Project Builder screenshot](./assets/how-to-lab/how-to-2.jpg)](./assets/how-to-lab/how-to-2.jpg)

-      **Start building:** Now you're in the Project Builder itself. This is where the magic happens.
-      On the left-hand side, you've got your main menus: Project, Workflow, and Subjects. These are
-      terms you'll see a lot, and they have specific meanings in the Zooniverse.  Project is pretty
-      self-explanatory; Galaxy Zoo, Penguin Watch, and of course, Kitteh Zoo, are all examples of
-      Zooniverse projects that you could build using the project builder. A workflow is the sequence
-      of tasks that you ask volunteers to do, and subjects are the things (usually images)
-      that volunteers do those tasks on.
+      **Start building:** Now you're in the Project Builder itself. This is where the magic happens. On the left-hand side, you've got your main menus: Project, Workflow, and Subjects. These are terms you'll see a lot, and they have specific meanings in the Zooniverse.  Project is pretty self-explanatory; Galaxy Zoo, Penguin Watch, and of course, Kitteh Zoo, are all examples of Zooniverse projects that you could build using the project builder. A workflow is the sequence of tasks that you ask volunteers to do, and subjects are the things (usually images) that volunteers do those tasks on.

       [![Project details editor screenshot](./assets/how-to-lab/how-to-3.jpg)](./assets/how-to-lab/how-to-3.jpg)

       ### Define your project.

-      The first thing you'll want to do is fill in some basic information about your project on the
-      Project Details page. Just click and type in the relevant boxes. We've added a short description
-      that will be formatted using the markdown language. The avatar and background image for
-      Kitteh Zoo are in this folder. Download these images to your computer. Now add these images by
-      dragging and dropping or by right clicking on the relevant boxes (like in the image above).
-      You can come back and add more details at any time while building your project.
+      The first thing you'll want to do is fill in some basic information about your project on the Project Details page. Just click and type in the relevant boxes. We've added a short description that will be formatted using the markdown language. The avatar and background image for Kitteh Zoo are in this folder. Download these images to your computer. Now add these images by dragging and dropping or by right clicking on the relevant boxes (like in the image above). You can come back and add more details at any time while building your project.

       ### Building a workflow

-      This is where you build the tasks that volunteers actually do. When you first get to this page,
-      you'll see there is a sample task (specifically a question) already in place.
+      This is where you build the tasks that volunteers actually do. When you first get to this page, you'll see there is a sample task (specifically a question) already in place.

       [![Project workflow editor screenshot](./assets/how-to-lab/how-to-4.jpg)](./assets/how-to-lab/how-to-4.jpg)

@@ -52,9 +38,7 @@

       [![Project workflow example](./assets/how-to-lab/how-to-5.jpg)](./assets/how-to-lab/how-to-5.jpg)

-      We'll start by replacing the sample text with our question, which asks people how many cats are in the
-      image. We add more answers using the "+" button under the "Yes" answer. Use the screenshot below to fill
-      in the workflow details (you may need to zoom in!)
+      We'll start by replacing the sample text with our question, which asks people how many cats are in the image. We add more answers using the "+" button under the "Yes" answer. Use the screenshot below to fill in the workflow details (you may need to zoom in!)

       [![Project workflow editing screenshot](./assets/how-to-lab/how-to-6.jpg)](./assets/how-to-lab/how-to-6.jpg)

@@ -79,92 +63,53 @@

           ![A sample of things that are not cats.](http://zooniverse-resources.s3.amazonaws.com/bigblogfiles/cat_demo/notcats.png)

-      We can set subsequent tasks to depend on the answer to this question. Right now we haven't made any other tasks,
-      so the only option is "End of Classification." Once we create more tasks, we'll go back through and link them.
-      Note that this question is _required_ (people can't move on until they've answered it) and only one answer is allowed.
-
-      Now we want to draw circles around the cat's faces and mark a point on their tails. Why? Because we can.
-      (For your own project you'd obviously want to think carefully about the reasons for adding tasks to a workflow,
-      and what you want to get from the answers/marks.)
+      We can set subsequent tasks to depend on the answer to this question. Right now we haven't made any other tasks, so the only option is "End of Classification." Once we create more tasks, we'll go back through and link them. Note that this question is _required_ (people can't move on until they've answered it) and only one answer is allowed.
+
+      Now we want to draw circles around the cat's faces and mark a point on their tails. Why? Because we can. (For your own project you'd obviously want to think carefully about the reasons for adding tasks to a workflow, and what you want to get from the answers/marks.)

       [![Project workflow task editing screenshot](./assets/how-to-lab/how-to-7.jpg)](./assets/how-to-lab/how-to-7.jpg)

-      So under the _Task_ list, we'll click on **drawing**. We're asking folks to draw (with ellipses) around the cats'
-      faces, as well as mark their tail tips with a point. We've changed the color on the Cattail points so they
-      stand out more too. As usual, the main text gives people basic instructions on what we want, and the help
-      text provides some more explanation on how to do the task.
-
-      In addition to marking all the cat faces, we want to know just how cute they are. So every time someone
-      marks a cat-face, we've added a pop-up question to ask just that. Add this question by clicking on
-      the _sub-tasks_ button below the _Type_ and _Color_ task specifications.
+      So under the _Task_ list, we'll click on **drawing**. We're asking folks to draw (with ellipses) around the cats' faces, as well as mark their tail tips with a point. We've changed the color on the Cattail points so they stand out more too. As usual, the main text gives people basic instructions on what we want, and the help text provides some more explanation on how to do the task.
+
+      In addition to marking all the cat faces, we want to know just how cute they are. So every time someone marks a cat-face, we've added a pop-up question to ask just that. Add this question by clicking on the _sub-tasks_ button below the _Type_ and _Color_ task specifications.

       [![Project workflow task details editing screenshot](./assets/how-to-lab/how-to-8.jpg)](./assets/how-to-lab/how-to-8.jpg)

-      When building your own project, you can combine any number of tasks in any order. You can start with a
-      drawing task instead of a question. You can add sub-tasks for any drawing tool you make.
-
-      In general, keep in mind that people are more likely to complete more classifications if the
-      workflow is short and simple. Try to keep the workflow as simple as possible to achieve your
-      research goals, and definitely try to only request tasks that cannot be accurately accomplished by automated methods.
+      When building your own project, you can combine any number of tasks in any order. You can start with a drawing task instead of a question. You can add sub-tasks for any drawing tool you make.
+
+      In general, keep in mind that people are more likely to complete more classifications if the workflow is short and simple. Try to keep the workflow as simple as possible to achieve your research goals, and definitely try to only request tasks that cannot be accurately accomplished by automated methods.

       ### Linking the workflow together

-      Now that all the tasks have been created, we've got to string them together by specifying what happens _next_.
-      Right now, this means you kind of need to work backwards. The drawing task is the last task in this workflow,
-      so we'll leave the "Next Task" button as the default "end of classification." But we'll have to go back to our first question.
-
-      The first question, "How many cats are in this image?" only allows one answer, so you can specify the next task depending
-      on the answer.  If folks say "None" for the number of cats, the classification ends. But if they say there's at least one
-      cat, then they go on to the next question.
+      Now that all the tasks have been created, we've got to string them together by specifying what happens _next_. Right now, this means you kind of need to work backwards. The drawing task is the last task in this workflow, so we'll leave the "Next Task" button as the default "end of classification." But we'll have to go back to our first question.
+
+      The first question, "How many cats are in this image?" only allows one answer, so you can specify the next task depending on the answer.  If folks say "None" for the number of cats, the classification ends. But if they say there's at least one cat, then they go on to the next question.

       [![Project workflow task editing screenshot](./assets/how-to-lab/how-to-9.jpg)](./assets/how-to-lab/how-to-9.jpg)

       ### Upload subjects

-      To really get started building a project, you need images to work with. Normally you would add your own
-      images by clicking on the "New Subject Set" button on the left hand side of the screen. This is one of
-      the trickier steps in project creation -- for the purposes of this tutorial you can simply copy the
-      Kitteh Zoo subject set, but check out the next section "Uploading subjects -- the nitty gritty" if you
-      want to practice the full approach. To do this go to the workflow you created and under the
-      _associated subject_ set section click on _add an example subject set_. You should now see the 'kittehs' subject set selected.
+      To really get started building a project, you need images to work with. Normally you would add your own images by clicking on the "New Subject Set" button on the left hand side of the screen. This is one of the trickier steps in project creation -- for the purposes of this tutorial you can simply copy the Kitteh Zoo subject set, but check out the next section "Uploading subjects -- the nitty gritty" if you want to practice the full approach. To do this go to the workflow you created and under the _associated subject_ set section click on _add an example subject set_. You should now see the 'kittehs' subject set selected.

       [![Project workflow task editing screenshot](./assets/how-to-lab/how-to-10.jpg)](./assets/how-to-lab/how-to-10.jpg)

       **CONGRATULATIONS!**

-      You should have successfully created Kitteh Zoo! To view it, got back to the _Build a Project_ page
-      (by clicking the button in the top right of the page) and then click on the view button next to the
-      new project you have just made.
+      You should have successfully created Kitteh Zoo! To view it, got back to the _Build a Project_ page (by clicking the button in the top right of the page) and then click on the view button next to the new project you have just made.

       [![Project list screenshot](./assets/how-to-lab/how-to-11.jpg)](./assets/how-to-lab/how-to-11.jpg)

       ### Uploading subjects - the Nitty Gritty

-      When you actually build your own project, there won't be an example set of images already loaded for you.
-      Get started uploading a set of subjects for your project by clicking on the "New Subject Set" button on
-      the left hand side of the screen. That will bring you to the _Subject Uploader_.
-
-      It's easiest if you have all of your images in a single folder along with a manifest file, and you will
-      upload both at the same time. The manifest file lists all the images we want to upload and links each
-      image to additional information, also called metadata, such as date, time, and photographer. There is more
-      information about the manifest in the "Details" section below, but for now you can see an example manifest
-      file in the ["Kitteh" zip file](https://data.zooniverse.org/tutorial/kitteh_zoo.zip).
-
-      Click on the "Upload Subjects" box and navigate to the "Kitteh" folder you downloaded. The easiest thing
-      to do is simply hit _cmd + a_ (on Mac, or _ctrl + a_ on Windows) to select everything in that folder.
-      Note that the subject uploader ignores the excel file and the other folders. Click "open" to select
-      those images and the manifest file for upload. The project uploader now indicates that the
-      "Cat project manifest.csv" has 29 subjects for upload.
-
-      Click "Upload" to start the process. It might take a little while, but when everything is uploaded,
-      you'll see a list of all of the subjects. The numbers to the left are unique identifiers for each subject,
-      and the icons to the right let you preview or delete each subject.
-
-      Subject sets can be pretty powerful, and sometimes complex. You can have a single subject set that you
-      add to over time, or have multiple subject sets, say, from different years or places. You can have
-      different subject sets for different workflows, but you don't have to. You can even have multiple
-      images in a given subject. For more details and advice on creating and structuring subject sets
-      and associated manifests, check out the details section below.
+      When you actually build your own project, there won't be an example set of images already loaded for you. Get started uploading a set of subjects for your project by clicking on the "New Subject Set" button on the left hand side of the screen. That will bring you to the _Subject Uploader_.
+
+      It's easiest if you have all of your images in a single folder along with a manifest file, and you will upload both at the same time. The manifest file lists all the images we want to upload and links each  image to additional information, also called metadata, such as date, time, and photographer. There is more information about the manifest in the "Details" section below, but for now you can see an example manifest file in the ["Kitteh" zip file](https://data.zooniverse.org/tutorial/kitteh_zoo.zip).
+
+      Click on the "Upload Subjects" box and navigate to the "Kitteh" folder you downloaded. The easiest thing to do is simply hit _cmd + a_ (on Mac, or _ctrl + a_ on Windows) to select everything in that folder. Note that the subject uploader ignores the excel file and the other folders. Click "open" to select those images and the manifest file for upload. The project uploader now indicates that the "Cat project manifest.csv" has 29 subjects for upload.
+
+      Click "Upload" to start the process. It might take a little while, but when everything is uploaded, you'll see a list of all of the subjects. The numbers to the left are unique identifiers for each subject, and the icons to the right let you preview or delete each subject.
+
+      Subject sets can be pretty powerful, and sometimes complex. You can have a single subject set that you add to over time, or have multiple subject sets, say, from different years or places. You can have different subject sets for different workflows, but you don't have to. You can even have multiple images in a given subject. For more details and advice on creating and structuring subject sets and associated manifests, check out the details section below.

       ### PROJECT BUILDER'S MANUAL – THE DETAILS


even cleaner indents

even cleaner indents

--- js/outer/about.coffee
+++ js/outer/about.coffee
@@ -8,8 +8,10 @@
       email: "paul@circleci.com"
       photo: "paul"
       visible: true
-      bio: ["You might not know it by looking at him, but Paul is a wicked card shark. When he's not making his poker buddies cry, he seeks out mogul-laced ski trails to hurl himself down, and within the first five minutes of meeting him he'll probably tell you the story about how he mostly landed a backflip on his skis. Lest his ego get too big, though, we can always remind him of the time he failed at a YCombinator startup, and he'll go bury his emotions in the sweet sweet taste of artisan chocolate--or any chocolate, really. ",
-        "Paul's been interviewed by the Wall Street Journal, and one of his proudest achievements was giving a Google Tech Talk on compilers and programming languages. He wrote phc, an open source PHP compiler, then did his PhD on compilers and static analysis in Dublin. Before working on the Firefox Javascript engine here in the Bay Area, he wrote stefon, an asset pipeline for Clojure. It pains Paul to see so much time wasted due to inadequate developer tools, so he approaches Continuous Integration from the perspective of developer productivity. He loves to focus on building the Circle product, but, as he'll tell you while he's walloping you at table tennis, he also has to take care of the business side of things, too. Then, while you're distracted by his work talk, he'll hit you with the smash that nearly got him booted from the San Francisco Table Tennis league, and you'll be done for."]
+      bio: [
+        "You might not know it by looking at him, but Paul is a wicked card shark. When he's not making his poker buddies cry, he seeks out mogul-laced ski trails to hurl himself down, and within the first five minutes of meeting him he'll probably tell you the story about how he mostly landed a backflip on his skis. Lest his ego get too big, though, we can always remind him of the time he failed at a YCombinator startup, and he'll go bury his emotions in the sweet sweet taste of artisan chocolate--or any chocolate, really.",
+        "Paul's been interviewed by the Wall Street Journal, and one of his proudest achievements was giving a Google Tech Talk on compilers and programming languages. He wrote phc, an open source PHP compiler, then did his PhD on compilers and static analysis in Dublin. Before working on the Firefox Javascript engine here in the Bay Area, he wrote stefon, an asset pipeline for Clojure. It pains Paul to see so much time wasted due to inadequate developer tools, so he approaches Continuous Integration from the perspective of developer productivity. He loves to focus on building the Circle product, but, as he'll tell you while he's walloping you at table tennis, he also has to take care of the business side of things, too. Then, while you're distracted by his work talk, he'll hit you with the smash that nearly got him booted from the San Francisco Table Tennis league, and you'll be done for."
+      ]
     ,
       name: "Allen Rohner"
       photo: "allen"
@@ -18,8 +20,10 @@
       github: "arohner"
       twitter: "arohner"
       email: "allen@circleci.com"
-      bio: ["Allen loves Ultimate Frisbee: he's played in the snow, on the beach, on Thanksgiving Day, New Year's Eve, and New Year's Day, and he regularly plays in 110 degree August heat. It helps take his mind off being rejected from YCombinator 4 different times, though he did get a book from Alexis Ohanian as a consolation prize.  His future plans include the tacocopter, the burrito bomber, and the mosquito laser. When Allen's not coding, he spends his time making his own pizza dough and brewing his own beer. He'd make his own whiskey, too, if he wanted to wait 10 years for a drink.",
-        "Back in the day, Allen started work on his own self-hosting, native code lisp, and then the clouds parted and he discovered Clojure. Since then, he's become a Clojure contributor, and has commits in clojure.core, contrib, lein, ring, compojure, noir, and about a dozen more libraries. He finished SICP, and wrote Scriptjure, a Clojure to Javascript compiler, 2 years before ClojureScript. Other devs used his work as a basis for Stevedore, which he's happy to say is used in production every day at Circle. In addition to co-founding Circle as a means to bring Continuous Deployment to the masses, he looks forward to building tools that help debug test failures by anticipating common problems. He currently deploys six times a day, and more on weekends."]
+      bio: [
+        "Allen loves Ultimate Frisbee: he's played in the snow, on the beach, on Thanksgiving Day, New Year's Eve, and New Year's Day, and he regularly plays in 110 degree August heat. It helps take his mind off being rejected from YCombinator 4 different times, though he did get a book from Alexis Ohanian as a consolation prize.  His future plans include the tacocopter, the burrito bomber, and the mosquito laser. When Allen's not coding, he spends his time making his own pizza dough and brewing his own beer. He'd make his own whiskey, too, if he wanted to wait 10 years for a drink.",
+        "Back in the day, Allen started work on his own self-hosting, native code lisp, and then the clouds parted and he discovered Clojure. Since then, he's become a Clojure contributor, and has commits in clojure.core, contrib, lein, ring, compojure, noir, and about a dozen more libraries. He finished SICP, and wrote Scriptjure, a Clojure to Javascript compiler, 2 years before ClojureScript. Other devs used his work as a basis for Stevedore, which he's happy to say is used in production every day at Circle. In addition to co-founding Circle as a means to bring Continuous Deployment to the masses, he looks forward to building tools that help debug test failures by anticipating common problems. He currently deploys six times a day, and more on weekends."
+      ]
     ,
       name: "David Lowe"
       visible: true
@@ -28,8 +32,10 @@
       twitter: "j_david_lowe"
       email: "dlowe@circleci.com"
       photo: "dlowe"
-      bio: ["David is responsible for all of the bugs. He wrote his first buggy code in BASIC with a pencil and paper, and he's been getting better at it ever since. He keeps a 1/4 acre garden and eats almost entirely home-grown veggies during the growing season.  Helping to balance out the numerous times that he's lost at chess, David has won the IOCCC on 5 separate occasions. In a past life, he wrote MTA software which at one time was sending about 1% of the internet's email.",
-        "Besides being invited to speak at the Frozen Perl Conference, OIT's CSET Department, and SOU's CS Department, David co-founded the SF perl user group, the southern Oregon geek group, and the Rogue hack lab hackerspace. He drank the automated testing kool-aid years ago, and after introducing and championing (and constantly fiddling with) continuous integration tools at his last N jobs, he came to Circle, and is particularly keen on building and scaling things which would be impossible to justify for any single small company where CI isn't what they do. He splits his time between introducing new bugs, telling kids to get off his lawn, and baking pies. His current running total is roughly 1500 delicious pastry concoctions."]
+      bio: [
+        "David is responsible for all of the bugs. He wrote his first buggy code in BASIC with a pencil and paper, and he's been getting better at it ever since. He keeps a 1/4 acre garden and eats almost entirely home-grown veggies during the growing season.  Helping to balance out the numerous times that he's lost at chess, David has won the IOCCC on 5 separate occasions. In a past life, he wrote MTA software which at one time was sending about 1% of the internet's email.",
+        "Besides being invited to speak at the Frozen Perl Conference, OIT's CSET Department, and SOU's CS Department, David co-founded the SF perl user group, the southern Oregon geek group, and the Rogue hack lab hackerspace. He drank the automated testing kool-aid years ago, and after introducing and championing (and constantly fiddling with) continuous integration tools at his last N jobs, he came to Circle, and is particularly keen on building and scaling things which would be impossible to justify for any single small company where CI isn't what they do. He splits his time between introducing new bugs, telling kids to get off his lawn, and baking pies. His current running total is roughly 1500 delicious pastry concoctions."
+      ]
     ,
       name: "Jenneviere Villegas"
       photo: "jenn"
@@ -38,8 +44,10 @@
       github: "jenneviere"
       twitter: "jenneviere"
       email: "jenneviere@circleci.com"
-      bio: ["Jenneviere moved to the Bay Area in 2010 after spending a few months there, singing and dancing, as the lead in a rock opera. She then worked as an extra on the movie Twixt, and when she realized that Mr. Coppola wasn't going to cast her as the lead in his next film, she began drowning her sorrows in hoppy IPAs and the soothing click of knitting needles. She took the black and works the Gate at That Thing In The Desert, and has only written a single line of code, ever.",
-        "Drawing from her many years' experience in her previous position as the Customer Amazement Specialist, Operations Manager, Returns Siren, and Retail Store Maven for a certain large utility kilt manufacturer, Jenneviere brings her skills as a professional pottymouth, dabbler in inappropriate and snarky humor, and cat wrangling to the team at Circle, and spends most of her time trying to keep everyone well-groomed and hairball free."]
+      bio: [
+        "Jenneviere moved to the Bay Area in 2010 after spending a few months there, singing and dancing, as the lead in a rock opera. She then worked as an extra on the movie Twixt, and when she realized that Mr. Coppola wasn't going to cast her as the lead in his next film, she began drowning her sorrows in hoppy IPAs and the soothing click of knitting needles. She took the black and works the Gate at That Thing In The Desert, and has only written a single line of code, ever.",
+        "Drawing from her many years' experience in her previous position as the Customer Amazement Specialist, Operations Manager, Returns Siren, and Retail Store Maven for a certain large utility kilt manufacturer, Jenneviere brings her skills as a professional pottymouth, dabbler in inappropriate and snarky humor, and cat wrangling to the team at Circle, and spends most of her time trying to keep everyone well-groomed and hairball free."
+      ]
     ,
       name: "Daniel Woelfel"
       photo: "daniel"
@@ -48,8 +56,10 @@
       github: "dwwoelfel"
       twitter: "DanielWoelfel"
       email: "daniel@circleci.com"
-      bio: ["Despite all our coercions, promises, bribes, and outright threats for nearly a year, it took us putting Daniel in a time-out and forcing him to type things about himself to get a bio out of him. He doesn't really have any hobbies: if he's doing something, it's usually either reading romantic fiction (not to be confused with romance novels!), coding, or binge-watching episodes of :tv-show-he-just-discovered-that-is-so-awesome-he-can't-put-it-down. One of his first programming projects was an app written in Python that creates an exact replica of an image using only html tables. It's been almost 2 years since he last touched the thing, and it's still cranking out tables. He's only been rejected from YCombinator 3 times, which is empirically below the threshold of consolation prizes.",
-        "Daniel has a bachelors degree in Math from Texas A&M. He has written an app to graph Kickstarter projects, and was a customer (the 155th!) before he joined the Circle team. He looks forward to helping increase the 'wow, I can do something cool with this!' feeling that users get when they use Circle, and has been tinkering with a few things recently to make that possible--a native websockets library so that our output feels more like you're directly connected to a terminal and hooking in something like jquery.terminal so that you actually can be directly connected to a terminal. He currently helps feed his movie habit by helping organize weekly movie nights at the office. Next up: Fight Club."]
+      bio: [
+        "Despite all our coercions, promises, bribes, and outright threats for nearly a year, it took us putting Daniel in a time-out and forcing him to type things about himself to get a bio out of him. He doesn't really have any hobbies: if he's doing something, it's usually either reading romantic fiction (not to be confused with romance novels!), coding, or binge-watching episodes of tv-show-he-just-discovered-that-is-so-awesome-he-can't-put-it-down. One of his first programming projects was an app written in Python that creates an exact replica of an image using only html tables. It's been almost 2 years since he last touched the thing, and it's still cranking out tables. He's only been rejected from YCombinator 3 times, which is empirically below the threshold of consolation prizes.",
+        "Daniel has a bachelors degree in Math from Texas A&M. He has written an app to graph Kickstarter projects, and was a customer (the 155th!) before he joined the Circle team. He looks forward to helping increase the 'wow, I can do something cool with this!' feeling that users get when they use Circle, and has been tinkering with a few things recently to make that possible--a native websockets library so that our output feels more like you're directly connected to a terminal and hooking in something like jquery.terminal so that you actually can be directly connected to a terminal. He currently helps feed his movie habit by helping organize weekly movie nights at the office. Next up: Fight Club."
+      ]
     ,
       name: "Mahmood Ali"
       photo: "mahmood"
@@ -58,8 +68,10 @@
       github: "notnoopci"
       twitter: "notnoop"
       email: "mahmood@circleci.com"
-      bio: ["Mahmood loves car camping, which might explain why he also likes moving as much as he does. He and his family have moved 6 times in the last 6 years, though his baby boy has been completely lazy and hasn't offered to carry a single box yet. In between relocation to new abodes, Mahmood spends the nicer Boston weather exploring bike trails and learning computer languages. He's determined that his son will know Java before he can talk.",
-        "During his time at MIT, Mahmood spoke at a few Java research conferences including Devoxx, and has pushed code to Java 8--and Java 7--compilers. He's been a speaker at OOPSLA/Splash, and is active in the open-source community. He enjoys being reminded of his in-production projects, like java-apns, every time he gets support tickets. He acknowledges his own fallibility, and aims to help the Circle team continue to bring reliable test automation to all developers. His Achilles Heel is his pair of permanently underachieving glasses."]
+      bio: [
+        "Mahmood loves car camping, which might explain why he also likes moving as much as he does. He and his family have moved 6 times in the last 6 years, though his baby boy has been completely lazy and hasn't offered to carry a single box yet. In between relocation to new abodes, Mahmood spends the nicer Boston weather exploring bike trails and learning computer languages. He's determined that his son will know Java before he can talk.",
+        "During his time at MIT, Mahmood spoke at a few Java research conferences including Devoxx, and has pushed code to Java 8--and Java 7--compilers. He's been a speaker at OOPSLA/Splash, and is active in the open-source community. He enjoys being reminded of his in-production projects, like java-apns, every time he gets support tickets. He acknowledges his own fallibility, and aims to help the Circle team continue to bring reliable test automation to all developers. His Achilles Heel is his pair of permanently underachieving glasses."
+      ]
     ,
       name: "Gordon Syme"
       photo: "gordon"
@@ -68,8 +80,10 @@
       github: "gordonsyme"
       twitter: "gordon_syme"
       email: "gordon@circleci.com"
-      bio: ["Gordon has been racing dinghies right through the winter off the coast of Ireland for the past 16-odd years, and as his oft-showcased cabinet of prize coffee mugs will tell you, he's damn good at it. When he's not busy bludgeoning a piano with his ham-fisted fingers, he spends his free time rolling down mountains on a bicycle… sometimes even the right way up. In his down time, he's a board game shark, though you won't catch him trying to buy Boardwalk or get out of jail free.",
-        "Gordon joins the team after spending significant time at Amazon building tools to monitor the entire network (he built their DWDM monitoring from the ground up). He also built a JVM bytecode recompiler to enable running applications on a clustered VM without needing re-programming effort, and has grand plans for how he's going to make waves at Circle. We're pretty sure they include company Power Grid tournaments."]
+      bio: [
+        "Gordon has been racing dinghies right through the winter off the coast of Ireland for the past 16-odd years, and as his oft-showcased cabinet of prize coffee mugs will tell you, he's damn good at it. When he's not busy bludgeoning a piano with his ham-fisted fingers, he spends his free time rolling down mountains on a bicycle… sometimes even the right way up. In his down time, he's a board game shark, though you won't catch him trying to buy Boardwalk or get out of jail free.",
+        "Gordon joins the team after spending significant time at Amazon building tools to monitor the entire network (he built their DWDM monitoring from the ground up). He also built a JVM bytecode recompiler to enable running applications on a clustered VM without needing re-programming effort, and has grand plans for how he's going to make waves at Circle. We're pretty sure they include company Power Grid tournaments."
+      ]
     ,
       name: "Danny King"
       photo: "danny"
@@ -78,6 +92,8 @@
       github: "dannykingme"
       twitter: "dannykingme"
       email: "danny@circleci.com"
-      bio: ["Danny (not to be confused with Daniel) has batted a .500 baseball season with zero strikeouts and once took 2nd place at a poker tournament at the Mirage in Vegas. When he was 15 he, along with two of his paintball teammates, won a pickup paintball game against the three top-rated players from the number one team in the world, though he reckons this is only arguably impressive.  In the world of virtual sports, Danny has scored over a million points in Tony Hawk's Pro Skater, and has racked up massive karma points by designing a website--pro bono--for a non-profit organization that helps provide activity bags to hospitalized children. He was selected for a reality TV show on Fox but he turned it down (they're evil).",
-        "Danny finished high school at the young age of 16.  Two years after that, he cashed his first design freelance check. His past clients include Verizon, Lego, The Tribune, and Tyson Foods, and he has four individual 5-star rated projects in the Age of Empires design community. Not only does he design, but he's learned a bit of code, too, so he can build the things he designs, and his past projects include writing a tool that auto-hides Google Map's UI with simple CSS. He comes to Circle bursting with fresh ideas (like the new logo!) and can be found manipulating pixels with his hood securely in the Up position."]
+      bio: [
+        "Danny (not to be confused with Daniel) has batted a .500 baseball season with zero strikeouts and once took 2nd place at a poker tournament at the Mirage in Vegas. When he was 15 he, along with two of his paintball teammates, won a pickup paintball game against the three top-rated players from the number one team in the world, though he reckons this is only arguably impressive.  In the world of virtual sports, Danny has scored over a million points in Tony Hawk's Pro Skater, and has racked up massive karma points by designing a website--pro bono--for a non-profit organization that helps provide activity bags to hospitalized children. He was selected for a reality TV show on Fox but he turned it down (they're evil).",
+        "Danny finished high school at the young age of 16.  Two years after that, he cashed his first design freelance check. His past clients include Verizon, Lego, The Tribune, and Tyson Foods, and he has four individual 5-star rated projects in the Age of Empires design community. Not only does he design, but he's learned a bit of code, too, so he can build the things he designs, and his past projects include writing a tool that auto-hides Google Map's UI with simple CSS. He comes to Circle bursting with fresh ideas (like the new logo!) and can be found manipulating pixels with his hood securely in the Up position."
+      ]
     ]


Use the population data in the bubble plot

--- app/scripts/dcplotsctrl.coffee
+++ app/scripts/dcplotsctrl.coffee
@@ -1,6 +1,6 @@
 angular.module('taarifaWaterpointsApp')

-  .controller 'DCPlotsCtrl', ($scope, $http, $modal) ->
+  .controller 'DCPlotsCtrl', ($scope, $http, $modal, populationData) ->
     $scope.gridsterOpts =
       margins: [10, 10]
       columns: 12
@@ -36,6 +36,7 @@
       statusPerManagement: { sizeX: 6, sizeY: 4, row: 21, col: 0, title: "Functionality by Management" }
       statusPerExtraction: { sizeX: 6, sizeY: 4, row: 21, col: 6, title: "Functionality by Extraction" }

+    # FIXME: move to a factory/service
     modalSpinner = null
     $scope.openSpinner = () ->
       modalSpinner = $modal.open
@@ -48,6 +49,7 @@

     dimensions = []
     xfilter = null
+    popData = null

     # Called when the tab is activated for the first time
     # Has to be done on tab activation for else the charts can not pickup
@@ -75,7 +77,10 @@
             # only keep the renderlet once
             dc.renderlet null

-          setupCharts $scope.region
+          # ensure we have the population data
+          populationData.then (data) ->
+            popData = data
+            setupCharts $scope.region

     # what value to use for year 0
     YEAR_ZERO=1950
@@ -317,7 +322,7 @@
           .keyAccessor(keyAcc)
           .valueAccessor(valueAcc)
           .radiusValueAccessor(radiusAcc)
-          .maxBubbleRelativeSize(0.1)
+          .maxBubbleRelativeSize(0.06)
           .x(d3.scale.linear().domain(d3.extent(group.all(),keyAcc)))
           .y(d3.scale.linear().domain(d3.extent(group.all(),valueAcc)))
           .r(d3.scale.linear().domain(d3.extent(group.all(),radiusAcc)))
@@ -334,7 +339,7 @@
             d.key +
              "\nAverage payment: " + valueAcc(d).toPrecision(4) +
              "\n% Functional: " + keyAcc(d).toPrecision(4) +
-             "\nPopulation served: " + radiusAcc(d))
+             "\nPopulation: " + radiusAcc(d))
           .renderTitle(true)

       removeEmptyGroups = (group) ->
@@ -428,25 +433,34 @@
         res

       reduceCostStatus = (group) ->
+        isFunc = (x) -> x.status_group == "functional"
         res = group.reduce(\
           ((p, v) ->
             ++p.count
             p.total += v.amount_tsh
-            p.pop += v.population
-            p.numFun += (v.status_group == "functional") ? 1 : 0
+            p.pop_served_fun += if isFunc(v) then v.population else 0
+            p.numFun += if isFunc(v) then 1 else 0
             p.avgCost = p.total / p.count
             p.percFun = p.numFun / p.count * 100
             p),
           ((p, v) ->
             --p.count
             p.total -= v.amount_tsh
-            p.pop -= v.population
-            p.numFun -= (v.status_group == "functional") ? 1 : 0
+            p.pop_served_fun -= if isFunc(v) then v.population else 0
+            p.numFun -= if isFunc(v) then 1 else 0
             p.avgCost = (p.count) ? p.total / p.count * 1 : 0
             p.percFun = (p.count) ? p.numFun / p.count * 100 : 0
             p),
           (() ->
-            count: 0, total: 0, pop: 0, percFun: 0, numFun: 0, avgCost: 0))
+            count: 0, total: 0, pop_served_fun: 0, pop: 0, popReach: 0, percFun: 0, numFun: 0, avgCost: 0))
+
+        res.all().forEach((d) ->
+          # Lookup the population of the ward
+          pop = popData.lookup(null,null,d.key)
+          if pop > 0
+            d.value.pop = pop
+            d.value.popReach = d.value.pop_served_fun / d.value.pop * 100)
+
         res

       reduceAvg = (group, fieldAcc) ->


Removed debug info

Removed debug info

--- app/js/publisher_poc/nu.nl.coffee
+++ app/js/publisher_poc/nu.nl.coffee
@@ -16,7 +16,6 @@
   factlink_publisher.addFactlinkButtons()

   FACTLINK.on 'factlink.factsLoaded', (facts) ->
-    console.info "FACTS: ", facts
     ui = new UI
     ui.updateFactsCount facts.length
     ui.bindActions()


Properly serialize env var

Properly serialize env var

--- app/serializers/env-var.coffee
+++ app/serializers/env-var.coffee
@@ -9,4 +9,8 @@
   serialize: (snapshot, options) ->
     return { env_var: this._super(snapshot, options) }

+  normalizeSingleResponse: (store, primaryModelClass, payload, id, requestType) ->
+    payload = payload.env_var;
+    return @_super(store, primaryModelClass, payload, id, requestType)
+
 `export default Serializer`


fix radio to make mock promise cancel on close

--- app/scripts/services/radio.coffee
+++ app/scripts/services/radio.coffee
@@ -20,6 +20,7 @@

     # fake radio event sent every 100 ms
     mockEnabled = false
+    mockPromise = undefined
     mockRadio = ->
       now = new Date().getTime()
       num = Math.random()
@@ -35,11 +36,12 @@
       enableMock: (millis = 100) ->
         unless mockEnabled
           mockEnabled = true
-          $interval(mockRadio, millis)
+          mockPromise = $interval(mockRadio, millis)
       initialized: ->
         return _init
       close: ->
         _init = false
+        $interval.cancel(mockPromise)
         mockEnabled = false
         callbacks = {}
         return true


Change path and filename

Change path and filename

--- app/templates/Gruntfile.coffee
+++ app/templates/Gruntfile.coffee
@@ -150,18 +150,20 @@
           csslintrc: '.csslintrc'

     csscomb:
-      files:
+      dynamic_map:
+        expand: true
         src: [
           '../htdocs/**/*.css'
           '!../htdocs/common/css/extras/*.css'
         ]
+        dest: '../htdocs/**/*.css'

     combine_mq:
       default_options:
         expand: true
-        cwd: '../htdocs/path/to/css/'
+        cwd: '../htdocs/common/css/'
         src: '*.css'
-        dest: '../htdocs/path/to/css/'
+        dest: '../htdocs/common/css/'

     cssmin:
       dist:
@@ -171,7 +173,7 @@
             '../htdocs/**/*.css'
             '!../htdocs/**/*.min.css'
           ]
-          ext: '.min.css'
+          ext: '.css'
         ]

     xmlsitemap:


Introduce uglify-save-license

Introduce uglify-save-license

--- app/templates/Gruntfile.coffee
+++ app/templates/Gruntfile.coffee
@@ -34,36 +34,14 @@

   JS_ROOT = "#{ SRC_ROOT }js/"

-  # Preserve banner/license comments certainly
-  isIncludedInBanner = do ->
-    prevCommentLine = 0
-
-    return (node, comment) ->
-      if comment.type is 'comment2' and
-      (/^\!|@preserve|@license|@cc_on/mi.test comment.value)
-        return true
-
-      # コメントが先頭行にあるか、先頭行から連なるコメントである場合、バナーであると判断する
-      result = comment.line <= 1 or comment.line is prevCommentLine + 1
-
-      # コメントがバナーに含まれる場合、そのコメントの行番号を保存する
-      # コメントがバナーではない場合、行番号の保存をリセットする
-      if result
-        prevCommentLine = comment.line
-      else
-        prevCommentLine = 0
-
-      return result
-
   grunt.initConfig
     bower:
       options:
         targetDir: "#{ DEST_ROOT }.tmp/bower_exports/"
         cleanTargetDir: true
+      install:
         bowerOptions:
           production: true
-
-      install: {}

     modernizr:
       devFile: 'remote'
@@ -167,11 +145,11 @@

     uglify:
       options:
-        preserveComments: isIncludedInBanner
+        preserveComments: require 'uglify-save-license'

       main:
         options:
-          banner: "/*! Copyright (c) 2013 #{ settings.author } | MIT License */"
+          banner: "/*! Copyright (c) 2014 #{ settings.author } | MIT License */"
           compress:
             global_defs:
               DEBUG: false
@@ -423,7 +401,6 @@
           grunt.file.write destPath, html
           console.log "File \"#{ destPath }\" created."

-
   # Remove 'width' and 'height' properties from SVG
   grunt.task.registerTask 'flexSVG', 'An internal task.', ->
     _cwd = "#{ SRC_ROOT }img/"
@@ -434,13 +411,6 @@
       if match?
         svgString = svgString.replace match, 'viewBox'
       grunt.file.write "#{ DEST_ROOT }.tmp/svg/#{ filepath }", svgString
-
-  grunt.task.registerTask 'addNoJekyll',
-    'Add .nojekyll if needed',
-    ->
-      if grunt.file.expand("#{ DEST_ROOT }**/_*").length > 0
-        grunt.file.write  "#{ DEST_ROOT }.nojekyll", ''
-        console.log "File \"#{ DEST_ROOT }.nojekyll\" created."

   grunt.task.registerTask 'postprocessCSS', ['autoprefixer', 'cssmin']


Ensure process.env.HOME not overriden on Linux in failing spec

--- spec/fs-plus-spec.coffee
+++ spec/fs-plus-spec.coffee
@@ -324,20 +324,18 @@

   describe ".getAppDataDirectory", ->
     originalPlatform = null
-    originalHome = null

     beforeEach ->
       originalPlatform = process.platform
-      originalHome = process.env.HOME

     afterEach ->
       Object.defineProperty process, 'platform', value: originalPlatform
-      Object.defineProperty process.env, 'HOME', value: originalHome
-
-    it "returns a Application Support path on Mac", ->
+
+    it "returns the Application Support path on Mac", ->
       Object.defineProperty process, 'platform', value: 'darwin'
-      Object.defineProperty process.env, 'HOME', value: path.join(path.sep, 'Users', 'Buzz') unless originalPlatform is 'darwin'
-      expect(fs.getAppDataDirectory()).toBe path.join(process.env.HOME, 'Library', 'Application Support')
+      unless process.env.HOME
+        Object.defineProperty process.env, 'HOME', value: path.join(path.sep, 'Users', 'Buzz')
+      expect(fs.getAppDataDirectory()).toBe path.join(fs.getHomeDirectory(), 'Library', 'Application Support')

     it "returns %AppData% on Windows", ->
       Object.defineProperty process, 'platform', value: 'win32'


:art: Fixed typo

:art: Fixed typo

--- spec/grammar-spec.coffee
+++ spec/grammar-spec.coffee
@@ -162,7 +162,7 @@
       expect(lines[4][0]).toEqual value: "~", scopes: ["source.perl", "string.quoted.other.q.perl", "punctuation.definition.string.end.perl"]
       expect(lines[4][1]).toEqual value: ";", scopes: ["source.perl"]

-  describe "tokenizes single quoting", ->
+  describe "tokenizes word quoting", ->
     it "qw(word)", ->
       {tokens} = grammar.tokenizeLine("qw(Aword Bword Cword);")
       expect(tokens[0]).toEqual value: "qw(", scopes: ["source.perl", "string.quoted.other.q-paren.perl", "punctuation.definition.string.begin.perl"]


Fix issue #10

Fix issue #10

--- app/views/data_settings.coffee
+++ app/views/data_settings.coffee
@@ -36,6 +36,9 @@
       extSources: Manager.get('sources').getSources()
       intSources: @intSources or []
       sourceType: @sourceType
+
+    if @sourceType is 'internal'
+      opts['source'] = @model.dataSource.get('source')

     if @selectedSource
       new_opts =


Add tests for ‘-’ (failing, partly due to bug in ‘k’)

And add a stub for the tests for ‘+’ and ‘enter’.

--- spec/motions-spec.coffee
+++ spec/motions-spec.coffee
@@ -530,6 +530,91 @@

       it "moves the cursor to the beginning of the line", ->
         expect(editor.getCursorScreenPosition()).toEqual [0,0]
+
+  describe "the - keybinding", ->
+    beforeEach ->
+      editor.setText("abcdefg\n  abc\n  abc\n")
+
+    describe "from the middle of a line", ->
+      beforeEach -> editor.setCursorScreenPosition([1, 4])
+
+      describe "as a motion", ->
+        beforeEach -> keydown('-')
+
+        it "moves the cursor to the first character of the previous line", ->
+          expect(editor.getCursorScreenPosition()).toEqual [0, 0]
+
+      describe "as a selection", ->
+        beforeEach ->
+          keydown('d')
+          keydown('-')
+
+        it 'deletes the current and previous line', ->
+          expect(editor.getText()).toBe "  abc\n"
+          expect(editor.getCursorScreenPosition()).toEqual [0, 4]
+
+    describe "from the first character of a line indented the same as the previous one", ->
+      beforeEach -> editor.setCursorScreenPosition([2, 2])
+
+      describe "as a motion", ->
+        beforeEach -> keydown('-')
+
+        it "moves to the first character of the previous line (directly above)", ->
+          expect(editor.getCursorScreenPosition()).toEqual [1, 2]
+
+      describe "as a selection", ->
+        beforeEach ->
+          keydown('d')
+          keydown('-')
+
+        it "selects to the first character of the previous line (directly above)", ->
+          expect(editor.getText()).toBe "abcdefg\n"
+          expect(editor.getCursorScreenPosition()).toEqual [0, 2]
+
+    describe "from the beginning of a line preceded by an indented line", ->
+      beforeEach -> editor.setCursorScreenPosition([2, 0])
+
+      describe "as a motion", ->
+        beforeEach -> keydown('-')
+
+        it "moves the cursor to the first character of the previous line", ->
+          expect(editor.getCursorScreenPosition()).toEqual [1, 2]
+
+      describe "as a selection", ->
+        beforeEach ->
+          keydown('d')
+          keydown('-')
+
+        it 'selects to the first character of the previous line', ->
+          expect(editor.getText()).toBe "abcdefg\n"
+          expect(editor.getCursorScreenPosition()).toEqual [0, 0]
+
+    describe "with a count", ->
+      beforeEach ->
+        editor.setText("1\n2\n3\n4\n5\n6\n")
+        editor.setCursorScreenPosition([4, 0])
+
+      describe "as a motion", ->
+        beforeEach ->
+          keydown('3')
+          keydown('-')
+
+        it "moves the cursor to the first character of that many lines previous", ->
+          expect(editor.getCursorScreenPosition()).toEqual [1, 0]
+
+      describe "as a selection", ->
+        beforeEach ->
+          keydown('d')
+          keydown('3')
+          keydown('-')
+
+        it 'deletes the current line plus that many previous lines', ->
+          expect(editor.getText()).toBe "1\n6\n"
+          expect(editor.getCursorScreenPosition()).toEqual [1, 0]
+
+  describe "the + keybinding (which should also be equivalent to the enter keybinding)", ->
+    # TODO copy the tests for the - keybinding into here, and then
+    #  change the values to expect downwards instead of upwards movement

   describe "the gg keybinding", ->
     beforeEach ->


Update zh-HANS.coffee

Update zh-HANS.coffee

--- app/locale/zh-HANS.coffee
+++ app/locale/zh-HANS.coffee
@@ -2148,8 +2148,8 @@
 #    for_loops_nested: "Nested For Loops"
 #    for_loops_range: "For Loops Range"
     functions: "函数"
-#    game_ai: "Game AI"
-#    game_goals: "Game Goals"
+    game_ai: "游戏AI"
+    game_goals: "游戏目标"
 #    game_spawn: "Game Spawn"
     graphics: "图形"
 #    graphs: "Graphs"


nav.admin/teacher i18n

nav.admin/teacher i18n

--- app/locale/zh-HANS.coffee
+++ app/locale/zh-HANS.coffee
@@ -232,8 +232,8 @@
     return_coco: "返回CodeCombat"
     classes: "我的课程"
     term_of_service: "服务条款"
-#    admin: "Admin"
-#    teacher: "Teacher"
+    admin: "管理员"
+    teacher: "教师"

   modal:
     close: "关闭"


Add Setting api

Add Setting api

--- app/models/setting.coffee
+++ app/models/setting.coffee
@@ -2,27 +2,43 @@
 List = require('models/list')

 class window.Setting extends Spine.Model
+
+  @extend @Local
+
   @configure 'Setting',
     'sort',
-    'username'
+    'username',
+    'theme'

   constructor: ->
     super
     @sort ?= yes

+  # Change a setting
+  # Setting.set("theme", "dark")
   @set: (key, value) =>
     @first().updateAttribute key, value
+    true

+  @toggle: (key) =>
+    @set key, !@get(key)
+
+  # Get a setting
+  # Setting.get("theme")
+  @get: (key) =>
+    @first()[key]
+
+  # Return sort mode
   @sortMode: =>
-    @first().sort
+    @get("sort")

+  # Check is user is pro
   @isPro: ->
     return true

-  @extend @Local
-
+  # Toggle sort
   @toggleSort: ->
-    @first().updateAttribute "sort", !@first().sort
+    @toggle "sort"
     @trigger "changeSort", List.current

 module.exports = Setting


add saving/saved messages

add saving/saved messages

--- app/scripts/editor.coffee
+++ app/scripts/editor.coffee
@@ -3,6 +3,8 @@
 	## PROPERTIES ##
 	titlePromptMessage: 'New title'
 	promptMessage: 'Type your article here'
+	savingMessage: 'Saving...'
+	savedMessage: 'Saved!'
 	status:
 		cmd: false
 		ctrl: false


rework dashboard loading process

rework dashboard loading process

--- src/services/dashboards/dashboards.svc.coffee
+++ src/services/dashboards/dashboards.svc.coffee
@@ -105,44 +105,35 @@
         _self.loadLocked=true

         if (needConfigurationLoad() || force)
-
           ImpacMainSvc.load(force).then(
             (success)->
               orgId = success.currentOrganization.id

-              # Retrieve dashboards
-              $http.get(ImpacRoutes.dashboards.index(orgId)).then(
-                (dashboards)->
-                  _self.setDashboards(dashboards.data).then(->
-                    _self.setCurrentDashboard()
-                    deferred.resolve(_self.config)
-                    $log.info("Impac! - DashboardsSvc: loaded (force=#{force})")
-
-                  ).finally( -> _self.loadLocked = false )
-                (error)->
+              # Retrieve dashboards with widgets and kpis settings
+              dashboardsPromise = $http.get(ImpacRoutes.dashboards.index(orgId)).then(
+                (response) ->
+                  _self.setDashboards(response.data).then(-> _self.setCurrentDashboard() )
+                (error) ->
                   $log.error("Impac! - DashboardsSvc: cannot retrieve dashboards list for org: #{orgId}")
-                  _self.loadLocked=false
+              )
+
+              # Retrieve widgets templates
+              widgetsTemplatesPromise = $http.get(widgetsTemplatesUrl()).then(
+                (response) ->
+                  _self.setWidgetsTemplates(response.data.widgets)
+                (error) ->
+                  $log.error("Impac! - DashboardsSvc: cannot retrieve widgets templates", ImpacRoutes.widgets.templates())
+              )
+
+              # Remove the lock and resolve the promise once both the dashboards and widgets templates are loaded
+              $q.all([dashboardsPromise, widgetsTemplatesPromise]).then(
+                (success) ->
+                  $log.info("Impac! - DashboardsSvc: loaded (force=#{force})")
+                  deferred.resolve(_self.config)
+                (errors) ->
                   deferred.reject(error)
               )
-
-              # Retrieve widgets templates
-              url =
-                if ImpacTheming.get().dhbWidgetsConfig.templates.defaultToFinancialYear
-                  fy_end_month = ImpacMainSvc.getFinancialYearEndMonth()
-                  "#{ImpacRoutes.widgets.templates()}?financial_year_end_month=#{fy_end_month}"
-                else
-                  ImpacRoutes.widgets.templates()
-
-              $http.get(url).then(
-                (response) ->
-                  _self.setWidgetsTemplates(response.data.widgets)
-                  _self.loadLocked = false
-
-                (error) ->
-                  $log.error("Impac! - DashboardsSvc: cannot retrieve widgets templates", ImpacRoutes.widgets.templates())
-                  _self.loadLocked = false
-                  deferred.reject(error)
-              )
+              .finally( -> _self.loadLocked = false )

             (error)->
               $log.error("Impac! - DashboardsSvc: cannot retrieve current organization")
@@ -163,6 +154,13 @@

       return deferred.promise

+
+    widgetsTemplatesUrl = ->
+      if ImpacTheming.get().dhbWidgetsConfig.templates.defaultToFinancialYear
+        fy_end_month = ImpacMainSvc.getFinancialYearEndMonth()
+        return "#{ImpacRoutes.widgets.templates()}?financial_year_end_month=#{fy_end_month}"
+      else
+        return ImpacRoutes.widgets.templates()

     setDefaultCurrentDashboard = ->
       if _self.config.dashboards? && _self.config.dashboards.length > 0


refactor

refactor

--- app/scripts/editor.coffee
+++ app/scripts/editor.coffee
@@ -1,10 +1,16 @@
+class Editor
+	update: ->
+		$('#debug').text $('#editor').html()
+	handleKeyUp: (e)->
+		console.log e.keyCode
+	init: ->
+		@update()
+
+		# binding
+		$('#editor').on 'keyup', (e)=>
+			@update()
+			@handleKeyUp(e)
+
 $(document).ready ->
-	update = ->
-		$('#debug').text $('#editor').html()
-
-	# init
-	update()
-
-	# binding
-	$('#editor').on 'keyup', ->
-		update()+	editor = new Editor
+	editor.init()


Improve test

Improve test

--- src/spec/integration/order-export.spec.coffee
+++ src/spec/integration/order-export.spec.coffee
@@ -47,6 +47,7 @@
   , 20000 # 20sec

   it 'should export as XML', (done) ->
+    @orderExport._exportOptions.fetchHours = 1
     @orderExport.run()
     .then (xmlOrders) =>
       expectedOrder = _.find xmlOrders, (o) => o.id is @order.id


Support optional fileId param

Support optional fileId param

Extend `_dataToSchema`, `write` and `load` methods to support optional `fileId` param

--- files.coffee
+++ files.coffee
@@ -1261,6 +1261,11 @@
           extension: data.extension
       _downloadRoute:  data._downloadRoute or @downloadRoute
       _collectionName: data._collectionName or @collectionName
+
+    #Optional fileId
+    if data.fileId
+       ds._id = data.fileId;
+
     @_updateFileTypes ds
     ds._storagePath = data._storagePath or @storagePath(_.extend(data, ds))
     return ds
@@ -1275,6 +1280,7 @@
   @param {String} opts.type - File mime-type
   @param {Object} opts.meta - File additional meta-data
   @param {String} opts.userId - UserId, default *null*
+  @param {String} opts.fileId - _id, default *null*
   @param {Function} callback - function(error, fileObj){...}
   @param {Boolean} proceedAfterUpload - Proceed onAfterUpload hook
   @summary Write buffer to FS and add to FilesCollection Collection
@@ -1296,7 +1302,7 @@
     check callback, Match.Optional Function
     check proceedAfterUpload, Match.Optional Boolean

-    fileId   = Random.id()
+    fileId   = opts.fileId or Random.id()
     FSName   = if @namingFunction then @namingFunction() else fileId
     fileName = if (opts.name or opts.fileName) then (opts.name or opts.fileName) else FSName

@@ -1352,6 +1358,7 @@
   @param {String} opts.type - File mime-type
   @param {Object} opts.meta - File additional meta-data
   @param {String} opts.userId - UserId, default *null*
+  @param {String} opts.fileId - _id, default *null*
   @param {Function} callback - function(error, fileObj){...}
   @param {Boolean} proceedAfterUpload - Proceed onAfterUpload hook
   @summary Download file, write stream to FS and add to FilesCollection Collection
@@ -1376,7 +1383,7 @@

     self      = @
     opts     ?= {}
-    fileId    = Random.id()
+    fileId    = opts.fileId or Random.id()
     FSName    = if @namingFunction then @namingFunction() else fileId
     pathParts = url.split('/')
     fileName  = if (opts.name or opts.fileName) then (opts.name or opts.fileName) else pathParts[pathParts.length - 1] or FSName
@@ -1496,9 +1503,8 @@
           userId:       opts.userId
           extension:    extension
           _storagePath: path.replace "#{nodePath.sep}#{opts.fileName}", ''
-
-        if opts.fileId
-          result._id = opts.fileId
+          fileId:       opts.fileId or null
+

         self.collection.insert result, (error, _id) ->
           if error


Add scroll delay for checkbox selections

--- apps/collect/client.coffee
+++ apps/collect/client.coffee
@@ -111,7 +111,18 @@

   # Whenever params change, scroll to the top
   params.on 'change', ->
-    $('html,body').animate { scrollTop: 0 }, 400
+    if _.keys(params.changedAttributes())[0] in ['major_periods', 'silent']
+      delayedScroll()
+    else
+      $('html,body').animate { scrollTop: 0 }, 400
+
+  # 1 second delay for checkbox selections
+  timer = null
+  delayedScroll = ->
+    clearTimeout(timer)
+    timer = setTimeout ->
+      $('html,body').animate { scrollTop: 0 }, 400
+    , 1000

   params.on 'change', ->
     analytics.track 'Commericial filter: params changed',


Some more cleaning up and refactoring on Delaunay.

--- examples/delaunay.coffee
+++ examples/delaunay.coffee
@@ -81,23 +81,21 @@
 # a,b` describe the same oriented triangle for given `a`, `b` and `c`, but `a,
 # c, a` does not.
 class Triangle
-  constructor: (a, b, c) ->
-    [as, bs, cs] = seq.map([a, b, c], (x) -> x.toString()).into []
-    [@a, @b, @c] =
-      if as < bs and as < cs
-        [a, b, c]
-      else if bs < cs
-        [b, c, a]
-      else
-        [c, a, b]
-
-  memo @, 'vertices', -> [@a, @b, @c]
+  constructor: (@a, @b, @c) ->
+
+  memo @, 'vertices', ->
+    if @a.toString() < @b.toString() and @a.toString() < @c.toString()
+      [@a, @b, @c]
+    else if @b.toString() < @c.toString()
+      [@b, @c, @a]
+    else
+      [@c, @a, @b]
+
   memo @, 'toSeq',    -> seq @vertices()
-  memo @, 'toString', -> "T(#{@a}, #{@b}, #{@c})"
+  memo @, 'toString', -> "T(#{seq.join @, ', '})"
   memo @, 'hashCode', -> hashCode @toString()

-  equals: (other) ->
-    equal(@a, other.a) and equal(@b, other.b) and equal(@c, other.c)
+  equals: (other) -> seq.equals @, other

 # Here's a quick shortcut for the constructor.
@@ -179,10 +177,7 @@
     plus: (a, b, c) ->
       if equal @third(a, b), c
         this
-      else if x = seq([[a, b], [b, c], [c, a]]).find((e) => @third__.get(e)?]
-        [f, g] = x
-        h = @third__.get x
-        trace -> "  Error in plus [#{@toSeq()?.join ', '}], (#{a}, #{b}, #{c})"
+      else if seq.find([[a, b], [b, c], [c, a]], ([p, q]) => @third p, q)
         throw new Error "Orientation mismatch."
       else
         new Triangulation @third__.plusAll [[[a,b],c], [[b,c],a], [[c,a],b]]


fix histocaching bug

fix histocaching bug

Former-commit-id: 21066291cf7c279e01706da85033faa86c098aed [formerly ed045b5b6517d7b32403be548c1b15b5fdccd34f] [formerly 201b116b0739c47ba8266905d68f0aa180d687f0 [formerly 29f799f4910eff5b960846cd1c4bb5ab0618f368]] [formerly 371924b04360ff82fe09bb87565e6fb37dba65e4 [formerly ed23d7be4a2ac897a87d5f8b0c5206ea9d7bb845] [formerly 078a3f772d0ca184aa8307912356fdd2399f2f0e [formerly 078a3f772d0ca184aa8307912356fdd2399f2f0e [formerly 77e5c64a69e2367f4f5c68439fc1b4a4b6ac8c02]]]]
Former-commit-id: e30962cc271e07bcd8edc61b037348aae95da1ec [formerly ff527dc3cbd1ce5077392906bdf4bcc6e5bb12b9] [formerly 78f103c7aff2364f1226b1dfc552b6cb3904df67 [formerly 9d04a9d76c0fc6a0fe4c79d29b1cc4d4fb2f81a8]]
Former-commit-id: 779de90f8a213fb33f286b2f0639c6c5699b87b9 [formerly 3cc8e4e6ca09111a5f73f470a1ccfa6dcabc0a3a]
Former-commit-id: bf40b645e0170cec4f76b74a909fff6855fcff00
Former-commit-id: 3fe644934a01d8e62a6975a572a351d5cb844074

--- @client/histogram.coffee
+++ @client/histogram.coffee
@@ -344,6 +344,7 @@
     # Draw the avatars in the histogram. Placement will be determined later
     # by the physics sim
     DIV
+      key: @histocache_key()
       ref: 'histo'
       style:
         height: @props.height


fix a bug where Abot UBanim time was showing a Date as an integer instead of formatting it nicely

--- source/javascripts/index/holidayTableFactory.coffee
+++ source/javascripts/index/holidayTableFactory.coffee
@@ -101,7 +101,7 @@
       rows.push(description: "שַׁבָּת ends", time: zmanim.setHaKochabim3Stars().format('h:mm'))
       rows.push(description: "רַבֵּנוּ תָּם", time: rabbenuTamTime(zmanim))
       if zmanim.sunset().isBefore(moment(gregorianDate).hour(18).minute(35)) && !hebrewDate.isErebPurim()
-        rows.push(description: "אָבוֹת וּבָּנִים", time: roundedToNearest5Minutes(moment(zmanim.sunset()).add(101, 'minute')))
+        rows.push(description: "אָבוֹת וּבָּנִים", time: roundedToNearest5Minutes(moment(zmanim.sunset()).add(101, 'minute')).format('h:mm'))
     if hebrewDate.isYomKippur()
       rows.push(description: "נְעִילָה", time: minutesBefore(zmanim.sunset(), 55).format('h:mm'))
       rows.push(description: "בִּרְכַּת כֹּהֲנִים before", time: zmanim.sunset().format('h:mm'))


fixes a bug in HebrewDate#isErebPurim

fixes a bug in HebrewDate#isErebPurim

--- source/javascripts/site/hebrewDateExtensions.coffee
+++ source/javascripts/site/hebrewDateExtensions.coffee
@@ -5,7 +5,7 @@
 HebrewDate.prototype.hasHadlakatNerotAfterSetHaKochabim = -> (@isErebYomTob() && @isShabbat()) || (@is1stDayOfYomTob() && !@isErebShabbat())
 HebrewDate.prototype.hasHadlakatNerot = -> @isErebShabbat() || @isErebYomTob() || @isErebYomKippur()
 HebrewDate.prototype.isNesiim = -> @monthAndRangeAre('NISAN', [1,2,3,4,5,6,7,8,9,10,11,12,13])
-HebrewDate.prototype.isErebPurim = -> @monthAndRangeAre('ADAR', 13) || @monthAndRangeAre('ADAR_SHENI', 13)
+HebrewDate.prototype.isErebPurim = -> @monthAndRangeAre('ADAR', [13]) || @monthAndRangeAre('ADAR_SHENI', [13])
 HebrewDate.prototype.isTenthOfTebet = HebrewDate.prototype.is10Tevet
 HebrewDate.prototype.isSeventeenthOfTamuz = HebrewDate.prototype.is17Tamuz
 HebrewDate.prototype.isNinthOfAb = HebrewDate.prototype.is9Ab


UI/core making the modal unit to udpate the size limits after the element inserted to the page

--- ui/core/src/modal.coffee
+++ ui/core/src/modal.coffee
@@ -86,9 +86,9 @@
   show: ()->
     hide_all_modals()

-    @limit_size(dom_window.size())
     @insertTo(document.body)
     @$super.apply(@, arguments)
+    @limit_size(dom_window.size())

     Modal.current = @constructor.current = @emit('show')


Add Spce T002-001 for Label Model

Add Spce T002-001 for Label Model

--- spec/src/coffee/label/models/t002_label_spec.coffee
+++ spec/src/coffee/label/models/t002_label_spec.coffee
@@ -19,7 +19,6 @@
         .reply(
           200
           (url, body)->
-            console.log "HTTP POST"
             type: "post"
         )
       nock("http://localhost:8888")
@@ -27,49 +26,91 @@
         .reply(
           200
           (url, body)->
-            console.log "HTTP PUT"
-            type: "post"
+            type: "put"
         )
       nock("http://localhost:8888")
         .intercept("/user_name/repo_name", "DELETE")
         .reply(
           200
           (url, body)->
-            console.log "HTTP DELETE"
             type: "delete"
         )
-    it "001: #set, #save", (done)->
-      @label = new @Label(
-      )
       @label_url = "http://localhost:8888/user_name/repo_name"
-      @label.url = @label_url
-      @label.set "name", "category1/child-category1/label1"
-      @label.get("name").should.equal "category1/child-category1/label1"
-      @label.save(
+      @spy = sinon.spy()
+
+    it "001: create", (done)->
+      label1 = new @Label()
+      label1.on "sync", =>
+        @spy "sync"
+      label1.url = @label_url
+      label1.set "name", "category1/child-category1/label1"
+      label1.get("name").should.equal "category1/child-category1/label1"
+      label1.save(
         {
           name: "category1/child-category2/label1"
         }
         {
           success:
-            (ret)=>
-              console.log arguments
-              ret.get("type").should.equal "post"
-              @label.get("name").should.equal "category1/child-category2/label1"
-              done()
+            (model, ret)=>
+              setTimeout(
+                =>
+                  ret.type.should.equal "post"
+                  label1.get("name").should.equal "category1/child-category2/label1"
+                  @spy.calledWith("sync").should.equal true
+                  done()
+                0
+              )
         }
       )
-    it "002: #set, #destroy", (done)->
-      @label = new @Label(
+
+    it "002: update", (done)->
+      label1 = new @Label(
         id: "label1"
       )
-      @label_url = "http://localhost:8888/user_name/repo_name"
-      @label.url = @label_url
-      @label.set "name", "カテゴリ1/子カテゴリ1/ラベル1"
-      @label.get("name").should.equal "カテゴリ1/子カテゴリ1/ラベル1"
-      @label.destroy(
+      label1.on "sync", =>
+        @spy "sync"
+      label1.url = @label_url
+      label1.set "name", "category1/child-category1/label1"
+      label1.get("name").should.equal "category1/child-category1/label1"
+      label1.save(
+        {
+          name: "category1/child-category2/label1"
+        }
+        {
+          success:
+            (model, ret)=>
+              setTimeout(
+                =>
+                  ret.type.should.equal "put"
+                  label1.get("name").should.equal "category1/child-category2/label1"
+                  @spy.calledWith("sync").should.equal true
+                  done()
+                0
+              )
+        }
+      )
+
+    it "003: delete", (done)->
+      label1 = new @Label(
+        id: "label1"
+      )
+      label1.on "sync", =>
+        @spy "sync"
+      label1.on "destroy", =>
+        @spy "destroy"
+      label1.url = @label_url
+      label1.set "name", "カテゴリ1/子カテゴリ1/ラベル1"
+      label1.get("name").should.equal "カテゴリ1/子カテゴリ1/ラベル1"
+      label1.destroy(
         success: (model, ret)=>
-          console.log "after destroy: ", model
-          done()
+          setTimeout(
+            =>
+              ret.type.should.equal "delete"
+              @spy.calledWith("sync").should.equal true
+              @spy.calledWith("destroy").should.equal true
+              done()
+            0
+          )
       )

   describe "002: color", ->


use a CompositeDisposable for the config

--- lib/block-cursor.coffee
+++ lib/block-cursor.coffee
@@ -1,4 +1,6 @@
 'use strict'
+{CompositeDisposable} = require 'atom'
+
 class BlockCursor
   cursorStyle = null
   cursorTypeMap =
@@ -30,22 +32,17 @@
       maximum: 500

   activate: ->
-    @cursorTypeObserveSubscription =
-      atom.config.observe 'block-cursor.cursorType', (val) => @applyCursorType val
-    @primaryColorObserveSubscription =
-      atom.config.observe 'block-cursor.primaryColor', (val) => @applyPrimaryColor val
-    @secondaryColorObserveSubscription =
-      atom.config.observe 'block-cursor.secondaryColor', (val) => @applySecondaryColor val
-    @pulseDurationObserveSubscription =
-      atom.config.observe 'block-cursor.pulseDuration', (val) => @applyPulse val
+    @subs = new CompositeDisposable()
+    @subs.add atom.config.observe 'block-cursor.cursorType', @applyCursorType.bind @
+    @subs.add atom.config.observe 'block-cursor.primaryColor', @applyPrimaryColor.bind @
+    @subs.add atom.config.observe 'block-cursor.secondaryColor', @applySecondaryColor.bind @
+    @subs.add atom.config.observe 'block-cursor.pulseDuration', @applyPulseDuration.bind @

   deactivate: ->
-    @cursorTypeObserveSubscription.dispose()
-    @primaryColorObserveSubscription.dispose()
-    @secondaryColorObserveSubscription.dispose()
-    @pulseDurationObserveSubscription.dispose()
-    cursorStyle.parentNode.removeChild cursorStyle
-    cursorStyle = null
+    @subs.dispose()
+    if cursorStyle?
+      cursorStyle.parentNode.removeChild cursorStyle
+      cursorStyle = null

   applyCursorType: (cursorTypeName) ->
     @cursorType = cursorType = cursorTypeMap[cursorTypeName]
@@ -65,7 +62,7 @@
     @updateStylesheet secondarySelector, 'background-color', color
     @updateStylesheet secondarySelector, 'border-color', color

-  applyPulse: (duration) ->
+  applyPulseDuration: (duration) ->
     @updateStylesheet primarySelector, 'transition-duration', "#{duration}ms"

   getCursorStyle: ->


Isobject.

Isobject.

--- src/chaplin/dispatcher.coffee
+++ src/chaplin/dispatcher.coffee
@@ -77,7 +77,7 @@
   # The default implementation uses require() from a AMD module loader
   # like RequireJS to fetch the constructor.
   loadController: (name, handler) ->
-    return handler(name) if _.isObject name
+    return handler(name) if name and typeof name is 'object'

     fileName = name + @settings.controllerSuffix
     moduleName = @settings.controllerPath + fileName


fix to get extension if link has query params after extension

--- files.coffee
+++ files.coffee
@@ -1082,7 +1082,7 @@
   ###
   _getExt: (fileName) ->
     if !!~fileName.indexOf('.')
-      extension = fileName.split('.').pop()
+      extension = fileName.split('.').pop().split('?')[0]
       return { ext: extension, extension, extensionWithDot: '.' + extension }
     else
       return { ext: '', extension: '', extensionWithDot: '' }


Add missing prop specification

Add missing prop specification

--- app/assets/javascripts/rglossa/components/centre/search_inputs/cwb_simple_input.jsx.coffee
+++ app/assets/javascripts/rglossa/components/centre/search_inputs/cwb_simple_input.jsx.coffee
@@ -2,6 +2,7 @@

 window.CwbSimpleInput = React.createClass
   propTypes:
+    hasPhoneticForm: React.PropTypes.bool.isRequired
     searchQuery: React.PropTypes.object.isRequired
     handleQueryChanged: React.PropTypes.func.isRequired
     handleSearch: React.PropTypes.func.isRequired


guard against possible undefined

guard against possible undefined

--- app/assets/javascripts/species/views/search_form/taxon_concept_search_text_field.js.coffee
+++ app/assets/javascripts/species/views/search_form/taxon_concept_search_text_field.js.coffee
@@ -22,5 +22,5 @@
     $('.search fieldset').removeClass('parent-focus parent-active')

   showDropdown: () ->
-    if @.$().val().length > 2
+    if @.$()?.val().length > 2
       $('.search fieldset').addClass('parent-focus parent-active')


Remove sort columns with undefined values in find options.

--- reactive-table.coffee
+++ reactive-table.coffee
@@ -128,9 +128,15 @@
   if @_options.page > data.totalPages
     @_options.page = 1

+  # It seems that leaving sort columns with undefined values makes find go wild.
+  # So keep only valid sort columns in the sort option.
+  sort = {}
+  for key, value of @_sort
+    sort[key] = value if !!value
+
   # Query again with these options
   opts =
-    sort: @_sort
+    sort: sort
     fields: @_fields
     skip: @_options.limit * (@_options.page - 1)
     limit: @_options.limit


Possible fix for #28

Possible fix for #28

--- lib/atom-react.coffee
+++ lib/atom-react.coffee
@@ -54,12 +54,12 @@

       return indent if precedingRow < 0

-      if @editor.isBufferRowCommented(bufferRow) and @editor.isBufferRowCommented(precedingRow)
-        return @editor.indentationForBufferRow(precedingRow)
-
       precedingLine = @buffer.lineForRow(precedingRow)

       return indent if not precedingLine?
+
+      if @editor.isBufferRowCommented(bufferRow) and @editor.isBufferRowCommented(precedingRow)
+        return @editor.indentationForBufferRow(precedingRow)

       tagStartTest = tagStartRegex.testSync(precedingLine)
       decreaseIndentTest = decreaseIndentRegex.testSync(precedingLine)


Fix problem with location Icon

Fix problem with location Icon

Cannot access the map functions before the map is loaded, moved defining
location icon to after the if statement that checks if the map is
loaded.

--- client.coffee
+++ client.coffee
@@ -732,15 +732,15 @@
 			if location?
 				log 'Rendered location on the map'
 				location = location.split(',')
-				locationIcon = L.icon({
-					iconUrl: Plugin.resourceUri('location.png'),
-					iconSize:     [40, 40],
-					iconAnchor:   [12, 39],
-					popupAnchor:  [-3, -75]
-				});
 				if mapReady()
 					# Show the player's location on the map
 					latLngObj= L.latLng(location[0], location[1])
+					locationIcon = L.icon({
+						iconUrl: Plugin.resourceUri('location.png'),
+						iconSize:     [40, 40],
+						iconAnchor:   [12, 39],
+						popupAnchor:  [-3, -75]
+					});
 					marker = L.marker(latLngObj, {icon: locationIcon})
 					marker.bindPopup("Hier ben ik nu!" + "<br> accuracy: " + state.get('accuracy'))
 					if window.beaconCurrentLocation


Help page in setup is more fancy

Help page in setup is more fancy

--- client.coffee
+++ client.coffee
@@ -485,10 +485,14 @@
 						renderTimeButton 'Days'
 						renderTimeButton 'Months'
 				# Gameinfo setup page:
+				expanded = Obs.create(false)
 				Dom.div !->
 					Dom.style margin: '-8px'
-					helpContent()
-
+					Ui.button tr('Game info'), !->
+						expanded.set(!expanded.get())
+					if expanded.get()
+						helpContent()
+

 		else if currentPage is 'setup1' # Setup map boundaries


use new coordinates in frontend

use new coordinates in frontend

--- app/assets/javascripts/modules/maps/search.js.coffee
+++ app/assets/javascripts/modules/maps/search.js.coffee
@@ -21,7 +21,7 @@

       for pa in protected_areas
         marker = L.marker(
-          L.latLng(pa.the_geom_latitude, pa.the_geom_longitude),
+          L.latLng(pa.coordinates?[0], pa.coordinates?[1]),
           { title: pa.name }
         )

@@ -35,8 +35,8 @@
   fitToResults: (points) ->
     return if points.length is 0

-    lats = points.map((p) -> parseFloat(p.the_geom_latitude))
-    lons = points.map((p) -> parseFloat(p.the_geom_longitude))
+    lats = points.map((p) -> parseFloat(p.coordinates[0]))
+    lons = points.map((p) -> parseFloat(p.coordinates[1]))

     maxLat = Math.max.apply(Math, lats)
     minLat = Math.min.apply(Math, lats)


Got rid of empty function parameter calls.

--- coffeescript/script.coffee
+++ coffeescript/script.coffee
@@ -7,7 +7,7 @@

   # Grab parameters from URL.
-  getLocationParameters = () ->
+  getLocationParameters = ->
     params = {}
     search = window.location.search

@@ -97,7 +97,7 @@
   #    Populate the form from the loaded data
   #    generate the TaskPaper month from the items field, and
   #    store the results in localStorage.
-  do generateTaskPaperMonth = () ->
+  do generateTaskPaperMonth = ->

     # Read the form data; prepare to generate a month.
     selectedYear   = $('input[name="year"]:checked')
@@ -216,7 +216,7 @@
   # itemsField.on 'keyup', $.debounce(250, generateTaskPaperMonth)
   # itemsField.on 'keyup', $.debounce(250, checkForEmptyItemsField)
   itemsField.on 'keyup', generateTaskPaperMonth
-  itemsField.on 'keyup', () ->
+  itemsField.on 'keyup', ->
     if itemsField.val().trim() isnt ''
       emptyButton.fadeIn('fast')
     else
@@ -275,10 +275,10 @@
     client.on 'datarequested', (client) ->
       client.setText($('#taskpaper-month').val())

-  client.on 'noFlash', () ->
+  client.on 'noFlash', ->
     $('#copy-button').hide()
     console.error('No Flash installed. Hiding the \'Copy\' button.')

-  client.on 'wrongFlash', () ->
+  client.on 'wrongFlash', ->
     $('#copy-button').hide()
     console.error('Wrong Flash installed. Hiding the \'Copy\' button.')


Prevent submitting (and validating) dialog inputs with surrounding form

--- src/trix/controllers/toolbar_controller.coffee
+++ src/trix/controllers/toolbar_controller.coffee
@@ -11,6 +11,7 @@

   constructor: (@element) ->
     @attributes = {}
+    @resetDialogInputs()

     handleEvent "mousedown", onElement: @element, matchingSelector: actionButtonSelector, withCallback: @didClickActionButton
     handleEvent "mousedown", onElement: @element, matchingSelector: attributeButtonSelector, withCallback: @didClickAttributeButton
@@ -101,12 +102,13 @@

     element = @getDialogForAttributeName(attributeName)
     input = getInputForDialog(element, attributeName)
+    @delegate?.toolbarWillShowDialog(input?)
+    element.classList.add("active")

-    @delegate?.toolbarWillShowDialog(input?)
-
-    element.classList.add("active")
-    input?.value = @attributes[attributeName] ? ""
-    input?.select()
+    if input
+      input.removeAttribute("disabled")
+      input.value = @attributes[attributeName] ? ""
+      input.select()

   setAttribute: (dialogElement) ->
     attributeName = getAttributeName(dialogElement)
@@ -125,9 +127,13 @@

   hideDialog: ->
     @element.querySelector(activeDialogSelector)?.classList.remove("active")
-    for input in @element.querySelectorAll("input.validate")
+    @resetDialogInputs()
+    @delegate?.toolbarDidHideDialog()
+
+  resetDialogInputs: ->
+    for input in @element.querySelectorAll(dialogInputSelector)
+      input.setAttribute("disabled", "disabled")
       input.classList.remove("validate")
-    @delegate?.toolbarDidHideDialog()

   getDialogForAttributeName: (attributeName) ->
     @element.querySelector(".dialog[data-attribute=#{attributeName}]")


Teardown Chosen events

Teardown Chosen events

--- app/assets/javascripts/views/chosen-select.js.coffee
+++ app/assets/javascripts/views/chosen-select.js.coffee
@@ -33,6 +33,10 @@
       @rerenderChosen()
   ).on('didInsertElement')

+  teardown: (->
+    @.$().chosen('destroy')
+  ).on('willDestroyElement')
+
   cleanSearchText: (option, context) ->
     option.text


some bug fixes

some bug fixes

--- scripts.coffee
+++ scripts.coffee
@@ -3,18 +3,23 @@

 $ ->
   currentMenuItem = null
+  anchor_selector = ".markdown-body h3,
+    .markdown-body h4,
+    .markdown-body h5,
+    .markdown-body h6"
+  contentHeaders = $(anchor_selector)
   if anchors
     anchors.options =
-      placement: 'right',
-      icon: '#'
-    anchors.add('.markdown-body h3, .markdown-body h4, .markdown-body h5, .markdown-body h6')
+      placement: "right",
+      icon: "#"
+    anchors.add(anchor_selector)

   $(document).on "click", ".js-nav-item", (e) ->
     e.preventDefault()
     $("body").toggleClass("is-nav-close is-nav-open")

   $(document).on "keypress", (e) ->
-    contentHeaders = $('.markdown-body h3, .markdown-body h4, .markdown-body h5, .markdown-body h6');
+
     pushHash = (item) ->
       if item
         window.location = item.url
@@ -30,9 +35,16 @@
           return getNextMenuItem()

         else
+          if contentHeaders.length == 0
+            currentMenuItem =
+              index: 0
+            return getNextMenuItem()
+
           currentMenuItem =
             index: 0,
             url: "#" + $(contentHeaders[0]).attr("id")
+
+

       else if currentMenuItem.index + 1 >= contentHeaders.length
         next = $(".js-next-page")
@@ -64,8 +76,8 @@

         else
           currentMenuItem =
-            index: contentHeaders.length - 1,
-            url: "#" + $(contentHeaders.last()).attr("id")
+            index: 0
+          return getPrevMenuItem()
       else if currentMenuItem.index <= 0
         prev = $(".js-last-page")


added isfull usages added settimeout identifier to tflobby.lobby.timer brain entry

--- src/commands/lifecycle.coffee
+++ src/commands/lifecycle.coffee
@@ -111,16 +111,19 @@
       players = lobby.players()
       format = lobby.slots()

-      if lobby.totalPlayers() < format
+      unless lobby.isFull()

         unless lobby.isAdded(target)
           @brain.set('tflobby.lobby', lobby.add(target))
           added = lobby.totalPlayers()
           msg.send(":: #{lobby.server.name} : #{lobby.map} : #{added}/#{format} : [ #{lobby.players().join(', ')} ] ::")

-          return unless added is format and not lobby.finalising
+          return unless lobby.isFull() and not lobby.finalising

-          return setTimeout(finalising.bind(@, msg), 60000)
+          return @brain.set(
+            'tflobby.lobby.timer',
+            setTimeout(finalising.bind(@, msg), 60000)
+          )

         return msg.reply(":: #{msg.random(@brain.get('tflobby.chat.affirmative'))} #{if targetingSelf then 'you are' else target + ' is'} already added...")


Fix: Hydrogen incompatible with Atom 1.11+

--- lib/code-manager.coffee
+++ lib/code-manager.coffee
@@ -139,8 +139,7 @@
     getRegexString: ->
         scope = @editor.getRootScopeDescriptor()

-        {commentStartString, commentEndString} =
-            @editor.languageMode.commentStartAndEndStringsForScope(scope)
+        {commentStartString, commentEndString} = @getCommentStrings(scope)

         unless commentStartString
             console.log 'CellManager: No comment string defined in root scope'
@@ -153,6 +152,13 @@
             escapedCommentStartString + '(%%| %%| <codecell>| In\[[0-9 ]*\]:?)'

         return regexString
+
+
+    getCommentStrings: (scope) ->
+        if parseFloat(atom.getVersion()) <= 1.1
+            return @editor.languageMode.commentStartAndEndStringsForScope(scope)
+        else
+            return @editor.getCommentStrings(scope)

     isComment: (position) ->


Leave and join rooms correctly

Leave and join rooms correctly

--- src/client/lib/01_routing/01_room_controller.coffee
+++ src/client/lib/01_routing/01_room_controller.coffee
@@ -10,16 +10,22 @@
     [
       Meteor.subscribe 'room', @params.roomId
       Meteor.subscribe 'player'
-      ready: =>
-        hasPlayer(@params.roomId) and @_joinCalled and not @_isJoining
     ]

   onBeforeAction: ->
-    unless @_isJoining or hasPlayer @params.roomId
+    unless @_isJoining
       Meteor.call 'joinRoom', @params.roomId, @isMaster, (error, result) =>
+        if error?
+          message = 'An error occured while attempting to join room.'
+          console.error message, error
         @_isJoining = false
       @_isJoining = true
-      @_joinCalled = true
+
+  onStop: ->
+    Meteor.call 'leaveRooms', (error, result) ->
+      if error?
+        message = 'An error occured while attempting to leave rooms.'
+        console.error message, error

 hasPlayer = (roomId) ->


correct path

correct path

--- apps/auction_support/client/registration_form.coffee
+++ apps/auction_support/client/registration_form.coffee
@@ -1,7 +1,7 @@
 sd = require('sharify').data
 Backbone = require 'backbone'
 analytics = require '../../../lib/analytics.coffee'
-analyticsHooks = require '../../../../lib/analytics_hooks.coffee'
+analyticsHooks = require '../../../lib/analytics_hooks.coffee'
 CurrentUser = require '../../../models/current_user.coffee'
 ErrorHandlingForm = require('../../../components/credit_card/client/error_handling_form.coffee')
 ModalPageView = require '../../../components/modal/page.coffee'


update prop types

update prop types

--- src/components/pinned-header-footer-card/index.cjsx
+++ src/components/pinned-header-footer-card/index.cjsx
@@ -11,7 +11,9 @@
   displayName: 'PinnedHeaderFooterCard'
   propTypes:
     buffer: React.PropTypes.number
-    fixedOffset: React.PropTypes.number
+    scrollSpeedBuffer: React.PropTypes.number
+    forceShy: React.PropTypes.bool
+    containerBuffer: React.PropTypes.number

   getDefaultProps: ->
     buffer: 60


[stark] fixed model id bug due to a change in backbone 1.1.1

--- lib/assets/javascripts/stark/model_util.js.coffee
+++ lib/assets/javascripts/stark/model_util.js.coffee
@@ -45,6 +45,10 @@
     else
       return false

-    @id = @[id] if set_id
+    if set_id
+      @id = @[id]
+      # need to set() as well due to a change in backbone 1.1.1 (isNew() now relies on has("id"))
+      # this is probably more correct anyway
+      @set {id: @id}

     return true


Fix dangling resources reference

Fix dangling resources reference

--- lib/editor-proxy.coffee
+++ lib/editor-proxy.coffee
@@ -9,7 +9,7 @@
 module.exports =
   setupContext: (@editorView) ->
     @indentation = @editorView.getEditor().getTabText()
-    emmet.require("resources").setVariable("indentation", @indentation)
+    resources.setVariable("indentation", @indentation)
     @syntax = @getSyntax()

   # Fetches the character indexes of the selected text.


dont capture space

dont capture space

--- foass.coffee
+++ foass.coffee
@@ -13,7 +13,7 @@

 module.exports = (robot) ->
-  robot.hear /^fu( )?(\w+)?/i, (msg) ->
+  robot.hear /^fu ?(\w+)?/i, (msg) ->
     options = [
       'off',
       'you',
@@ -30,7 +30,7 @@

     from = msg.message.user.name

-    to = msg.match[2]
+    to = msg.match[1]

     if to
       options.push(to)


Add XMLHttpRequest for UnnaturalREST

Add XMLHttpRequest for UnnaturalREST

--- lib/gamboge-view.coffee
+++ lib/gamboge-view.coffee
@@ -13,7 +13,7 @@
 # You should have received a copy of the GNU Affero General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.

-{View} = require 'atom'
+{$, Point, Range, View} = require 'atom'

 # This class listens to editor events, forwarding state, and updating a model. In
 # effect, this is kind of a View/Controller in classical MVC.
@@ -21,6 +21,9 @@
 # It is primarily concerned with getting surrounding token context, receiving
 # predictions, and forwarding display of predictions (through [INSERT CLASS
 # HERE]).
+
+# Use at most this many tokens to form predictions.
+NGRAM_ORDER = 3

 # Note: Heavily based on: atom/autocomplete, (C) GitHub Inc. 2014
 # https://github.com/atom/autocomplete/blob/master/lib/autocomplete-view.coffee
@@ -58,11 +61,13 @@
       # TODO
       # Figure out change location from cursor
       rawTokens = @getTokensForCursorContext()
-      trailer = @makeMostImportantTokenList(rawTokens)
-      console.log {trailer}
+      tokens = @makeMostImportantTokenList(rawTokens)

       # Set off prediction request
-      # Display predictions
+      @predict tokens, (predictions) =>
+      # TODO: Display predictions...
+        console.log predictions
+

     # TODO: onDidChangePath will probably be useful later for telling the
@@ -80,39 +85,59 @@
     # This... might be useful?

   # Gets a list of tokens for the preceeding context of the cursor.
+  # TODO: Possible refactor: get all of this token stuff into its own...
+  # thing...
   getTokensForCursorContext: ->
-    # TODO: Return both preceeding and following tokens.
-    contextRange = @editor.getCurrentParagraphBufferRange()
+
+    # TODO: Fancier token retrieving logic!
+    # Get tokens for the current line
+    cursorPosition = @editor.getCursorBufferPosition()
+    beginningOfLine = new Point(cursorPosition.row, 0)
+    contextRange = new Range(beginningOfLine, cursorPosition)
+
+    # Even though Grammar::tokenizeLines does this for us, it always assumes
+    # the first line of the input is the first line in the file.
     text = @editor.getTextInBufferRange(contextRange)
     isFirstLine = contextRange.intersectsRow(0)

-    # Get the grammar to tokenize the context for us!
+    # Get the grammar to tokenize the context for us.
     {tokens} = @grammar.tokenizeLine(text, null, isFirstLine)
     tokens

   # Given tokens, returns a list of strings of tokens.
   makeMostImportantTokenList: (tokens) ->
-    numTokens = tokens.length
+    nonWhitespace = []
+    for token in tokens
+      value = token.value
+      continue unless value.trim?
+      nonWhitespace.push(value) unless value.trim() is ""
+
+    numTokens = nonWhitespace.length
     # Get last three tokens to make a trigram
-    lastThreeTokens = tokens.slice(numTokens - 3, numTokens)
-    (token.value for token in lastThreeTokens)
+    lastThreeTokens = nonWhitespace.slice(numTokens - NGRAM_ORDER, numTokens)

+  # Do the prediction, calling callback when finished.
+  predict: (tokens, done) ->
+    # Create the token path component, URI encoding each token.
+    path = (encodeURIComponent(token) for token in tokens).join('/')
+
+    origin = atom.config.get 'gamboge.unnaturalRESTOrigin'
+    # use @grammar.name => But need a look-up table for the language...
+    lang = 'py' #@grammar.langage
+    url = "http://#{origin}/#{lang}/predict/#{path}"
+    xhr = new XMLHttpRequest()
+    xhr.open('GET', url, yes)
+    xhr.setRequestHeader('Accept', 'application/json')
+    xhr.addEventListener 'load', =>
+      console.log {request: xhr}
+      return unless xhr.status is 200
+      done(JSON.parse(xhr.responseText))
+    xhr.send()

   # Tear down any state and detach.
   destroy: ->
     @editorView.removeClass 'gamboge'
     @detach()

-  # Probably loads all of the corpus model, thing, stuff.
-  activate: ->
-    # TODO: What am I even doing on activate?
-    console.log "GambogeView activated!"
-    """
-    if @hasParent()
-      @detach()
-    else
-      atom.workspaceView.append(this)
-    """
-
 # TODO: Make a popover suggestion list ACTUALLY based on autocomplete!
 # https://github.com/atom/autocomplete/blob/master/lib/autocomplete-view.coffee


changed windowRate

changed windowRate

--- lib/getThrottles.coffee
+++ lib/getThrottles.coffee
@@ -1,7 +1,7 @@
 config = require '../config'

 config.rateLimits ?= {};
-windowRate = 60 * 1000
+windowRate = 1000

 tokenthrottle = require 'tokenthrottle'
 # if config.redis?.host


Handle new scope name for C++

Handle new scope name for C++

--- lib/linter-clang.coffee
+++ lib/linter-clang.coffee
@@ -8,7 +8,7 @@
 class LinterClang extends Linter
   # The syntax that the linter handles. May be a string or
   # list/tuple of strings. Names should be all lowercase.
-  @syntax: ['source.c++', 'source.c']
+  @syntax: ['source.c++', 'source.cpp', 'source.c']

   # A string, list, tuple or callable that returns a string, list or tuple,
   # containing the command line (with arguments) used to lint.


Fix tab margin by making sure last tab is set

--- lib/linter-views.coffee
+++ lib/linter-views.coffee
@@ -101,9 +101,9 @@
       tab.classList.remove('last')
       tab.visibility = atom.config.get("linter.showErrorTab#{key}")
       if tab.visibility
+        last = tab
         foundActive = foundActive || tab.active
-        if first then last = tab
-        else
+        unless first
           first = tab
           firstLabel = key
     @changeTab(firstLabel) if first and not foundActive


:bug: Fix relying on overlayer and use lines instead

--- lib/minimap-view.coffee
+++ lib/minimap-view.coffee
@@ -350,7 +350,7 @@
       overlayY = top
     else
       scrollViewOffset = @scrollView.offset().top
-      overlayerOffset = @scrollView.find('.overlayer').offset().top
+      overlayerOffset = @scrollView.find('.lines').offset().top
       overlayY = -overlayerOffset + scrollViewOffset

     @indicator.setY(overlayY * @scaleY)


Replace DamerauLevenshtein with pure Levenshtein

--- lib/models/fuzzy.coffee
+++ lib/models/fuzzy.coffee
@@ -58,7 +58,7 @@
     result[result.length] = ch
     idx++
   return {rendered: result.join(""), score: totalScore} if patternIdx is pattern.length
-
+
 fuzzy.filter = (pattern, arr, opts={}) ->
   highlighted = arr.reduce(
     (prev, element, idx, arr) ->
@@ -80,7 +80,7 @@
       return a.original.length - b.original.length
     return compare if compare
     a.index - b.index
-
+
   # No matches? Sort the original array using Damerau-Levenshtein.
   if highlighted.length < 1
     highlighted = arr.reduce(
@@ -89,7 +89,7 @@
         str = opts.extract(element) if opts.extract
         prev[prev.length] =
           string: str
-          score: DamerauLevenshtein(pattern, str)
+          score: levenshtein(pattern, str)
           index: idx
           original: element
         prev
@@ -99,64 +99,59 @@
       return compare if compare
       b.index - a.index
   highlighted
-
-DamerauLevenshtein = (down, across, prices={}, damerau=true) ->
-  # https://github.com/cbaatz/damerau-levenshtein
-  switch typeof prices.insert
-    when "function"
-      insert = prices.insert
-    when "number"
-      insert = (c) ->
-        prices.insert
-    else
-      insert = (c) ->
-        1
-  switch typeof prices.remove
-    when "function"
-      remove = prices.remove
-    when "number"
-      remove = (c) ->
-        prices.remove
-    else
-      remove = (c) ->
-        1
-  switch typeof prices.substitute
-    when "function"
-      substitute = prices.substitute
-    when "number"
-      substitute = (from, to) ->
-        prices.substitute
-    else
-      substitute = (from, to) ->
-        1
-  switch typeof prices.transpose
-    when "function"
-      transpose = prices.transpose
-    when "number"
-      transpose = (backward, forward) ->
-        prices.transpose
-    else
-      transpose = (backward, forward) ->
-        1
-# ---------------------------------------------------------------------------- #
-  ds = []
-  if down is across
-    return 0
-  else
-    down = down.split("")
-    down.unshift null
-    across = across.split("")
-    across.unshift null
-    down.forEach (d, i) ->
-      ds[i] = []  unless ds[i]
-      across.forEach (a, j) ->
-        if i is 0 and j is 0
-          ds[i][j] = 0
-        else if i is 0
-          ds[i][j] = ds[i][j - 1] + insert(a)
-        else if j is 0
-          ds[i][j] = ds[i - 1][j] + remove(d)
-        else
-          ds[i][j] = Math.min(ds[i - 1][j] + remove(d), ds[i][j - 1] + insert(a), ds[i - 1][j - 1] + ((if d is a then 0 else substitute(d, a))))
-          ds[i][j] = Math.min(ds[i][j], ds[i - 2][j - 2] + ((if d is a then 0 else transpose(d, down[i - 1]))))  if damerau and i > 1 and j > 1 and down[i - 1] is a and d is across[j - 1]
-    ds[down.length - 1][across.length - 1]
+
+###
+# Copyright (c) 2011 Andrei Mackenzie
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy of
+# this software and associated documentation files (the "Software"), to deal in
+# the Software without restriction, including without limitation the rights to
+# use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+# the Software, and to permit persons to whom the Software is furnished to do so,
+# subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in all
+# copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+# FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+# COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+# IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+###
+
+# Compute the edit distance between the two given strings
+levenshtein = (a, b) ->
+  return b.length  if a.length is 0
+  return a.length  if b.length is 0
+  matrix = []
+
+  # increment along the first column of each row
+  i = undefined
+  i = 0
+  while i <= b.length
+    matrix[i] = [i]
+    i++
+
+  # increment each column in the first row
+  j = undefined
+  j = 0
+  while j <= a.length
+    matrix[0][j] = j
+    j++
+
+  # Fill in the rest of the matrix
+  i = 1
+  while i <= b.length
+    j = 1
+    while j <= a.length
+      if b.charAt(i - 1) is a.charAt(j - 1)
+        matrix[i][j] = matrix[i - 1][j - 1]
+      else
+        # substitution
+        # insertion
+        matrix[i][j] = Math.min(matrix[i - 1][j - 1] + 1, Math.min(matrix[i][j - 1] + 1, matrix[i - 1][j] + 1)) # deletion
+      j++
+    i++
+  matrix[b.length][a.length]