fixed facts

fixed facts

--- facts.coffee
+++ facts.coffee
@@ -25,78 +25,78 @@
   "A cat can jump up to five times its own height in a single bound.",
   "Some cats have survived falls of over 65 feet (20 meters), due largely to    their “righting reflex.” The eyes and balance organs in the inner    ear tell it where it is in space so the cat can land on its feet. Even cats    without a tail have this ability.",
   ]
-  // "

A cat rubs against people to mark them as their territory

  A cat rubs against people not only to be affectionate but also to mark out    its territory with scent glands around its face. The tail area and paws also    carry the cat’s scent.",
-  // "Researchers are unsure exactly how a cat purrs. Most veterinarians believe    that a cat purrs by vibrating vocal folds deep in the throat. To do this,    a muscle in the larynx opens and closes the air passage about 25 times per    second.",
-  // "When a family cat died in ancient Egypt, family members would mourn by shaving    off their eyebrows. They also held elaborate funerals during which they drank    wine and beat their breasts. The cat was embalmed with a sculpted wooden    mask and the tiny mummy was placed in the family tomb or in a pet cemetery    with tiny mummies of mice.",
-  // "In 1888, more than 300,000 mummified cats were found an Egyptian cemetery.    They were stripped of their wrappings and carted off to be used by farmers    in England and the U.S. for fertilizer.",
-  // "Most cats give birth to a litter of between one and nine kittens. The largest    known litter ever produced was 19 kittens, of which 15 survived.",
-  // "Smuggling a cat out of ancient Egypt was punishable by death. Phoenician    traders eventually succeeded in smuggling felines, which they sold to rich    people in Athens and other important cities.",
-  // "The earliest ancestor of the modern cat lived about 30 million years ago.    Scientists called it the Proailurus, which means “first cat” in    Greek. The group of animals that pet cats belong to emerged around 12 million    years ago.",
-  // "The biggest wildcat today is the Siberian Tiger. It can be more than 12    feet (3.6 m) long (about the size of a small car) and weigh up to 700 pounds    (317 kg).",
-  // "The smallest wildcat today is the Black-footed cat. The females are less    than 20 inches (50 cm) long and can weigh as little as 2.5 lbs (1.2 kg).",
-  // "Many Egyptians worshipped the goddess Bast, who had a woman’s body    and a cat’s head.",
-  // "Mohammed loved cats and reportedly his favorite cat, Muezza, was a tabby.    Legend says that tabby cats have an “M” for Mohammed on top of    their heads because Mohammad would often rest his hand on the cat’s    head.",
-  // "While many parts of Europe and North America consider the black cat a sign    of bad luck, in Britain and Australia, black cats are considered lucky.",
-  // "The most popular pedigreed cat is the Persian cat, followed by the Main    Coon cat and the Siamese cat.",
-  // "The smallest pedigreed cat is a Singapura, which can weigh just 4 lbs (1.8    kg), or about five large cans of cat food. The largest pedigreed cats are    Maine Coon cats, which can weigh 25 lbs (11.3 kg), or nearly twice as much    as an average cat weighs.",
-  // "

    Some Siamese cats are cross-eyed to compensate for abnormal optic wiring

  Some Siamese cats appear cross-eyed because the nerves from the left side    of the brain go to mostly the right eye and the nerves from the right side    of the brain go mostly to the left eye. This causes some double vision, which    the cat tries to correct by “crossing” its eyes.",
-  // "Researchers believe the word “tabby” comes from Attabiyah, a    neighborhood in Baghdad, Iraq. Tabbies got their name because their striped    coats resembled the famous wavy patterns in the silk produced in this city.",
-  // "Cats hate the water because their fur does not insulate well when it’s    wet. The Turkish Van, however, is one cat that likes swimming. Bred in central    Asia, its coat has a unique texture that makes it water resistant.",
-  // "The Egyptian Mau is probably the oldest breed of cat. In fact, the breed    is so ancient that its name is the Egyptian word for “cat.”",
-  // "The costliest cat ever is named Little Nicky, who cost his owner $50,000.    He is a clone of an older cat.",
-  // "A cat usually has about 12 whiskers on each side of its face.",
-  // "A cat’s eyesight is both better and worse than humans. It is better    because cats can see in much dimmer light and they have a wider peripheral    view. It’s worse because they don’t see color as well as humans do.    Scientists believe grass appears red to cats.",
-  // "Spanish-Jewish folklore recounts that Adam’s first wife, Lilith, became    a black vampire cat, sucking the blood from sleeping babies. This may be    the root of the superstition that a cat will smother a sleeping baby or suck    out the child’s breath.",
-  // "Perhaps the most famous comic cat is the Cheshire Cat in Lewis Carroll’s Alice      in Wonderland. With the ability to disappear, this mysterious character      embodies the magic and sorcery historically associated with cats.",
-  // "In the original Italian version of Cinderella, the benevolent fairy godmother    figure was a cat.",
-  // "

    Two Siamese cats discovered microphones hidden by Russian spies in Holland’s embassy in Moscow

  In Holland’s embassy in Moscow, Russia, the staff noticed that the    two Siamese cats kept meowing and clawing at the walls of the building. Their    owners finally investigated, thinking they would find mice. Instead, they    discovered microphones hidden by Russian spies. The cats heard the microphones    when they turned on.",
-  // "The little tufts of hair in a cat’s ear that help keep out dirt direct    sounds into the ear, and insulate the ears are called “ear furnishings.”",
-  // "The ability of a cat to find its way home is called “psi-traveling.” Experts    think cats either use the angle of the sunlight to find their way or that    cats have magnetized cells in their brains that act as compasses.",
-  // "Isaac Newton invented the cat flap. Newton was experimenting in a pitch-black    room. Spithead, one of his cats, kept opening the door and wrecking his experiment.    The cat flap kept both Newton and Spithead happy.",
-  // "The world’s rarest coffee, Kopi Luwak, comes from Indonesia where    a wildcat known as the luwak lives. The cat eats coffee berries and the coffee    beans inside pass through the stomach. The beans are harvested from the cat's    dung heaps and then cleaned and roasted. Kopi Luwak sells for about $500    for a 450 g (1 lb) bag.",
-  // "A cat’s jaw can’t move sideways, so a cat can’t chew large    chunks of food.",
-  // "A cat almost never meows at another cat, mostly just humans. Cats typically    will spit, purr, and hiss at other cats.",
-  // "A cat’s back is extremely flexible because it has up to 53 loosely    fitting vertebrae. Humans only have 34.",
-  // "

    Many cat owners think their cats can read their minds

  Approximately 1/3 of cat owners think their pets are able to read their    minds.",
-  // "All cats have claws, and all except the cheetah sheath them when at rest.",
-  // "Two members of the cat family are distinct from all others: the clouded    leopard and the cheetah. The clouded leopard does not roar like other big    cats, nor does it groom or rest like small cats. The cheetah is unique because    it is a running cat; all others are leaping cats. They are leaping cats because    they slowly stalk their prey and then leap on it.",
-  // "A cat lover is called an Ailurophilia (Greek: cat+lover).",
-  // "In Japan, cats are thought to have the power to turn into super spirits    when they die. This may be because according to the Buddhist religion, the    body of the cat is the temporary resting place of very spiritual people.",
-  // "Most cats had short hair until about 100 years ago, when it became fashionable    to own cats and experiment with breeding.",
-  // "Cats have 32 muscles that control the outer ear (humans have only 6). A    cat can independently rotate its ears 180 degrees.",
-  // "

    During the nearly 18 hours a day that kittens sleep, an important growth hormone is released

  One reason that kittens sleep so much is because a growth hormone is released    only during sleep.",
-  // "Cats have about 130,000 hairs per square inch (20,155 hairs per square centimeter).",
-  // "The heaviest cat on record is Himmy, a Tabby from Queensland, Australia.    He weighed nearly 47 pounds (21 kg). He died at the age of 10.",
-  // "The oldest cat on record was Crème Puff from Austin, Texas, who lived    from 1967 to August 6, 2005, three days after her 38th birthday. A cat typically    can live up to 20 years, which is equivalent to about 96 human years.",
-  // "The lightest cat on record is a blue point Himalayan called Tinker Toy,    who weighed 1 pound, 6 ounces (616 g). Tinker Toy was 2.75 inches (7 cm)    tall and 7.5 inches (19 cm) long.",
-  // "The tiniest cat on record is Mr. Pebbles, a 2-year-old cat that weighed    3 lbs (1.3 k) and was 6.1 inches (15.5 cm) high.",
-  // "A commemorative tower was built in Scotland for a cat named Towser, who    caught nearly 30,000 mice in her lifetime.",
-  // "In the 1750s, Europeans introduced cats into the Americas to control pests.",
-  // "The first cat show was organized in 1871 in London. Cat shows later became    a worldwide craze.",
-  // "The first cartoon cat was Felix the Cat in 1919. In 1940, Tom and Jerry    starred in the first theatrical cartoon “Puss Gets the Boot.” In    1981 Andrew Lloyd Weber created the musical Cats, based on T.S.    Eliot’s Old Possum’s Book of Practical Cats.f",
-  // "The normal body temperature of a cat is between 100.5 ° and 102.5 °F.    A cat is sick if its temperature goes below 100 ° or above 103 °F.",
-  // "A cat has 230 bones in its body. A human has 206. A cat has no collarbone,    so it can fit through any opening the size of its head.",
-  // "A cat’s nose pad is ridged with a unique pattern, just like the fingerprint    of a human.",
-  // "If they have ample water, cats can tolerate temperatures up to 133 °F.",
-  // "Foods that should not be given to cats include onions, garlic, green tomatoes,    raw potatoes, chocolate, grapes, and raisins. Though milk is not toxic, it    can cause an upset stomach and gas. Tylenol and aspirin are extremely toxic    to cats, as are many common houseplants. Feeding cats dog food or canned    tuna that's for human consumption can cause malnutrition.",
-  // "A 2007 Gallup poll revealed that both men and women were equally likely    to own a cat.",
-  // "A cat’s heart beats nearly twice as fast as a human heart, at 110    to 140 beats a minute.",
-  // "

    Cat’s sweat only through their paws

  Cats don’t have sweat glands over their bodies like humans do. Instead,    they sweat only through their paws.",
-  // "In just seven years, a single pair of cats and their offspring could produce    a staggering total of 420,000 kittens.",
-  // "Relative to its body size, the clouded leopard has the biggest canines of    all animals' canines. Its dagger-like teeth can be as long as 1.8 inches    (4.5 cm).",
-  // "Cats spend nearly 1/3 of their waking hours cleaning themselves.",
-  // "Grown cats have 30 teeth. Kittens have about 26 temporary teeth, which they    lose when they are about 6 months old.",
-  // "A cat called Dusty has the known record for the most kittens. She had more    than 420 kittens in her lifetime.",
-  // "The largest cat breed is the Ragdoll. Male Ragdolls weigh between 12 and    20 lbs (5.4-9.0 k). Females weigh between 10 and 15 lbs (4.5-6.8 k).",
-  // "Cats are extremely sensitive to vibrations. Cats are said to detect earthquake    tremors 10 or 15 minutes before humans can.",
-  // "In contrast to dogs, cats have not undergone major changes during their    domestication process.",
-  // "A female cat is called a queen or a molly.",
-  // "In the 1930s, two Russian biologists discovered that color change in Siamese    kittens depend on their body temperature. Siamese cats carry albino genes    that work only when the body temperature is above 98° F. If these kittens    are left in a very warm room, their points won’t darken and they will    stay a creamy white.",
-  // "There are up to 60 million feral cats in the United States alone.",
-  // "The oldest cat to give birth was Kitty who, at the age of 30, gave birth    to two kittens. During her life, she gave birth to 218 kittens.",
-  // "The most traveled cat is Hamlet, who escaped from his carrier while on a    flight. He hid for seven weeks behind a pane. By the time he was discovered,    he had traveled nearly 373,000 miles (600,000 km).",
-  // "The most expensive cat was an Asian Leopard cat (ALC)-Domestic Shorthair    (DSH) hybrid named Zeus. Zeus, who is 90% ALC and 10% DSH, has an asking    price of £100,000 ($154,000).",
-  // "The cat who holds the record for the longest non-fatal fall is Andy. He    fell from the 16th floor of an apartment building (about 200 ft/.06 km) and    survived.",
-  // "The richest cat is Blackie who was left £15 million by his owner, Ben Rea.",
-  // "The claws on the cat’s back paws aren’t as sharp as the claws    on the front paws because the claws in the back don’t retract and,    consequently, become worn.",/
-//]+  # "

A cat rubs against people to mark them as their territory

  A cat rubs against people not only to be affectionate but also to mark out    its territory with scent glands around its face. The tail area and paws also    carry the cat’s scent.",
+  # "Researchers are unsure exactly how a cat purrs. Most veterinarians believe    that a cat purrs by vibrating vocal folds deep in the throat. To do this,    a muscle in the larynx opens and closes the air passage about 25 times per    second.",
+  # "When a family cat died in ancient Egypt, family members would mourn by shaving    off their eyebrows. They also held elaborate funerals during which they drank    wine and beat their breasts. The cat was embalmed with a sculpted wooden    mask and the tiny mummy was placed in the family tomb or in a pet cemetery    with tiny mummies of mice.",
+  # "In 1888, more than 300,000 mummified cats were found an Egyptian cemetery.    They were stripped of their wrappings and carted off to be used by farmers    in England and the U.S. for fertilizer.",
+  # "Most cats give birth to a litter of between one and nine kittens. The largest    known litter ever produced was 19 kittens, of which 15 survived.",
+  # "Smuggling a cat out of ancient Egypt was punishable by death. Phoenician    traders eventually succeeded in smuggling felines, which they sold to rich    people in Athens and other important cities.",
+  # "The earliest ancestor of the modern cat lived about 30 million years ago.    Scientists called it the Proailurus, which means “first cat” in    Greek. The group of animals that pet cats belong to emerged around 12 million    years ago.",
+  # "The biggest wildcat today is the Siberian Tiger. It can be more than 12    feet (3.6 m) long (about the size of a small car) and weigh up to 700 pounds    (317 kg).",
+  # "The smallest wildcat today is the Black-footed cat. The females are less    than 20 inches (50 cm) long and can weigh as little as 2.5 lbs (1.2 kg).",
+  # "Many Egyptians worshipped the goddess Bast, who had a woman’s body    and a cat’s head.",
+  # "Mohammed loved cats and reportedly his favorite cat, Muezza, was a tabby.    Legend says that tabby cats have an “M” for Mohammed on top of    their heads because Mohammad would often rest his hand on the cat’s    head.",
+  # "While many parts of Europe and North America consider the black cat a sign    of bad luck, in Britain and Australia, black cats are considered lucky.",
+  # "The most popular pedigreed cat is the Persian cat, followed by the Main    Coon cat and the Siamese cat.",
+  # "The smallest pedigreed cat is a Singapura, which can weigh just 4 lbs (1.8    kg), or about five large cans of cat food. The largest pedigreed cats are    Maine Coon cats, which can weigh 25 lbs (11.3 kg), or nearly twice as much    as an average cat weighs.",
+  # "

    Some Siamese cats are cross-eyed to compensate for abnormal optic wiring

  Some Siamese cats appear cross-eyed because the nerves from the left side    of the brain go to mostly the right eye and the nerves from the right side    of the brain go mostly to the left eye. This causes some double vision, which    the cat tries to correct by “crossing” its eyes.",
+  # "Researchers believe the word “tabby” comes from Attabiyah, a    neighborhood in Baghdad, Iraq. Tabbies got their name because their striped    coats resembled the famous wavy patterns in the silk produced in this city.",
+  # "Cats hate the water because their fur does not insulate well when it’s    wet. The Turkish Van, however, is one cat that likes swimming. Bred in central    Asia, its coat has a unique texture that makes it water resistant.",
+  # "The Egyptian Mau is probably the oldest breed of cat. In fact, the breed    is so ancient that its name is the Egyptian word for “cat.”",
+  # "The costliest cat ever is named Little Nicky, who cost his owner $50,000.    He is a clone of an older cat.",
+  # "A cat usually has about 12 whiskers on each side of its face.",
+  # "A cat’s eyesight is both better and worse than humans. It is better    because cats can see in much dimmer light and they have a wider peripheral    view. It’s worse because they don’t see color as well as humans do.    Scientists believe grass appears red to cats.",
+  # "Spanish-Jewish folklore recounts that Adam’s first wife, Lilith, became    a black vampire cat, sucking the blood from sleeping babies. This may be    the root of the superstition that a cat will smother a sleeping baby or suck    out the child’s breath.",
+  # "Perhaps the most famous comic cat is the Cheshire Cat in Lewis Carroll’s Alice      in Wonderland. With the ability to disappear, this mysterious character      embodies the magic and sorcery historically associated with cats.",
+  # "In the original Italian version of Cinderella, the benevolent fairy godmother    figure was a cat.",
+  # "

    Two Siamese cats discovered microphones hidden by Russian spies in Holland’s embassy in Moscow

  In Holland’s embassy in Moscow, Russia, the staff noticed that the    two Siamese cats kept meowing and clawing at the walls of the building. Their    owners finally investigated, thinking they would find mice. Instead, they    discovered microphones hidden by Russian spies. The cats heard the microphones    when they turned on.",
+  # "The little tufts of hair in a cat’s ear that help keep out dirt direct    sounds into the ear, and insulate the ears are called “ear furnishings.”",
+  # "The ability of a cat to find its way home is called “psi-traveling.” Experts    think cats either use the angle of the sunlight to find their way or that    cats have magnetized cells in their brains that act as compasses.",
+  # "Isaac Newton invented the cat flap. Newton was experimenting in a pitch-black    room. Spithead, one of his cats, kept opening the door and wrecking his experiment.    The cat flap kept both Newton and Spithead happy.",
+  # "The world’s rarest coffee, Kopi Luwak, comes from Indonesia where    a wildcat known as the luwak lives. The cat eats coffee berries and the coffee    beans inside pass through the stomach. The beans are harvested from the cat's    dung heaps and then cleaned and roasted. Kopi Luwak sells for about $500    for a 450 g (1 lb) bag.",
+  # "A cat’s jaw can’t move sideways, so a cat can’t chew large    chunks of food.",
+  # "A cat almost never meows at another cat, mostly just humans. Cats typically    will spit, purr, and hiss at other cats.",
+  # "A cat’s back is extremely flexible because it has up to 53 loosely    fitting vertebrae. Humans only have 34.",
+  # "

    Many cat owners think their cats can read their minds

  Approximately 1/3 of cat owners think their pets are able to read their    minds.",
+  # "All cats have claws, and all except the cheetah sheath them when at rest.",
+  # "Two members of the cat family are distinct from all others: the clouded    leopard and the cheetah. The clouded leopard does not roar like other big    cats, nor does it groom or rest like small cats. The cheetah is unique because    it is a running cat; all others are leaping cats. They are leaping cats because    they slowly stalk their prey and then leap on it.",
+  # "A cat lover is called an Ailurophilia (Greek: cat+lover).",
+  # "In Japan, cats are thought to have the power to turn into super spirits    when they die. This may be because according to the Buddhist religion, the    body of the cat is the temporary resting place of very spiritual people.",
+  # "Most cats had short hair until about 100 years ago, when it became fashionable    to own cats and experiment with breeding.",
+  # "Cats have 32 muscles that control the outer ear (humans have only 6). A    cat can independently rotate its ears 180 degrees.",
+  # "

    During the nearly 18 hours a day that kittens sleep, an important growth hormone is released

  One reason that kittens sleep so much is because a growth hormone is released    only during sleep.",
+  # "Cats have about 130,000 hairs per square inch (20,155 hairs per square centimeter).",
+  # "The heaviest cat on record is Himmy, a Tabby from Queensland, Australia.    He weighed nearly 47 pounds (21 kg). He died at the age of 10.",
+  # "The oldest cat on record was Crème Puff from Austin, Texas, who lived    from 1967 to August 6, 2005, three days after her 38th birthday. A cat typically    can live up to 20 years, which is equivalent to about 96 human years.",
+  # "The lightest cat on record is a blue point Himalayan called Tinker Toy,    who weighed 1 pound, 6 ounces (616 g). Tinker Toy was 2.75 inches (7 cm)    tall and 7.5 inches (19 cm) long.",
+  # "The tiniest cat on record is Mr. Pebbles, a 2-year-old cat that weighed    3 lbs (1.3 k) and was 6.1 inches (15.5 cm) high.",
+  # "A commemorative tower was built in Scotland for a cat named Towser, who    caught nearly 30,000 mice in her lifetime.",
+  # "In the 1750s, Europeans introduced cats into the Americas to control pests.",
+  # "The first cat show was organized in 1871 in London. Cat shows later became    a worldwide craze.",
+  # "The first cartoon cat was Felix the Cat in 1919. In 1940, Tom and Jerry    starred in the first theatrical cartoon “Puss Gets the Boot.” In    1981 Andrew Lloyd Weber created the musical Cats, based on T.S.    Eliot’s Old Possum’s Book of Practical Cats.f",
+  # "The normal body temperature of a cat is between 100.5 ° and 102.5 °F.    A cat is sick if its temperature goes below 100 ° or above 103 °F.",
+  # "A cat has 230 bones in its body. A human has 206. A cat has no collarbone,    so it can fit through any opening the size of its head.",
+  # "A cat’s nose pad is ridged with a unique pattern, just like the fingerprint    of a human.",
+  # "If they have ample water, cats can tolerate temperatures up to 133 °F.",
+  # "Foods that should not be given to cats include onions, garlic, green tomatoes,    raw potatoes, chocolate, grapes, and raisins. Though milk is not toxic, it    can cause an upset stomach and gas. Tylenol and aspirin are extremely toxic    to cats, as are many common houseplants. Feeding cats dog food or canned    tuna that's for human consumption can cause malnutrition.",
+  # "A 2007 Gallup poll revealed that both men and women were equally likely    to own a cat.",
+  # "A cat’s heart beats nearly twice as fast as a human heart, at 110    to 140 beats a minute.",
+  # "

    Cat’s sweat only through their paws

  Cats don’t have sweat glands over their bodies like humans do. Instead,    they sweat only through their paws.",
+  # "In just seven years, a single pair of cats and their offspring could produce    a staggering total of 420,000 kittens.",
+  # "Relative to its body size, the clouded leopard has the biggest canines of    all animals' canines. Its dagger-like teeth can be as long as 1.8 inches    (4.5 cm).",
+  # "Cats spend nearly 1/3 of their waking hours cleaning themselves.",
+  # "Grown cats have 30 teeth. Kittens have about 26 temporary teeth, which they    lose when they are about 6 months old.",
+  # "A cat called Dusty has the known record for the most kittens. She had more    than 420 kittens in her lifetime.",
+  # "The largest cat breed is the Ragdoll. Male Ragdolls weigh between 12 and    20 lbs (5.4-9.0 k). Females weigh between 10 and 15 lbs (4.5-6.8 k).",
+  # "Cats are extremely sensitive to vibrations. Cats are said to detect earthquake    tremors 10 or 15 minutes before humans can.",
+  # "In contrast to dogs, cats have not undergone major changes during their    domestication process.",
+  # "A female cat is called a queen or a molly.",
+  # "In the 1930s, two Russian biologists discovered that color change in Siamese    kittens depend on their body temperature. Siamese cats carry albino genes    that work only when the body temperature is above 98° F. If these kittens    are left in a very warm room, their points won’t darken and they will    stay a creamy white.",
+  # "There are up to 60 million feral cats in the United States alone.",
+  # "The oldest cat to give birth was Kitty who, at the age of 30, gave birth    to two kittens. During her life, she gave birth to 218 kittens.",
+  # "The most traveled cat is Hamlet, who escaped from his carrier while on a    flight. He hid for seven weeks behind a pane. By the time he was discovered,    he had traveled nearly 373,000 miles (600,000 km).",
+  # "The most expensive cat was an Asian Leopard cat (ALC)-Domestic Shorthair    (DSH) hybrid named Zeus. Zeus, who is 90% ALC and 10% DSH, has an asking    price of £100,000 ($154,000).",
+  # "The cat who holds the record for the longest non-fatal fall is Andy. He    fell from the 16th floor of an apartment building (about 200 ft/.06 km) and    survived.",
+  # "The richest cat is Blackie who was left £15 million by his owner, Ben Rea.",
+  # "The claws on the cat’s back paws aren’t as sharp as the claws    on the front paws because the claws in the back don’t retract and,    consequently, become worn.",/
+#]


proper path resolving

proper path resolving

--- packages/tower-support/server.coffee
+++ packages/tower-support/server.coffee
@@ -1,4 +1,5 @@
 require './shared'
+_path = require('path')

 # @todo tmp hack
 require('pathfinder').File.glob = ->
@@ -8,5 +9,5 @@
     if @exists(path)
       found = require('wrench').readdirSyncRecursive(path)
       for item, index in found
-        result.push(path + '/' + item)
+        result.push(_path.resolve(path, './' + item))
   result


Pass optional fileId param to addFile

Pass optional fileId param to addFile

Optionally set `_id` calling `addFile` on the server by passing `fileId` param to `opts` object

--- files.coffee
+++ files.coffee
@@ -1496,6 +1496,9 @@
           userId:       opts.userId
           extension:    extension
           _storagePath: path.replace "#{nodePath.sep}#{opts.fileName}", ''
+
+        if opts.fileId
+          result._id = opts.fileId

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


fixing typo

fixing typo

--- source/quo.coffee
+++ source/quo.coffee
@@ -121,7 +121,7 @@
   _Quo:: = $$.fn = {}

   # Instance Methods
-  $$.fn.forEach = [].forEach
+  $$.fn.forEach = EMPTY_ARRAY.forEach

   $$.fn.indexOf = EMPTY_ARRAY.indexOf


small fixes as suggested by @janpaul123

small fixes as suggested by @janpaul123

--- js-library/app/js/views/fact.coffee
+++ js-library/app/js/views/fact.coffee
@@ -5,7 +5,7 @@
 delay_between_highlight_and_show_button_open = 325

 delay_before_mouseout_detected = 300
-
+delay_before_scroll_into_screen_detected = 200

 class Highlighter
   constructor: (@$elements, @class) ->
@@ -87,7 +87,7 @@
           @timeout_handler = setTimeout =>
             @switchToState 'visible'
           , highlight_time_on_in_view
-        , 200
+        , delay_before_scroll_into_screen_detected
       # else: we are in a visible state, and
       # the fact is visible, no need to change state
     else
@@ -96,7 +96,6 @@

   switchToState: (to_state) =>
     return if to_state == @state
-    console.info 'Switching', @fact.id, @state, to_state if @fact.id == '148'

     switch to_state
       when 'small_time_visible'
@@ -114,10 +113,9 @@
     @fact_load_promotion = new FactLoadPromotion(@elements)

   isInView: ->
-    all = true
-    $(@elements).each ->
-      all = all & ($(this).data('inview') == 'both')
-    all
+    for element in $(@elements)
+      return false unless $(element).data('inview') == 'both'
+    true

   openFactlinkModal: (options={})=>
     Factlink.showInfo @id, => options.success?()


Add addViews method to mirror destroyViews

--- src/packages/spell-check/lib/spell-check-view.coffee
+++ src/packages/spell-check/lib/spell-check-view.coffee
@@ -1,5 +1,4 @@
 {View} = require 'space-pen'
-$ = require 'jquery'
 _ = require 'underscore'
 SpellCheckTask = require './spell-check-task'
 MisspellingView = require './misspelling-view'
@@ -38,16 +37,17 @@
       view.destroy() for view in @views
       @views = []

-  addView: (misspelling)->
-    view = new MisspellingView(misspelling, @editor)
-    @views.push(view)
-    @append(view)
+  addViews: (misspellings) ->
+    for misspelling in misspellings
+      view = new MisspellingView(misspelling, @editor)
+      @views.push(view)
+      @append(view)

   updateMisspellings: ->
     @task?.abort()

     callback = (misspellings) =>
       @destroyViews()
-      @addView(misspelling) for misspelling in misspellings
+      @addViews(misspellings)
     @task = new SpellCheckTask(@buffer.getText(), callback)
     @task.start()


animations n sprites, oh my!

animations n sprites, oh my!

--- ng-modules/travelScreen/travelScreen.coffee
+++ ng-modules/travelScreen/travelScreen.coffee
@@ -26,6 +26,40 @@
         # returns true if tile has travelled left off screen
         return (@tileW + @x) < 0

+class Sprite
+    constructor: (spritesheet, x=0, y=0)->
+        # sets up new sprite using given spritesheet src centered at x & y position on canvas
+        @sheet = new Image()
+        @sheet.src = spritesheet
+        @h = 399
+        @w = 182
+        @x = x
+        @y = y
+        @frame_n = 0
+        @max_frames = 4
+        # for slowing animation speed
+        @draws_per_frame = 50  # number of draw calls before setting new frame
+        @draw_counter = 0
+
+    next_frame: ()->
+        @frame_n += 1
+        if @frame_n > @max_frames
+            @frame_n = 0
+
+    draw: (ctx, x=@x, y=@y) ->
+        # draws a sprite centered at given location, or uses internal
+        # set locations to start in sprite sheet
+        ssx = @frame_n * @w
+        ssy = 0  # TODO: use y-axis in spritesheets for different ship conditionals/permuations (damage, age, etc)
+        x = x - @w/2
+        y = y - @h/2
+        ctx.drawImage(@sheet, ssx, ssy, @w, @h, x, y, @w, @h)
+        @draw_counter += 1
+        if @draw_counter > @draws_per_frame
+            @next_frame()
+            @draw_counter = 0
+
+
 # switching to javascript here...
 `
 var app = angular.module('travel-screen', [
@@ -53,6 +87,7 @@
         vm.shipImg = document.getElementById("player-ship");

         vm.tiles = [new Tile(0, document.getElementById("sun-bg"))];
+        vm.sprites = {}
     }
     vm.init();
     $scope.$on('resetGame', vm.init);
@@ -84,18 +119,30 @@

     vm.drawSprite = function(location, Xposition){
         // draws location if in view at global Xposition
-        var spriteW = 288, spriteH = 500;
+        var spriteW = 500;  // max sprite width (for checking when to draw)

         // if w/in reasonable draw distance
         if (vm.x + window.innerWidth + spriteW > Xposition    // if close enough
             && vm.x - spriteW < Xposition                  ) { // if we haven't passed it
-            // TODO:
-            // if sprite already in current sprites
-            // use existing y value (add small bit of drift?)
-            // else
-            // get random y value and add to list of current sprites
-            var rel_x = Xposition-vm.x;  // position relative to window view
-            vm.ctx.drawImage(vm.stationImg, rel_x - spriteW / 2, 300 - spriteH / 2);  // TODO: try to use gifs appended to dom
+            if (location in vm.sprites){  // if sprite already in current sprites
+                var rel_x = Xposition-vm.x;
+                vm.sprites[location].x = rel_x
+                // use existing y value (add small bit of drift)
+                if (Math.random() < 0.01) {  // small chance of drift
+                    vm.sprites[location].y += Math.round(Math.random() * 2 - 1)
+                    if (vm.sprites[location].y > 400) {
+                        vm.sprites[location].y = 399
+                    }
+                    if (vm.sprites[location].y < 200) {
+                        vm.sprites[location].y = 201
+                    }
+                }
+                vm.sprites[location].draw(vm.ctx)
+            } else {
+                // get random y value and add to list of current sprites
+                vm.sprites[location] = new Sprite('/assets/sprites/station_sheet.png', -1000, Math.random()*200+200);
+            }
+            // TODO: remove sprites once we're done with them..
         }
     }


runner button is pretty

runner button is pretty

--- src/public/javascripts/worker.coffee
+++ src/public/javascripts/worker.coffee
@@ -38,6 +38,6 @@
     worker = new Worker(stateMapper)
     setTimeout () ->
       $('#a').click () ->
-        $('#a').hide()
+        $('#a').slideUp()
         worker.init()
     , 100


Instagram service : use jsonp where possible !

--- src/remote/services/instagram.coffee
+++ src/remote/services/instagram.coffee
@@ -3,24 +3,43 @@
     slice = Array.prototype.slice

     handler = (req, callback, errback)=>
+
+      method = req.method.toLowerCase()
       path = req.path
       path = path.substring(1) if (path[0] == "/")
-      url  = "/api/v1/services/instagram/" + path
+      requestParams = {}

-      if req.method.toLowerCase() == 'delete'
-        req_data = JSON.stringify(req.params || {})
+      instaConfig = app.config.services.settings.instagram_app
+
+      if method == 'get'
+        requestParams =
+          url: "https://api.instagram.com/v1/" + path
+          dataType: 'jsonp'
+          data: _.extend({}, req.params, instaConfig)
+          type: 'get'
+        request = $.ajax(requestParams)
+        request.done (response)->
+          callback({ response: response.data, provider: 'instagram', pagination: response.pagination })
+        request.fail(errback)
+
       else
-        req_data = req.params
+        url  = "/api/v1/services/instagram/" + path
+        if req.method.toLowerCase() == 'delete'
+          req_data = JSON.stringify(req.params || {})
+        else
+          req_data = req.params

-      request = $.ajax
-        url: url
-        type: req.method
-        data: req_data
-        headers:
-          'Hull-App-Id': app.config.appId
+        requestParams =
+          url: url
+          type: req.method
+          data: req_data
+          headers:
+            'Hull-App-Id': app.config.appId

-      request.done((response)-> callback({ response: response, provider: 'instagram' }))
-      request.fail(errback)
+        request = $.ajax(requestParams)
+        request.done (response)->
+          callback({ response: response, provider: 'instagram' })
+        request.fail(errback)

       return


Assert.

Assert.

--- lib.coffee
+++ lib.coffee
@@ -215,6 +215,8 @@
   # The order of components is arbitrary and does not necessary match siblings relations in DOM.
   # Returns children which pass a predicate function.
   childComponentsWith: (propertyOrMatcherOrFunction) ->
+    assert propertyOrMatcherOrFunction
+
     propertyOrMatcherOrFunction = createMatcher propertyOrMatcherOrFunction

     results = new ComputedField =>


Plain HTTP.post works for Dump all DB function as well

--- client/serialize.coffee
+++ client/serialize.coffee
@@ -112,22 +112,20 @@

 #      console.log(multipartRequestBody,headers);

-      req = gapi.client.request
-        'path': '/upload/drive/v2/files'
-        'method': 'POST'
-        'params': {'uploadType': 'multipart'}
-        'headers': headers
-        'body': multipartRequestBody,
-      req.execute (res) ->
-          console.log res
-          if res.id
+      HTTP.post(
+        'https://www.googleapis.com/upload/drive/v2/files?uploadType=multipart&convert=false',
+        {content: multipartRequestBody, headers: headers}, (err,res) ->
+          console.log(err,res);
+          id = res.data.id;
+          if id
             addLog
               type: 'database'
               op: 'dumpall'
               params:
-                gdrive_id: res.id
-            url = getGDriveFileUrl(res.id)
-            callback {url: url,success:true, id:res.id}
+                gdrive_id: id
+            url = getGDriveFileUrl(id)
+            callback({url: url,success:true, id: id});
           else
-            callback {success: false, error: 'Insert failed.'}
+            callback({success: false, error: 'Insert failed.'});
+      )


FIX: Comment explaining teaching period state

--- src/app/admin/states/teachingperiods/teachingperiods.coffee
+++ src/app/admin/states/teachingperiods/teachingperiods.coffee
@@ -1,10 +1,7 @@
 angular.module('doubtfire.admin.states.teachingperiods', [])

 #
-# Convenors of a unit(s) can see a list of all the units they convene
-# in this view and make changes to those units.
-#
-# Users with an Administrator system role can create new units.
+# Users with an Administrator system role can create new Teaching Periods.
 #
 .config((headerServiceProvider) ->
   teachingPeriodsAdminViewStateData =


add test

add test


Added convenience method @validatesConfirmationOf

--- src/model.js.coffee
+++ src/model.js.coffee
@@ -62,6 +62,9 @@

     @validatesServerSideOf: ( attr, opts ) ->
       @validatesWith Lib.Validators.ServerSideValidator, attr, opts
+
+    @validatesConfirmationOf: ( attr, opts ) ->
+      @validatesWith Lib.Validators.ConfirmationValidator, attr, opts

     @validates: ( attr, validations ) ->
       for type, opts of validations


Specify max stream connections count in environment variable

--- lib/controllers/event-stream.coffee
+++ lib/controllers/event-stream.coffee
@@ -36,4 +36,5 @@
                 @emit "message:team#{teamId}", @format eventId, name, 5000, dataForTeam

-module.exports = new EventStream 1024
+maxStreamConnections = if process.env.MAX_STREAM_CONNECTIONS? then parseInt(process.env.MAX_STREAM_CONNECTIONS, 10) else 1024
+module.exports = new EventStream maxStreamConnections


:egg::secret: Updated in browser at strd6.github.io/editor

--- pixie.cson
+++ pixie.cson
@@ -1,4 +1,4 @@
-version: "0.2.1-pre.1"
+version: "0.2.1"
 entryPoint: "haml-jr"
 remoteDependencies: [
   "https://cdnjs.cloudflare.com/ajax/libs/coffee-script/1.6.3/coffee-script.min.js"


Issue #23: simplify characters and strings regexes in the Atom plugin

--- tools/atom-plugin/language-micro/grammars/micro.cson
+++ tools/atom-plugin/language-micro/grammars/micro.cson
@@ -20,7 +20,7 @@
 	}
 	{
 		'comment': 'Symbols'
-		'match': "'(?:(\\\\['\\\\tn])|[^'\\n])'"
+		'match': "'(?:(\\\\.)|[^'])'"
 		'captures':
 			'0':
 				'name': 'string.quoted.single.micro'
@@ -35,7 +35,7 @@
 		'patterns': [
 			{
 				'comment': 'Escaped symbol'
-				'match': '\\\\["\\\\tn]'
+				'match': '\\\\.'
 				'name': 'constant.character.escape.micro'
 			}
 		]


Issue #39: add the inln function to the Atom plugin

--- tools/atom-plugin/language-micro/grammars/micro.cson
+++ tools/atom-plugin/language-micro/grammars/micro.cson
@@ -47,7 +47,7 @@
 	}
 	{
 		'comment': 'Built-in functions (it should be described before functions)'
-		'match': '\\b(nil|false|true|num|type|arity|apply|_|floor|ceil|trunc|round|sin|cos|tn|arcsin|arccos|arctn|angle|pow|sqrt|exp|ln|lg|abs|random|head|tail|keys|if|size|str|strb|strs|strl|strh|strhh|args|env|time|in|out|outln|err|errln|exit)\\b'
+		'match': '\\b(nil|false|true|num|type|arity|apply|_|floor|ceil|trunc|round|sin|cos|tn|arcsin|arccos|arctn|angle|pow|sqrt|exp|ln|lg|abs|random|head|tail|keys|if|size|str|strb|strs|strl|strh|strhh|args|env|time|in|inln|out|outln|err|errln|exit)\\b'
 		'name': 'variable.function.micro'
 	}
 	{


add name to course

add name to course

--- tutor/test/components/cc-dashboard/index.spec.coffee
+++ tutor/test/components/cc-dashboard/index.spec.coffee
@@ -8,7 +8,7 @@
 BaseModel = require '../../../api/courses/1/cc/dashboard.json'
 ExtendBaseStore = (props) -> _.extend({}, BaseModel, props)

-BlankCourse = ExtendBaseStore(course: periods: [])
+BlankCourse = ExtendBaseStore(course: periods: [], name:'Blank!')

 IDS =
   BLANK: '0'


Adds hooks on minimap updates

Adds hooks on minimap updates

--- lib/minimap-git-diff-binding.coffee
+++ lib/minimap-git-diff-binding.coffee
@@ -14,6 +14,7 @@
     @gitDiff = require(@gitDiffPackage.path)

     @subscribe @editorView, 'editor:path-changed', @subscribeToBuffer
+    @subscribe @minimap, 'activated', @subscribeToMinimapView
     @subscribe @editorView, 'editor:contents-modified', @renderDiffs
     @subscribe atom.project.getRepo(), 'statuses-changed', =>
       @scheduleUpdate()
@@ -90,3 +91,10 @@

     if @buffer = @editor.getBuffer()
       @buffer.on 'contents-modified', @updateDiffs
+
+  subscribeToMinimapView: =>
+    return unless @editorView.getPane()?
+    minimapView = @minimap.minimapForEditorView(@editorView)
+
+    @subscribe minimapView.miniEditorView, 'minimap:updated', =>
+      @updateDiffs()


Use event-kit subscriptions for plugins management

--- lib/mixins/plugin-management.coffee
+++ lib/mixins/plugin-management.coffee
@@ -1,4 +1,5 @@
 Mixin = require 'mixto'
+{CompositeDisposable} = require 'event-kit'

 # Public: Provides methods to manage minimap plugins.
 #
@@ -15,6 +16,7 @@
 class PluginManagement extends Mixin
   # Internal: Stores the minimap plugin with their identifying name as key.
   plugins: {}
+  pluginsSubscriptions: {}

   # Public: Registers a minimap `plugin` with the given `name`.
   #
@@ -24,6 +26,7 @@
   # plugin - The plugin {Object} to register.
   registerPlugin: (name, plugin) ->
     @plugins[name] = plugin
+    @pluginsSubscriptions[name] = new CompositeDisposable

     event = {name, plugin}
     @emit('plugin:added', event)
@@ -81,12 +84,15 @@

     atom.config.set(settingsKey, true) unless atom.config.get(settingsKey)?

-    atom.config.observe settingsKey, =>
+    @pluginsSubscriptions[name].add atom.config.observe settingsKey, =>
       @updatesPluginActivationState(name)

-    atom.workspaceView.command "minimap:toggle-#{name}", =>
+    commands = {}
+    commands["minimap:toggle-#{name}"] = =>
       atom.config.set settingsKey, not atom.config.get(settingsKey)
       @updatesPluginActivationState(name)
+
+    @pluginsSubscriptions[name].add atom.commands.add 'atom-workspace', commands

   # Internal: When the `minimap.displayPluginsControls` setting is toggled,
   # this function will unregister the commands and setting that was created
@@ -94,6 +100,6 @@
   #
   # name - The identifying {String} name of the plugin.
   unregisterPluginControls: (name) ->
-    atom.config.unobserve "minimap.plugins.#{name}"
-    atom.workspaceView.off "minimap:toggle-#{name}"
+    @pluginsSubscriptions[name].dispose()
+    delete @pluginsSubscriptions[name]
     delete @config.plugins.properties[name]


-Fixed indent error in js file

-Fixed indent error in js file

--- vendor/assets/javascripts/ajax_nested_form.js.coffee
+++ vendor/assets/javascripts/ajax_nested_form.js.coffee
@@ -9,7 +9,7 @@

 	@addFields: (link, association, content, container, position) ->
 		new_id = new Date().getTime()
-    regexp = new RegExp("new_" + association, "g")
-    content_new = content.replace(regexp, new_id)
-    if position is "first" then $(container).prepend(content_new) else $(container).append(content_new)
+		regexp = new RegExp("new_" + association, "g")
+		content_new = content.replace(regexp, new_id)
+		if position is "first" then $(container).prepend(content_new) else $(container).append(content_new)
 		return


changed default image to be a random unicorn

--- vendor/assets/javascripts/overlay_me/overlays.coffee
+++ vendor/assets/javascripts/overlay_me/overlays.coffee
@@ -21,7 +21,18 @@
   OverlayMe.loadDefaultImage = ->
     # double check that the dynamic loading list is also empty
     if OverlayMe.dyn_manager.isEmpty()
-      OverlayMe.dyn_manager.addImage('https://a248.e.akamai.net/assets.github.com/images/modules/about_page/octocat.png')
+      unicorns = [
+        "http://img.photobucket.com/albums/v629/Master_Becca/unicorn.png",
+        "http://fc07.deviantart.net/fs49/f/2009/200/b/3/Fat_Unicorn_and_the_Rainbow_by_la_ratta.jpg",
+        "http://www.deviantart.com/download/126388773/Unicorn_Pukes_Rainbow_by_Angel35W.jpg",
+        "http://macmcrae.com/wp-content/uploads/2010/02/unicorn.jpg",
+        "http://www.deviantart.com/download/195169729/rainbow_farting_unicorn_by_hi_my_name_is_light-d3875xd.jpg",
+        "http://edwardhotspur.files.wordpress.com/2012/01/rainbowunicorn.jpg",
+        "https://s.deviantart.com/th/fs18/f/2007/220/1/3/Unicorn_Vomit___Finished___by_Rainbow_Colors_Club.jpg",
+        "http://4.bp.blogspot.com/-uPLiez-m9vY/TacC_Bmsn3I/AAAAAAAAAyg/jusQIA8aAME/s1600/Behold_A_Rainbow_Unicorn_Ninja_by_Jess4921.jpg",
+        "http://www.everquestdragon.com/everquestdragon/main/image.axd?picture=2009%2F9%2FPaperPaperNewrainbow.png"
+      ]
+      OverlayMe.dyn_manager.addImage(unicorns[Math.floor(Math.random()*unicorns.length)])

   # adding all overlay images
   $.ajax


set currentGroupId global on login-success in boot.coffee

--- app/boot.coffee
+++ app/boot.coffee
@@ -16,4 +16,6 @@

   $rootScope.$on 'auth:login-success', (event, user) ->
     authSuccess(event, user).then ->
-      $location.path('/groups/1')+      # TODO: later, this will be the first group that the user is a member of
+      global.cobudgetApp.currentGroupId = 1
+      $location.path("/groups/#{global.cobudgetApp.currentGroupId}")


TestClient is now an event emitter, and attaches itself to its window.

--- src/browser/test_client.coffee
+++ src/browser/test_client.coffee
@@ -1,6 +1,7 @@
+EventEmitter = require('events').EventEmitter
 bootstrap    = require('../client/dnode_client')

-class TestClient
+class TestClient extends EventEmitter
     constructor : (id, dom) ->
         @id = id
         # Make sure we get a fresh JSDOM, not one that has been augmented with
@@ -8,6 +9,9 @@
         @jsdom = dom.getFreshJSDOM()
         @document = @jsdom.jsdom()
         @window = @document.parentWindow
+        # Attach this testClient to the window, so that the client code can
+        # emit events on the testClient (like 'testDone')
+        @window.testClient = this
         @window.__envSessionID = id
         bootstrap(@window, @document)


A row per entry

A row per entry

--- src/pg-brain.coffee
+++ src/pg-brain.coffee
@@ -8,9 +8,10 @@
 #   Run the following SQL to setup the table and column for storage.
 #
 #   CREATE TABLE brain (
-#     key TEXT unique,
+#     key TEXT,
+#     type TEXT,
 #     value JSON default '{}'::json,
-#     CONSTRAINT brain_pkey PRIMARY KEY (key)
+#     CONSTRAINT brain_pkey PRIMARY KEY (key, type)
 #   )
 #
 # Author:
@@ -30,23 +31,24 @@
   client.connect()
   robot.logger.debug "pg-brain connected to #{database_url}."

-  query = client.query("SELECT key, value FROM brain")
-  query.on 'row', (row) ->
+  query = client.query "SELECT key, type, value FROM brain", (err, result) =>
+    if(err)
+      return robot.logger.error('error running query', err)
     data = {}
-    data[row.key] = value
+    for row in result.rows
+      data[row.type] ||= {}
+      data[row.type][row.key] = row.value
     robot.brain.mergeData data
-    robot.logger.debug "pg-brain loaded. #{row.key}"
-
-  client.on "error", (err) ->
-    robot.logger.error err
+    robot.logger.debug "pg-brain loaded."

   robot.brain.on 'save', (data) ->
-    for key, value of data
-      query = client.query("INSERT INTO brain(key, value)  VALUES ($1, $2)", [key, value])
-      query.on "error", (err) ->
-        console.log err
-          query = client.query("UPDATE brain SET value = $2 WHERE key = $1", [key, value])
-      robot.logger.debug "pg-brain saved. #{key}"
+    for type, keyValue of data
+      for key, value of keyValue
+        query = client.query("INSERT INTO brain(key, type, value)  VALUES ($1, $2, $3)", [key, type, value])
+        query.on "error", (err) ->
+          console.log err
+            query = client.query("UPDATE brain SET value = $3 WHERE key = $1 AND type = $2", [key, type, value])
+    robot.logger.debug "pg-brain saved. #{key}"

   robot.brain.on 'close', ->
     client.end()


improving regexp a little more

improving regexp a little more

--- src/toaster/grains/script.coffee
+++ src/toaster/grains/script.coffee
@@ -221,22 +221,23 @@
 				namespace = filefolder.replace(/\//g, ".").slice 0, -1

 				# validate if there is a class inside the file
-				rgx = "(class)+\\s+(\\w+)(\\s*$|\\s+(extends)\\s+(\\w+)\\s*$)"
+				rgx = "(^|=\\s*)(class)+\\s+(\\w+)" +
+					  "(\\s*$|\\s+(extends)\\s+(\\w+)\\s*$)"

 				# validate if there is a class extends inside the file
-				rgx_ext = /(class)+\s+(\w+)\s+(extends)\s+(\w+)\s*$/m
+				rgx_ext = /(^|=\s*)(class)+\s+(\w+)\s+(extends)\s+(\w+)\s*$/m

 				# if there is a class inside the file
 				if raw.match( new RegExp rgx, "m" )?
-
+
 					# if the file is not in the root src folder (outside any
 					# folder/package )
 					if namespace != ""
 						# then modify the class declarations before starting
 						# the parser thing, adding the package the headers
 						# declarations
-						repl = "pkg( '#{namespace}' ).$2 = $1 $2"
-						repl += "$3" if rgx_ext.test raw
+						repl = "pkg( '#{namespace}' ).$3 = $2 $3"
+						repl += "$4" if rgx_ext.test raw
 						raw = raw.replace new RegExp( rgx, "gm" ), repl

 					# assemble some more infos about the file.
@@ -283,6 +284,7 @@

 					dependencies:  dependencies
 				}
+
 			# finally executes callback, passing the collected buffer
 			cb buffer


use go to step so that url updates as well

--- src/components/task/index.cjsx
+++ src/components/task/index.cjsx
@@ -49,7 +49,7 @@
     # Instead, reset the refreshFor state
     # and advance step to the next step after.
     @untrackRefreshStep()
-    @setState({currentStep: nextState.currentStep + 1})
+    @goToStep(nextState.currentStep + 1)()
     return false

   trackRefreshStep: ->


Fix deprecations in spinner.

Fix deprecations in spinner.

--- test/javascripts/components/progress_spinner_test.js.coffee
+++ test/javascripts/components/progress_spinner_test.js.coffee
@@ -4,6 +4,6 @@

 test 'spins', ->
   spinner = @subject()
-  equal spinner.state, 'preRender'
+  equal spinner._state, 'preRender'
   @append()
-  equal spinner.state, 'inDOM'
+  equal spinner._state, 'inDOM'


Upload files in order

Upload files in order

Fix #115: upload all files in parallel, but make the messages appear in
order by file.


Fix focus-on-scroll behavior

Fix focus-on-scroll behavior

Sometimes caused scrolling to go up and back down (specifically, when
scrolling to a message with a descendant in editing mode).


no need for keymaps anymore

no need for keymaps anymore

--- keymaps/compatible.cson
+++ keymaps/compatible.cson
@@ -1,63 +0,0 @@
-# Keybindings require three things to be fully defined: A selector that is
-# matched against the focused element, the keystroke and the command to
-# execute.
-#
-# Below is a basic keybinding which registers on all platforms by applying to
-# the root workspace element.
-
-# For more detailed documentation see
-# https://atom.io/docs/latest/advanced/keymaps
-
-# fix at-sign and backslash
-# ------------------------------------------------------------------------------
-# unset previous bindings
-'.platform-win32 atom-workspace atom-text-editor:not([mini])':
-  'ctrl-alt-[': 'unset!' # 'editor:fold-current-row'
-
-'.platform-win32 atom-text-editor':
-  'ctrl-alt-q': 'unset!' # 'autoflow:reflow-selection'
-
-'.platform-win32 atom-workspace atom-text-editor':
-  'ctrl-alt-q': 'compatible:at'
-  'ctrl-alt-[': 'compatible:backslash'
-
-# fix atom core bindings
-# ------------------------------------------------------------------------------
-# toggle line comments
-'.platform-win32 atom-workspace atom-text-editor:not([mini])':
-  'ctrl-/': 'unset!'
-'.platform-win32 atom-text-editor':
-  'ctrl-7': 'editor:toggle-line-comments'
-
-# fix vim-mode bindings
-# ------------------------------------------------------------------------------
-'.platform-win32 atom-text-editor.vim-mode:not(.insert-mode)':
-  '}': 'unset!' # 'vim-mode:move-to-next-paragraph'
-  '{': 'unset!' # 'vim-mode:move-to-previous-paragraph'
-  '^': 'unset!' # 'vim-mode:move-to-first-character-of-line'
-  '+': 'unset!' # 'vim-mode:move-to-first-character-of-line-down'
-  '=': 'unset!' # 'vim-mode:auto-indent'
-  '~': 'unset!' # 'vim-mode:toggle-case'
-  ';': 'unset!' # 'vim-mode:repeat-find'
-  '`': 'unset!' # 'vim-mode:move-to-mark-literal'
-  '\'': 'unset!' # 'vim-mode:move-to-mark'
-  '/': 'unset!' # 'vim-mode:search'
-  '?': 'unset!' # 'vim-mode:revese-search'
-  '*': 'unset!' # 'vim-mode:search-current-word'
-  '#': 'unset!' # 'vim-mode:reverse-search-current-word'
-
-  # indend and outdent <> wont work. They fire the same key ...
-
-  'ctrl-alt-0': 'vim-mode:move-to-next-paragraph'
-  'ctrl-alt-7': 'vim-mode:move-to-previous-paragraph'
-  '\\': 'vim-mode:move-to-first-character-of-line'
-  '=': 'vim-mode:move-to-first-character-of-line-down'
-  ')': 'vim-mode:auto-indent'
-  'ctrl-alt-=': 'vim-mode:toggle-case'
-  '<': 'vim-mode:repeat-find'
-  '}': 'vim-mode:move-to-mark-literal'
-  '?': 'vim-mode:move-to-mark'
-  '&': 'vim-mode:search'
-  '{': 'vim-mode:revese-search'
-  '+': 'vim-mode:search-current-word'
-  '/': 'vim-mode:reverse-search-current-word'


Keybindings for wrap in cmd/env

Keybindings for wrap in cmd/env

--- keymaps/latextools.cson
+++ keymaps/latextools.cson
@@ -15,6 +15,8 @@
   'ctrl-alt-b': 'latextools:build'
   'ctrl-l j': 'latextools:jump-to-pdf'
   'ctrl-l x': 'latextools:ref-cite-complete'
+  'ctrl-l ctrl-c': 'latextools:wrap-in-command'
+  'ctrl-l ctrl-n': 'latextools:wrap-in-environment'

 ".platform-darwin atom-text-editor[data-grammar='text tex latex']":
   'cmd-l cmd-l': 'editor:select-line'
@@ -24,3 +26,5 @@
   'cmd-alt-b': 'latextools:build'
   'cmd-l j': 'latextools:jump-to-pdf'
   'cmd-l x': 'latextools:ref-cite-complete'
+  'cmd-l cmd-c': 'latextools:wrap-in-command'
+  'cmd-l cmd-n': 'latextools:wrap-in-environment'


add n/N for repeating last find

add n/N for repeating last find


Should be compatible with new Selection type


Ensure logout works even with invalid credentials, or if not logged in

Before this point, if you had an invalid token, an expired token, or a
token for a different site, you couldn't log out to clear it properly.

Not a big deal, but awkward and messy, and easily fixed.

Change-Type: patch

--- lib/actions/auth.coffee
+++ lib/actions/auth.coffee
@@ -130,7 +130,6 @@

 			$ resin logout
 	'''
-	permission: 'user'
 	action: (params, options, done) ->
 		resin = require('resin-sdk-preconfigured')
 		resin.auth.logout().nodeify(done)


feature: mouse factor

feature: mouse factor

--- lib/editor-background.coffee
+++ lib/editor-background.coffee
@@ -64,16 +64,22 @@
       default:true
       desctiption:"Transparent background under file tabs"
       order:7
+    mouseFactor:
+      type:"integer"
+      default: 4
+      description: "move background with mouse (higher value = slower)"
+      order:8
     textShadow:
       type:"string"
-      default:"0px 5px 2px rgba(0, 0, 0, 0.52)"
+      #default:"0px 5px 2px rgba(0, 0, 0, 0.52)"
+      default:"0px 0px 1px rgba(0,0,0,1),0px 0px 3px #000,0px 0px 3px #000"
       description:"Add a little text shadow to code"
-      order:8
+      order:9
     style:
       type:"string"
       default:"background:radial-gradient(rgba(0,0,0,0) 30%,rgba(0,0,0,0.75));"
       description:"Your custom css rules :]"
-      order:9
+      order:10
     boxDepth:
       type:"integer"
       default: 500
@@ -146,12 +152,15 @@
     boxStyle

   mouseMove: (ev) ->
+    conf=atom.config.get('editor-background')
     @mouseX=ev.pageX
     @mouseY=ev.pageY
+    if conf.boxDepth>0 then @updateBox() else @updateBgPos()

   activateMouseMove: ->
-    window.addEventListener 'mousemove',(ev) =>  @mouseMove.apply @,[ev]
+    body = document.querySelector 'body'
+    body.addEventListener 'mousemove',(ev) =>  @mouseMove.apply @,[ev]

   initialize: ->
@@ -169,7 +178,8 @@
     loaded = (@elements[k] for k in keys when @elements[k]?)

     if loaded.length == keys.length
-      @activateMouseMove()
+      conf=atom.config.get('editor-background')
+      if conf.mouseFactor>0 then @activateMouseMove()
       @elements.plane = document.createElement('div')
       @elements.plane.style.cssText = planeInitialCss
       @elements.body.insertBefore @elements.plane,@elements.body.childNodes[0]
@@ -183,16 +193,28 @@

       @colors.workspaceBgColor=style(@elements.editor).backgroundColor
       @colors.treeOriginalRGB=style(@elements.treeView).backgroundColor
-      console.log @colors
       @packagesLoaded = true
       @applyBackground.apply @
     else
       setTimeout (=>@initialize.apply @),1000

+  updateBgPos: ->
+    conf = atom.config.get('editor-background')
+    body = qr 'body'
+    factor = conf.mouseFactor
+    polowaX = body.clientWidth // 2
+    polowaY = body.clientHeight // 2
+    offsetX =  @mouseX - polowaX
+    offsetY =  @mouseY - polowaY
+    x = (offsetX // factor)
+    y = (offsetY // factor)
+    inline @elements.bg,"background-position:#{x}px #{y}px !important;"
+

   updateBox: (depth) ->
+    conf=atom.config.get('editor-background')
+    if not depth? then depth = conf.boxDepth
     depth2 = depth // 2
-    conf=atom.config.get('editor-background')
     background=conf.imageURL
     opacity=(conf.boxOpacity / 100).toFixed(2)
     range=conf.boxRange
@@ -201,12 +223,17 @@
     if bgSize=='manual' then bgSize=conf.manualBackgroundSize
     if bgSize=='original' then bgSize='auto'
     body = qr 'body'
-    x=body.style.width - @mouseX
-    y=@mouseY
+    factor = conf.mouseFactor
+    polowaX = body.clientWidth // 2
+    polowaY = body.clientHeight // 2
+    offsetX =  @mouseX - polowaX
+    offsetY =  @mouseY - polowaY
+    x = polowaX + (offsetX // factor)
+    y = polowaY + (offsetY // factor)
     boxCss="
     .eb-box-wrapper{
       perspective:1000px;
-      perspective-origin:50% 50%;
+      perspective-origin:#{x}px #{y}px;
       position:fixed;
       top:0;
       left:0;
@@ -233,7 +260,7 @@
       left:0;
     }
     .eb-top{
-      transform: translate3d(0,-50%,0) rotateX(-90deg);
+      transform: translate3d(0,-50%,0) rotateX(90deg);
       top:0;
     }
     .eb-right{
@@ -241,7 +268,7 @@
       right:0;
     }
     .eb-bottom{
-      transform: translate3d(0,50%,0) rotateX(90deg);
+      transform: translate3d(0,50%,0) rotateX(-90deg);
       bottom:0;
     }
     .eb-back{
@@ -251,14 +278,9 @@
       height:100%;
     }
     "
-    console.log 'elements',@elements
     @elements.boxStyle.innerText = boxCss
     if depth==0
       @elements.boxStyle.innerText=".eb-box-wrapper{display:none;}"
-
-
-  deactivate: ->
-    @subscriptions.dispose()

   applyBackground: ->
     atom.workspaceView.addClass 'editor-background'
@@ -290,7 +312,10 @@
         @elements.css.innerText="atom-text-editor::shadow .line{text-shadow:"+
         conf.textShadow+" !important;}"

-      @updateBox conf.boxDepth
+      if conf.boxDepth>0
+        @updateBox conf.boxDepth
+      else
+        @elements.boxStyle.innerText=".eb-box-wrapper{display:none;}"

       if conf.backgroundSize!='original'
         inline @elements.bg, 'background-size:'+conf.backgroundSize+


support untitled files

support untitled files

--- lib/editor/highlights.coffee
+++ lib/editor/highlights.coffee
@@ -1,8 +1,13 @@
 module.exports =
+  matchesPath: (ed, path) ->
+    ed.getPath() == path ||
+      (!ed.getPath() &&
+        ed.getBuffer().inkId.toString() == path.match(/untitled-(\d*)/)?[1])
+
   observeLines: (ls, f) ->
-    atom.workspace.observeTextEditors (ed) ->
+    atom.workspace.observeTextEditors (ed) =>
       for l in ls
-        if l.file == ed.getPath()
+        if @matchesPath ed, l.file
           f ed, l

   errorLines: (ls) ->


More consistent use of ' instead of "

More consistent use of ' instead of "

--- lib/git-plus-commands.coffee
+++ lib/git-plus-commands.coffee
@@ -19,28 +19,28 @@

   if atom.project.getRepo()?
     atom.workspaceView.unbind 'git-plus:init'
-    atom.workspaceView.command "git-plus:commit", -> GitCommit()
-    atom.workspaceView.command "git-plus:commit-amend", -> GitCommitAmend()
-    atom.workspaceView.command "git-plus:add", -> GitAdd()
-    atom.workspaceView.command "git-plus:checkout-current-file", -> GitCheckoutCurrentFile()
-    atom.workspaceView.command "git-plus:diff", -> GitDiff()
-    atom.workspaceView.command "git-plus:diff-all", -> GitDiffAll()
-    atom.workspaceView.command "git-plus:add-all", -> GitAdd(true)
-    atom.workspaceView.command "git-plus:checkout", -> GitBranch.gitBranches()
-    atom.workspaceView.command "git-plus:new-branch", -> GitBranch.newBranch()
-    atom.workspaceView.command "git-plus:pull", -> GitPull()
-    atom.workspaceView.command "git-plus:push", -> GitPush()
-    atom.workspaceView.command "git-plus:fetch", -> GitFetch()
-    atom.workspaceView.command "git-plus:add-and-commit", -> GitAddAndCommit()
-    atom.workspaceView.command "git-plus:add-all-and-commit", -> GitAddAllAndCommit()
-    atom.workspaceView.command "git-plus:remove", -> GitRemove(true)
-    atom.workspaceView.command "git-plus:remove-current-file", -> GitRemove()
-    atom.workspaceView.command "git-plus:log", -> GitLog()
-    atom.workspaceView.command "git-plus:log-current-file", -> GitLog(true)
-    atom.workspaceView.command "git-plus:show", -> GitShow()
-    atom.workspaceView.command "git-plus:tags", -> GitTags()
+    atom.workspaceView.command 'git-plus:commit', -> GitCommit()
+    atom.workspaceView.command 'git-plus:commit-amend', -> GitCommitAmend()
+    atom.workspaceView.command 'git-plus:add', -> GitAdd()
+    atom.workspaceView.command 'git-plus:checkout-current-file', -> GitCheckoutCurrentFile()
+    atom.workspaceView.command 'git-plus:diff', -> GitDiff()
+    atom.workspaceView.command 'git-plus:diff-all', -> GitDiffAll()
+    atom.workspaceView.command 'git-plus:add-all', -> GitAdd(true)
+    atom.workspaceView.command 'git-plus:checkout', -> GitBranch.gitBranches()
+    atom.workspaceView.command 'git-plus:new-branch', -> GitBranch.newBranch()
+    atom.workspaceView.command 'git-plus:pull', -> GitPull()
+    atom.workspaceView.command 'git-plus:push', -> GitPush()
+    atom.workspaceView.command 'git-plus:fetch', -> GitFetch()
+    atom.workspaceView.command 'git-plus:add-and-commit', -> GitAddAndCommit()
+    atom.workspaceView.command 'git-plus:add-all-and-commit', -> GitAddAllAndCommit()
+    atom.workspaceView.command 'git-plus:remove', -> GitRemove(true)
+    atom.workspaceView.command 'git-plus:remove-current-file', -> GitRemove()
+    atom.workspaceView.command 'git-plus:log', -> GitLog()
+    atom.workspaceView.command 'git-plus:log-current-file', -> GitLog(true)
+    atom.workspaceView.command 'git-plus:show', -> GitShow()
+    atom.workspaceView.command 'git-plus:tags', -> GitTags()
   else
-    atom.workspaceView.command "git-plus:init", -> GitInit()
+    atom.workspaceView.command 'git-plus:init', -> GitInit()
     atom.workspaceView.unbind 'git-plus:commit'
     atom.workspaceView.unbind 'git-plus:commit-amend'
     atom.workspaceView.unbind 'git-plus:add'


Port PR #305 to 5.5 branch

Port PR #305 to 5.5 branch

--- lib/git-plus-commands.coffee
+++ lib/git-plus-commands.coffee
@@ -49,7 +49,7 @@
       commands.push ['git-plus:commit-amend', 'Commit Amend', -> GitCommitAmend(repo)]
       commands.push ['git-plus:add-and-commit', 'Add And Commit', -> git.add(repo, file: currentFile).then -> GitCommit(repo)]
       commands.push ['git-plus:add-all-and-commit', 'Add All And Commit', -> git.add(repo).then -> GitCommit(repo)]
-      commands.push ['git-plus:add-all-commit-and-push', 'Add All Commit And Push', -> git.add(repo).then -> GitCommit(repo, andPush: true)]
+      commands.push ['git-plus:add-all-commit-and-push', 'Add All, Commit And Push', -> git.add(repo).then -> GitCommit(repo, andPush: true)]
       commands.push ['git-plus:checkout', 'Checkout', -> GitBranch.gitBranches(repo)]
       commands.push ['git-plus:checkout-remote', 'Checkout Remote', -> GitBranch.gitRemoteBranches(repo)]
       commands.push ['git-plus:new-branch', 'Checkout New Branch', -> GitBranch.newBranch(repo)]


Go back to the old way of invoking wptidy on mac/linux

--- lib/wp-phptidy.coffee
+++ lib/wp-phptidy.coffee
@@ -23,6 +23,6 @@
 				);

 			else
-		  		child_process.exec('php ' + __dirname + '/../vendor/wp-phptidy.php replace ' + filepath)
+		  		child_process.exec(__dirname + '/../vendor/wp-phptidy.php replace ' + filepath)


Fix #260

Fix #260

--- lib/models/git-commit.coffee
+++ lib/models/git-commit.coffee
@@ -142,10 +142,14 @@

   destroyCommitEditor: ->
     @cleanup()
-    atom.workspace.getTextEditors().some (editor) ->
-      if editor.getURI().includes 'COMMIT_EDITMSG'
-        editor.destroy()
-        return true
+    atom.workspace.getPanes().some (pane) ->
+      pane.getItems().some (paneItem) ->
+        if paneItem.getURI().includes 'COMMIT_EDITMSG'
+          if pane.getItems().length is 1
+            pane.destroy()
+          else
+            paneItem.destroy()
+          return true

   # Public: Undo the amend
   #


fixing typo

fixing typo

--- client/views/googleplus/googleplus.coffee
+++ client/views/googleplus/googleplus.coffee
@@ -10,11 +10,11 @@
     # Schema tags
     #
     description = data.googleplus?.description || data.excerpt || data.description || data.summary
-    url = data.url || Shareit.location.origin() + Shareit.location.pathname()
+    url = data.url || ShareIt.location.origin() + ShareIt.location.pathname()
     title = data.title
     itemtype = data.googleplus?.itemtype || 'Article'
     $('html').attr('itemscope', '').attr('itemtype', "http://schema.org/#{itemtype}")
-    $('<meta>', { itemprop: 'name', content: Shareit.location.hostname() }).appendTo 'head'
+    $('<meta>', { itemprop: 'name', content: ShareIt.location.hostname() }).appendTo 'head'
     $('<meta>', { itemprop: 'url', content: url }).appendTo 'head'
     $('<meta>', { itemprop: 'description', content: description }).appendTo 'head'

@@ -25,7 +25,7 @@
         img = data.thumbnail
     if img
       if not /^http(s?):\/\/+/.test(img)
-        img = Shareit.location.origin() + img
+        img = ShareIt.location.origin() + img

     $('<meta>', { itemprop: 'image', content: img }).appendTo 'head'
     #


activity: remove obsolete hashtag transformation case

--- client/Social/Activity/views/activitylistitem.coffee
+++ client/Social/Activity/views/activitylistitem.coffee
@@ -100,7 +100,6 @@
       post = text[offset + match.length]
       return match  if pre  isnt ' ' and offset isnt 0
       return match  if post isnt ' ' and (offset + match.length) isnt text.length
-      return match  if pre  is '&'
       return "[##{tag}](#{prefix}/Activity/Topic/#{tag})"


DefineStackView: Stack saved warning copy http://take.ms/HB7o8

--- client/admin/lib/views/stacks/definestackview.coffee
+++ client/admin/lib/views/stacks/definestackview.coffee
@@ -272,11 +272,12 @@

       action = if not stackTemplates?.length then 'addAndWarn' else 'add'

-      @outputView[action] "
-        Your stack script has been successfully saved and applied
-        to all your team members. You can now close this window
-        or continue working with your stack.
-      "
+      @outputView[action] """
+        Your stack script has been successfully saved and all your team
+        members now will use the stack you have just saved.
+
+        You can now close this window or continue working with your stack.
+      """

       @cancelButton.setTitle 'Close'
       @saveButton.hide()


implemented selection of filename without extension

--- client/ide/lib/views/tabview/idetabhandleview.coffee
+++ client/ide/lib/views/tabview/idetabhandleview.coffee
@@ -101,7 +101,10 @@
       @setClass 'edit-mode'
       @titleInput.setValue title
       @titleInput.setFocus()
-      @titleInput.selectAll()
+      if title.lastIndexOf('.') > 0
+        @titleInput.selectRange(0, title.lastIndexOf('.'))
+      else
+        @titleInput.selectAll()
       windowController.addLayer this
     else
       @unsetClass 'edit-mode'


common: fix return behavior for multiple error messages

--- client/Main/KD.extend.coffee
+++ client/Main/KD.extend.coffee
@@ -245,7 +245,7 @@

     if Array.isArray err
       @showError er  for er in err
-      return
+      return err.length

     if 'string' is typeof err
       message = err


Added set palette function

Added set palette function

--- lib/assets/javascripts/pixie/editor/pixel/create.coffee
+++ lib/assets/javascripts/pixie/editor/pixel/create.coffee
@@ -252,6 +252,12 @@
           class: 'swatch'
           style: "background-color: #{color.toString()}"

+      setPalette: (colors) ->
+        swatches.empty()
+
+        colors.each (color) ->
+          self.addSwatch(Color(color))
+
       addTool: (tool) ->
         name = tool.name
         alt = name.capitalize()
@@ -559,8 +565,7 @@
       action.name = key
       self.addAction(action)

-    $.each palette, (i, color) ->
-      self.addSwatch(Color(color))
+    self.setPalette(palette)

     self.setTool(tools.pencil)


package 名称验证

package 名称验证

--- lib/gitCreatePackage/gitCreatePackage-info-view.coffee
+++ lib/gitCreatePackage/gitCreatePackage-info-view.coffee
@@ -42,6 +42,8 @@
       @div class: "form-group", =>
         @label 'Package Name:'
         @subview 'packageName', new EditorView(mini: true)
+        @div style: "background-color: #f7ea57;", outlet: "warnPackageText", =>
+          @label style: "font-weight: bolder; color: black;",outlet: "warnPackageTextLabel"

       @div class: "form-group", =>
         @label 'Describe:'
@@ -67,6 +69,9 @@

     @selectPrivateGit.change =>
       @userAccountAttached()
+
+    @checkNameEditorView @packageName
+    @warnPackageText.hide()

   attached: ->
     @privateSelect.hide()
@@ -151,7 +156,7 @@
         # console.log packageHave
         options =
           repo: @selectGit
-          packageName: @packageName.getText()
+          packageName: @packageName.originalText
           describe: @describe.getText()

         unless packageHave
@@ -177,3 +182,28 @@
     .catch (err) ->
       console.trace err.stack
       alert "#{err}"
+
+
+  checkNameEditorView: (editorView)->
+    editorView.originalText = ''
+    editorView.hiddenInput.on 'focusout', (e) =>
+      @checkName editorView
+
+
+  checkName: (editorView)->
+    str = editorView.getText()
+    regEx = /[\`\~\!\@\#\$\%\^\&\*\(\)\+\=\|\{\}\'\:\;\,\\\[\]\<\>\/\?\~\！\@\#\￥\%\…\…\&\*\（\）\—\—\+\|\{\}\【\】\‘\；\：\”\“\’\。\，\、\？]/g
+    strcheck = str.replace(/[^\x00-\xff]/g,"-")
+    strcheck = strcheck.replace(regEx,"-")
+    strcheck = strcheck.replace(/-+/g, '-')
+
+    editorView.originalText = strcheck
+    @warnPackageTextLabel.html("Will be created as #{strcheck}")
+
+    if strcheck is str
+      @warnPackageText.hide()
+    else
+      @warnPackageText.show()
+
+
+


fix a bug

fix a bug

--- lib/gitCreatePackage/gitCreatePackage-info-view.coffee
+++ lib/gitCreatePackage/gitCreatePackage-info-view.coffee
@@ -19,7 +19,7 @@
           @label "You will open your source to everyone.", class: 'radioLabel'

         @div outlet: 'publicSelect', =>
-          @select class:'gitCreatePackageSelect', outlet: 'selectPublicGit', =>
+          @select class:'form-control', outlet: 'selectPublicGit', =>
             @option "github"

       @div class: "form-group", =>
@@ -28,7 +28,7 @@
           @label 'You only open your source to your company.', class: 'radioLabel'

         @div outlet: 'privateSelect', =>
-          @select class:'gitCreatePackageSelect', outlet: 'selectPrivateGit', =>
+          @select class:'form-control', outlet: 'selectPrivateGit', =>
             @option "gogs"

       @div class: "form-group", =>


allows fair relation to reach the profile with either format of data

--- models/mixins/relations/fair.coffee
+++ models/mixins/relations/fair.coffee
@@ -4,7 +4,9 @@

     Profile = require '../../../models/profile.coffee'

-    profile = new Profile id: @get('organizer')?.profile_id
+    profile_id = @get('default_profile_id') || @get('organizer')?.profile_id
+
+    profile = new Profile id: profile_id

     @__related__ =
       profile: profile


Implementing 4 level AMF curve subdivision support

--- p3d.coffee
+++ p3d.coffee
@@ -34,6 +34,14 @@
     xmlDoc=new ActiveXObject("Microsoft.XMLDOM")
     xmlDoc.async=false
     xml.loadXML(text)
+
+hermiteSpline = (s, v, t) -> # see http://en.wikipedia.org/wiki/Cubic_Hermite_spline
+  # (2*s^3 - 3*s^2 + 1)*v[0] + (s^3 - 2*s^2 + s)*t[0] + (-2*s^3 + 3*s^2)*v[1] + (s^3-s^2)*t[1]
+  c = [
+    [ 2*Math.pow(s,3) - 3*Math.pow(s,2) + 1, Math.pow(s,3) - 2*Math.pow(s,2) + s ],
+    [ -2*Math.pow(s,3) + 3*Math.pow(s,2), Math.pow(s,3) - Math.pow(s,2) ]
+  ]
+  v[0][i]*c[0][0] + t[0][i]*c[0][1] + v[1][i]*c[1][0] + t[1][i]*c[1][1] for i in [0..2]

 base64_encode = (data) ->
   # http://kevin.vanzonneveld.net
@@ -265,10 +273,9 @@
       undefined

     # Scaling the object to mm
-    console.log xml
+    #console.log xml
     unitStr = root.getAttribute("unit") || root.getAttribute("units")
     scale = @_toMillimeters unitStr
-    console.log scale

     # initializing indice and vert counts
     vertCount = 0; indiceCount = 0
@@ -290,20 +297,95 @@
     $ "//triangle", (node) ->
       indices[indiceCount++] = parseInt(read node, "v#{k}") for k in [1..3]

+    # Helper Functions
+    isFlat = (face) -> ( (n = face.normals)[0] == n[1] == n[2] )
+    cross = (vA, vB) -> [
+        vA[1]*vB[2] - vA[2]*vB[1],
+        vA[2]*vB[0] - vA[0]*vB[2],
+        vA[0]*vB[1] - vA[1]*vB[0]
+      ]
+    magnitude = (v) -> Math.sqrt Math.pow(v[0],2) + Math.pow(v[1],2) + Math.pow(v[2],2)
+
+    # Adds a face's vertices and normals to the mesh at the given index
+    addFace = (face, mesh, index) -> for attr in ['vertices', 'normals']
+      mesh[attr][index+j*3+k] = face[attr][j][k] for j in [0..2] for k in [0..2]
+
     # Expanding (duplicating) the normals and verts so that there is a 1:1 of verts to indices
     # This allows us to modify the normals on a per-face basis in edges and makes mesh spliting trivial
-    nOfTriangles = @nOfTriangles = indices.length/3 # TODO: when noftriangles is fixed this will be implicit
+    nOfTriangles = indices.length/3 # TODO: when noftriangles is fixed this will be implicit
     exp = {}
-    exp[attr] = new Float32Array(@nOfTriangles*9) for attr in ['vertices', 'normals']
-    @_eachFace (face, i) ->
-      console.log i
-      for attr in ['vertices', 'normals']
-        exp[attr][i*9+j*3+k] = face[attr][j][k] for j in [0..2] for k in [0..2]
+    exp[attr] = new Float32Array(nOfTriangles*9) for attr in ['vertices', 'normals']
+    @_eachFace (face, i) -> addFace(face, exp, i*9)
     indices[i] = i for i in [0..indices.length-1]
     @[attr] = exp[attr] for attr in ['vertices', 'normals']
     @verts = @vertices

     # Define the normals for verts without a normal as the normal vector of the face
+    @_eachFace @_calculateVertexNormals
+
+    #return
+    # Counting Curved Surfaces
+    subdivisionLevels = 4
+    trianglesPerSurface = Math.pow 4, subdivisionLevels
+    @_eachFace (face) -> ( nOfTriangles += trianglesPerSurface-1 if !isFlat face )
+
+    # Subdividing Curved Surfaces
+    exp[attr] = new Float32Array(nOfTriangles*9) for attr in ['vertices', 'normals']
+    vertCount = 0
+
+    subdivide = (face, fIndex) ->
+      # calculating each edge midpoint and normal
+      midVerts = []
+      midNormals = []
+      for edge in [[0,1], [1,2], [2,0]]
+        v = [ face.vertices[edge[0]], face.vertices[edge[1]] ]
+        n = [ face.normals[edge[0]], face.normals[edge[1]] ]
+        d = ( v[1][i] - v[0][i] for i in [0..2] )
+        t = for i in [0..1]
+          crossProduct = cross cross(n[i], d), n[i]
+          ( magnitude(d) * crossProduct[j] / magnitude(crossProduct) for j in [0..2] )
+        midVerts.push v01 = hermiteSpline(0.5, v, t, fIndex)
+        # TODO: these normals are probably only aproximately correct.
+        # What is the proper way to calculate these??
+        midNormals.push n01 = ( (n[1][i] + n[0][i])/2 for i in [0..2] )
+
+      newFaces = for i in [0..2] # calculating the outer subdivided faces
+        vertices: [ midVerts[i], midVerts[ j = (i+2)%3 ], face.vertices[i] ]
+        normals: [ midNormals[i], midNormals[j], face.normals[i] ]
+      #newFaces = []
+      newFaces.push # calculating the center subdivided faces
+        vertices: midVerts, normals: midNormals
+
+      return newFaces
+
+    # iterating through each original face and subdividing it if it is has non-uniform normals
+    @_eachFace (face, fIndex) ->
+      if isFlat face
+        for attr in ['vertices', 'normals']
+          exp[attr][vertCount+j*3+k] = face[attr][j][k] for j in [0..2] for k in [0..2]
+        vertCount += 9
+      else
+        faces = [face]
+        newFaces = []
+        for i in [0..subdivisionLevels-1]
+          newFaces = []
+          for f in faces
+            newFaces.push f2 for f2 in subdivide f, fIndex + Math.pow(4,i)
+          faces = newFaces
+        # adding each subdivided face to the mesh
+        for f in newFaces
+          addFace f, exp, vertCount
+          #addFace face, exp, vertCount
+          vertCount += 9
+
+
+    indices = @indices = new Float32Array(nOfTriangles*3)
+    @indices[i] = i for i in [0..@indices.length-1]
+    @[attr] = exp[attr] for attr in ['vertices', 'normals']
+    @verts = @vertices
+
+    # TODO: temporarily using face normals for the subdivided faces until I figure out how to calculate them.
+    @nOfTriangles = nOfTriangles
     @_eachFace @_calculateVertexNormals

     # Parsing Edges (TODO)
@@ -313,6 +395,8 @@
     #    verts[vertCount++] = parseFloat s
     # TODO: eventually we will be able to pull normals from the AMF file
     #@_eachFace @_calculateVertexNormals
+
+    @nOfTriangles = nOfTriangles
     undefined

   _parseTextObj: (text) ->
@@ -417,7 +501,8 @@
         f.normals[i][j] = vN[j] for j in [0..2]

   # Splits the object into 2^16 vert chunks and returns the chunks
-  split: () =>
+  split: () => # TODO: since each object's verts are 1:1 with indices at this point we can use monolithic subarrays here
+    #console.log "splitting"
     bytesPerMesh = Math.pow(2,16) # is this even in bytes!?
     bytesPerMesh -= bytesPerMesh % 9 # Rounding the bytes per mesh down to the nearest face


Made Accounts_UseDNSDomainCheck false by default.

--- packages/rocketchat-lib/server/startup/settings.coffee
+++ packages/rocketchat-lib/server/startup/settings.coffee
@@ -25,7 +25,7 @@
 		@add 'Accounts_BlockedDomainsList', '', { type: 'string' }
 		@add 'Accounts_BlockedUsernameList', '', { type: 'string' }
 		@add 'Accounts_UseDefaultBlockedDomainsList', true, { type: 'boolean' }
-		@add 'Accounts_UseDNSDomainCheck', true, { type: 'boolean' }
+		@add 'Accounts_UseDNSDomainCheck', false, { type: 'boolean' }

 		@add 'Accounts_RegistrationForm', 'Public', { type: 'select', public: true, values: [ { key: 'Public', i18nLabel: 'Accounts_RegistrationForm_Public' }, { key: 'Disabled', i18nLabel: 'Accounts_RegistrationForm_Disabled' }, { key: 'Secret URL', i18nLabel: 'Accounts_RegistrationForm_Secret_URL' } ] }
 		@add 'Accounts_RegistrationForm_SecretURL', Random.id(), { type: 'string' }


Parse and use CLI argments for flash player location

--- lib/flash-loader.coffee
+++ lib/flash-loader.coffee
@@ -1,6 +1,7 @@
 {app} = require 'electron'
 {join} = require 'path-extra'
 fs = require 'fs-extra'
+{warn, error} = require './utils'

 platform = process.platform
 filename = switch platform
@@ -88,14 +89,44 @@
 getAllVersions = ->
   new FlashPlayerVersions getBuiltInFlashPath(), findChromeFlashPath(), findSystemFlashPath()

+useFlashLoc = 'auto'
+CLIFlashPath = ''
+reCLIArg = /^--flash-player=(.+)$/i
+reUseSpecialPath = /^@(auto|builtin|chrome|system)$/i
 parseCLIArg = (arg) ->
-  # --flash=useChrome
-  # --flash-path=/Applications/Google\ Chrome.app/Contents/Versions/48.0.2564.109/Google\ Chrome\ Framework.framework/Internet\ Plug-Ins/PepperFlash/PepperFlashPlayer.plugin
+  if (m = reCLIArg.exec arg)?
+    value = m[1]
+    if value.startsWith '@'
+      if (m = reUseSpecialPath.exec value)?
+        useFlashLoc = m[1]
+      else
+        error "Unknown flash player location: #{value}"
+    else
+      if validatePath value
+        useFlashLoc = 'cli'
+        CLIFlashPath = value
+      else
+        error "Invalid flash player path:\n#{value}"
+    true
+  else
+    false

-getPath = ->
-  flashPath = builtInPath
-  flashPath ?= findChromeFlashPath()
-  flashPath ?= findSystemFlashPath()
+getPath = (loc = useFlashLoc) ->
+  switch loc
+    when 'auto'
+      flashPath = getBuiltInFlashPath()
+      flashPath ?= findChromeFlashPath()
+      flashPath ?= findSystemFlashPath()
+      return flashPath
+    when 'builtin'
+      flashPath = getBuiltInFlashPath()
+    when 'chrome'
+      flashPath = findChromeFlashPath()
+    when 'system'
+      flashPath = findSystemFlashPath()
+    when 'cli'
+      flashPath = CLIFlashPath
+  flashPath ?= getPath 'auto'
   flashPath

 load = ->
@@ -106,7 +137,7 @@
   app.commandLine.appendSwitch 'ppapi-flash-path', flashPath

-exports.parseCLIArg = ->
+exports.parseCLIArg = parseCLIArg
 exports.loadFlashPlayer = load
 exports.getFlashPlayerVersion = getFlashVersion
 exports.getAllVersions = getAllVersions


:art: Make it work without bottomBar it is required to write specs

--- lib/linter-views.coffee
+++ lib/linter-views.coffee
@@ -131,7 +131,8 @@
     @removeMarkers()
     @removeBubble()
     @subscriptions.dispose()
-    @bottomBar.destroy()
+    if @bottomBar
+      @bottomBar.destroy()
     @panel.destroy()

 module.exports = LinterViews


extend notification times

extend notification times

--- lib/local_server.coffee
+++ lib/local_server.coffee
@@ -80,7 +80,7 @@
    * Inject loading spinner while compiling
   ###
   compiling: ->
-    @bs.notify('compiling...')
+    @bs.notify('compiling...', 999999)

   ###*
    * Sanitize error message and inject into page
@@ -93,6 +93,6 @@
       err.replace(/(\r\n|\n|\r)/gm, '<br>')
     else
       "compile error!"
-    @bs.notify(cleanError, 100000)
+    @bs.notify(cleanError, 999999)

 module.exports = Server


use for loop for selection iteration just for consistency

--- lib/misc-command.coffee
+++ lib/misc-command.coffee
@@ -155,7 +155,7 @@
   @commandScope: 'atom-text-editor.vim-mode-plus.insert-mode.replace'
   @extend()
   execute: ->
-    @editor.getSelections().forEach (selection) =>
+    for selection in @editor.getSelections()
       # char might be empty.
       char = @vimState.modeManager.getReplacedCharForSelection(selection)
       if char?


Disable subkeys toolbar button when no subkeys

--- muleview/app/view/ChartsView.coffee
+++ muleview/app/view/ChartsView.coffee
@@ -79,6 +79,7 @@
             ,
               xtype: "button"
               text: "Select Subkeys"
+              disabled: @subkeys.length == 0
               handler: =>
                 @showSubkeysSelector()
             ,


fix: on windows, MMB by default does "auto scroll" - prevent that

--- scroll_drag.coffee
+++ scroll_drag.coffee
@@ -294,6 +294,8 @@
     if ev.button isnt MB then return
     @serv = @serv.start ev
     document.addEventListener 'mousemove', @move
+    ev.preventDefault(); #windows needs that - otherwise does the annoying scrolling
+    ev.stopPropagation();
     false
   up: (ev) =>
     if ev.button isnt MB then return


Fix error message

Fix error message

--- node_modules_koding/koding-config-manager/config.coffee
+++ node_modules_koding/koding-config-manager/config.coffee
@@ -78,7 +78,7 @@
          "schema but it exists on file, please add it to schema."
         else
           "file but it's required in schema, please define before proceeding. "
-      console.log "[KONFIG][ERROR] [ Property Mismatch : #{i} ] Not found in #{where} [paths: [config/#{@path}.coffee] [./config.schema.coffee]]"
+      console.log "[KONFIG][ERROR] [ Property Mismatch : #{i} ] Not found in #{where} [paths: [config/#{path}.coffee] [./config.schema.coffee]]"

     err = if info.length > 0 then yes else no
     # process.nextTick => @emit "result", err, info


moveCursorToFirstCharacterOfLine -> moveToFirstCharacterOfLine

--- lib/symbols-view.coffee
+++ lib/symbols-view.coffee
@@ -65,7 +65,7 @@
     if editor = atom.workspace.getActiveTextEditor()
       editor.scrollToBufferPosition(position, center: true)
       editor.setCursorBufferPosition(position)
-      editor.moveCursorToFirstCharacterOfLine() if beginningOfLine
+      editor.moveToFirstCharacterOfLine() if beginningOfLine

   attach: ->
     @storeFocusedElement()


change maxwidth on dragLeave event

change maxwidth on dragLeave event

--- lib/tab-bar-view.coffee
+++ lib/tab-bar-view.coffee
@@ -264,6 +264,7 @@
       false

   onDragLeave: (event) ->
+    tab.style.maxWidth = '' for tab in @getTabs()
     @removePlaceholder()

   onDragEnd: (event) ->


Change default to markdown_v2

Change default to markdown_v2

--- src/smc-webapp/markdown.coffee
+++ src/smc-webapp/markdown.coffee
@@ -19,7 +19,7 @@

 markdown_to_html_v2 = (raw_string) ->
     s = processor.processSync(raw_string).toString()
-    return {s:s, has_mathjax:has_mathjax}
+    return {s:s, has_mathjax:false}

 ###
 # Old Method retained for backwards compatibility
@@ -43,7 +43,7 @@
     s = misc.replace_all(s, '[ ]', "")
     return misc.replace_all(s, '[x]', "")

-markdown_to_html = (s) ->
+markdown_to_html_v1 = (s) ->
     # See https://github.com/sagemathinc/cocalc/issues/1801
     [text, math] = remove_math(s)
     if math.length > 0
@@ -69,4 +69,4 @@
     exports.html_to_markdown = (s) ->
         return reMarker.render(s)

-exports.markdown_to_html = markdown_to_html_2
+exports.markdown_to_html = markdown_to_html_v2


Opening a file upload modal iff there is one file input closes #397

--- wzk/ui/inlineform/DynamicForm.coffee
+++ wzk/ui/inlineform/DynamicForm.coffee
@@ -81,7 +81,9 @@
     if @formNum is @MAX_FORMS
       goog.dom.classes.add(@btn, 'disabled')
       goog.events.unlistenByKey(@listener)
-    @openFileDialog row
+
+    if @dom.all('input[type=file]', row).length == 1
+      @openFileDialog(row)

   ###*
     @protected


Ensure Packet defaults to at least HEADER_SIZE length

--- src/wrathnet/net/packet.coffee
+++ src/wrathnet/net/packet.coffee
@@ -13,7 +13,7 @@
 class WrathNet.net.Packet extends ByteBuffer

   # Creates a new packet with given opcode from given source or length
-  constructor: (opcode, source, outgoing=true) ->
+  constructor: (opcode, source=@constructor.HEADER_SIZE, outgoing=true) ->
     super source, ByteBuffer.LITTLE_ENDIAN

     # Holds the opcode for this packet


Tighten scoping of line-block continuation markers

--- grammars/restructuredtext.cson
+++ grammars/restructuredtext.cson
@@ -92,7 +92,7 @@
 			{
 				match: "^\\s*(\\|)"
 				captures:
-					0: name: "punctuation.separator.line-block.restructuredtext"
+					1: name: "punctuation.separator.line-block.restructuredtext"
 			}

 			{include: "#inlines"}


chore(Gruntfile): Added updatebuild task

--- misc/grunt/tasks.coffee
+++ misc/grunt/tasks.coffee
@@ -79,6 +79,25 @@
     grunt.file.write componentFile, JSON.stringify(data, null, "  "), encoding: "utf8"
     grunt.log.writeln "Updated bower.json"

+  ###
+  @name updatebuild
+  @description
+  Update bower.json version of all bower repositories
+  ###
+  grunt.registerMultiTask "updatebuild", "Update all bower.json in build/", ->
+    version = grunt.config.get("pkg").version
+    @files.forEach (file) ->
+      src = file.src.filter (filepath) ->
+        unless (exists = grunt.file.exists(filepath))
+          grunt.log.warn "File '#{filepath}' not found"
+        return exists
+      .map (filepath) ->
+        data         = grunt.file.readJSON filepath, encoding: "utf8"
+        data.version = grunt.config.get("pkg").version
+        return data
+
+      grunt.file.write file.dest, JSON.stringify(src, null, "  "), encoding: "utf8"
+      grunt.log.writeln "Updated version in #{file.dest}"
   grunt.registerTask "bump", "Bump package version up and generate changelog", ->
     done = @async()


Add tests for indicator.getDataFrom

Add tests for indicator.getDataFrom

--- test/models/indicator.coffee
+++ test/models/indicator.coffee
@@ -4,6 +4,7 @@
 Indicator = require('../../models/indicator')
 fs = require 'fs'
 Q = require('q')
+GDocGetter = require('../../getters/gdoc')

 suite('Indicator')

@@ -51,16 +52,16 @@
   indicator.query().then( ->
     try
       assert.isTrue(
-        getDataStub.calledWith(indicator.source),
-        "Expected getDataFrom to be called with the indicator source"
+        getDataStub.calledOnce,
+        "Expected getDataFrom to be called"
       )

       formatDataCallArgs = formatDataFrom.getCall(0).args

       assert.isTrue(
-        formatDataFrom.calledWith(indicator.source, gotData),
-        "Expected formatDataFrom to be called with the indicator source and
-          fetched data, but was called with #{formatDataCallArgs}"
+        formatDataFrom.calledWith(gotData),
+        "Expected formatDataFrom to be called with the fetched data,
+        but was called with #{formatDataCallArgs}"
       )

       done()
@@ -68,3 +69,26 @@
       done(err)
   ).fail(done)
 )
+
+test(".getDataFrom calls a getter based on the given type", ->
+  indicator = new Indicator(
+    source: 'gdoc'
+  )
+
+  gDocGetterStub = sinon.stub(GDocGetter::, 'constructor', ->
+    Q.fcall(->)
+  )
+
+  indicator.getDataFrom().then(->
+    try
+      assert.isTrue(
+        gDocGetterStub.calledOnce,
+        "Expect the GDocGetter to be called, as the indicator.source is 'gdoc'"
+      )
+      done()
+    catch err
+      done(err)
+    finally
+      gDocGetterStub.restore()
+  ).fail(done)
+)


fix test

fix test

--- test/spec/router_spec.coffee
+++ test/spec/router_spec.coffee
@@ -501,8 +501,8 @@
         expect(url).to.match /^items\/23\?/
         expect(utils.queryParams.parse(url)).to.eql {two: 3, four: 5}

-      it 'should not set extra params to querstring when paramsInQS is false'
-        route = new Route 'items/:id', 'null', 'null', paramsInQS: false
+      it 'should not set extra params to querstring when paramsInQS is false', ->
+        route = new Route 'items/:id', 'null', 'null', {paramsInQS: false}
         url = route.reverse id: 23, two: 3
         expect(url).to.be 'items/23'


test(server): Add basic server unit tests

Adds 4 unit tests for the server:
 - should start the web server after all files have been preprocessed
   successfully
 - should start the web server after all files have been preprocessed
   with an error
 - should launch browsers after the web server has started
 - should try next port if already in use

--- test/unit/server.spec.coffee
+++ test/unit/server.spec.coffee
@@ -1,7 +1,139 @@
 # TODO(vojta):
-'should try next port if already in use'
-'should launch browsers after web server started'
-
 # single run
 'should run tests when all browsers captured'
 'should run tests when first browser captured if no browser configured'
+
+#==============================================================================
+# lib/server.js module
+#==============================================================================
+describe 'server', ->
+  BrowserCollection = require('../../lib/browser_collection')
+  EventEmitter = require('events').EventEmitter
+  loadFile = require('mocks').loadFile
+  q = require('q')
+
+  m = mockConfig = browserCollection = emitter = injector = webServerOnError = null
+  fileListOnResolve = fileListOnReject = mockInjector = mockLauncher = null
+  mockFileList = mockWebServer = mockSocketServer = mockExecutor = doneSpy = null
+
+  beforeEach ->
+    browserCollection = new BrowserCollection
+    doneSpy = sinon.spy()
+    emitter = new EventEmitter
+
+    fileListOnResolve = fileListOnReject = null
+
+    m = loadFile __dirname + '/../../lib/server.js'
+
+    mockConfig =
+      frameworks: []
+      port: 9876
+      autoWatch: true
+      hostname: 'localhost'
+      urlRoot: '/'
+      browsers: ['fake']
+      singleRun: true
+      browserDisconnectTolerance: 0
+
+    mockExecutor =
+      schedule: ->
+
+    mockFileList =
+      refresh: sinon.spy( -> then: (onResolve, onReject) ->
+        fileListOnResolve = onResolve
+        fileListOnReject = onReject
+      )
+
+    mockInjector =
+      get: ->
+      invoke: sinon.spy( -> [])
+      createChild: ->
+        instantiate: ->
+          init: ->
+
+    mockLauncher =
+      launch: ->
+      markCaptured: ->
+      areAllCaptured: -> false
+      restart: -> true
+      kill: -> true
+
+    mockSocketServer =
+      flashPolicyServer:
+        close: ->
+      sockets:
+        sockets: {}
+        on: ->
+        emit: ->
+
+    mockWebServer =
+      on: (name, handler) ->
+        if name == 'error'
+          webServerOnError = handler
+      listen: sinon.spy((port, callback) -> callback && callback())
+      removeAllListeners: ->
+      close: ->
+
+    webServerOnError = null
+
+  #============================================================================
+  # server.start()
+  #============================================================================
+  describe 'start', ->
+    it 'should start the web server after all files have been preprocessed successfully', ->
+      m.start(mockInjector, mockConfig, mockLauncher, emitter, null, mockFileList,
+        mockWebServer, browserCollection, mockSocketServer, mockExecutor, doneSpy)
+
+      expect(mockFileList.refresh).to.have.been.called
+      expect(fileListOnResolve).not.to.be.null
+      expect(mockWebServer.listen).not.to.have.been.called
+      expect(mockInjector.invoke).not.to.have.been.calledWith mockLauncher.launch, mockLauncher
+
+      fileListOnResolve()
+
+      expect(mockWebServer.listen).to.have.been.called
+      expect(mockInjector.invoke).to.have.been.calledWith mockLauncher.launch, mockLauncher
+
+    it 'should start the web server after all files have been preprocessed with an error', ->
+      m.start(mockInjector, mockConfig, mockLauncher, emitter, null, mockFileList,
+        mockWebServer, browserCollection, mockSocketServer, mockExecutor, doneSpy)
+
+      expect(mockFileList.refresh).to.have.been.called
+      expect(fileListOnReject).not.to.be.null
+      expect(mockWebServer.listen).not.to.have.been.called
+      expect(mockInjector.invoke).not.to.have.been.calledWith mockLauncher.launch, mockLauncher
+
+      fileListOnReject()
+
+      expect(mockWebServer.listen).to.have.been.called
+      expect(mockInjector.invoke).to.have.been.calledWith mockLauncher.launch, mockLauncher
+
+    it 'should launch browsers after the web server has started', ->
+      m.start(mockInjector, mockConfig, mockLauncher, emitter, null, mockFileList,
+        mockWebServer, browserCollection, mockSocketServer, mockExecutor, doneSpy)
+
+      expect(mockWebServer.listen).not.to.have.been.called
+      expect(mockInjector.invoke).not.to.have.been.calledWith mockLauncher.launch, mockLauncher
+
+      fileListOnResolve()
+
+      expect(mockWebServer.listen).to.have.been.called
+      expect(mockInjector.invoke).to.have.been.calledWith mockLauncher.launch, mockLauncher
+
+    it 'should try next port if already in use', ->
+      m.start(mockInjector, mockConfig, mockLauncher, emitter, null, mockFileList,
+        mockWebServer, browserCollection, mockSocketServer, mockExecutor, doneSpy)
+
+      expect(mockWebServer.listen).not.to.have.been.called
+      expect(webServerOnError).not.to.be.null
+
+      expect(mockConfig.port).to.be.equal 9876
+
+      fileListOnResolve()
+
+      expect(mockWebServer.listen).to.have.been.calledWith(9876)
+
+      webServerOnError({ code: 'EADDRINUSE'})
+
+      expect(mockWebServer.listen).to.have.been.calledWith(9877)
+      expect(mockConfig.port).to.be.equal 9877


only default wait for replacement to checked for long running tasks

--- SingularityUI/app/views/taskOverviewSubview.coffee
+++ SingularityUI/app/views/taskOverviewSubview.coffee
@@ -60,6 +60,10 @@
                         btnText = 'Kill task'
                         templ = killTemplate

+                    checked = ""
+                    if @model.get('task').taskRequest.request.requestType in ['WORKER', 'SERVICE']
+                        checked = "checked"
+
                     vex.dialog.confirm
                         buttons: [
                             $.extend {}, vex.dialog.buttons.YES,
@@ -68,7 +72,7 @@
                             vex.dialog.buttons.NO
                         ]
                         input: """
-                            <input name="wait-for-replacement-task" id="wait-for-replacement-task" type="checkbox" checked /> Wait for replacement task to start before killing this task
+                            <input name="wait-for-replacement-task" id="wait-for-replacement-task" type="checkbox" #{checked} /> Wait for replacement task to start before killing this task
                             <input name="message" type="text" placeholder="Message (optional)" />
                         """
                         message: templ id: @model.taskId


softer colors

softer colors

--- graphics.coffee
+++ graphics.coffee
@@ -7,6 +7,9 @@
     @program_dims = {x: 20, y: 32}
     @mapping_dims = {x: 90, y: 10}
     @proglist_dims = {}
+
+    @program_fill_style = '#0000c0'
+    @grid_stroke_style = '#404040'

     @computeOutlines()

@@ -68,8 +71,8 @@
     # grid
     @ctx.save()
     @ctx.translate(@board_dims.x, @board_dims.y)
-    @ctx.strokeStyle = 'grey'
-    @ctx.fillStyle = 'blue'
+    @ctx.strokeStyle = @grid_stroke_style
+    @ctx.fillStyle = @program_fill_style
     @ctx.lineWidth = 1
     for x in [0...@game_state.Wi]
       for y in [0...@game_state.Hi]


semirecomplexify test

semirecomplexify test

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -87,7 +87,7 @@
         assert.notOk nav.root.active, notActiveMsg
         assert.end()
       test "No-Index", (assert) ->
+        assert.plan 1
         for uncle in nav.parent.siblings
           if uncle.title is 'Greek'
             assert.notOk uncle.href?, "Nav without index shouldn't have href"
-        assert.end()