使いそうな関数をひと通り書いた

使いそうな関数をひと通り書いた

--- public/js/hue.coffee
+++ public/js/hue.coffee
@@ -8,7 +8,7 @@
   constructor: (ip, user) ->
     @ip   = ip
     @user = user
-    @url  = "http://" + ip + "/api"
+    @url  = "http://" + ip + "/api/"

   request = (uri, method, data) ->
     dfd = $.Deferred()
@@ -19,63 +19,69 @@
         dfd.resolve $.parseJSON msg
         return dfd.promise()
       error  : (err) ->
-        dfd.reject $.parseJSON err
+        dfd.reject error
         return dfd.promise()
     # add data to param if 'POST' or 'PUT'
     if method != 'GET'
       param['data'] = data
     $.ajax param

+  # 成功したらライトの数をreturn, ダメだったらfalse
   getLights: () ->
-    # TODO: jsonからライトの数だけ取り出してreturn
     dfd = $.Deferred()
-    uri = '/' + @user + 'lights'
+    uri = @user + '/lights'
     method = 'GET'
     request.call this, uri, method, null
       .then (result) ->
-        dfd.resolve result
+        if result.length == 1
+          dfd.resolve false
+        else
+          dfd.resolve result.length
         return dfd.promise()
       .fail (err) ->
         dfd.reject err
         return dfd.reject()

+  # リクエストのtrue or falseをreturn
   lightTrriger: (light, trigger) ->
-    # TODO: jsonから成功か否かを判別してbooleanでreturn
     uri = '/' + @user + '/lights/' + light.toString() + '/state'
     method = 'PUT'
     data =
-      'on': if trigger then true else false
+      'on': trigger
     request.call this, uri, method, data
       .then (result) ->
-        dfd.resolve result
+        res = result[0]["error"] == undefined
+        dfd.resolve res
         return dfd.promise()
       .fail (err) ->
         dfd.reject err
         return dfd.promise()

+  # リクエストのtrue or falseをreturn
   changeBri: (light, bri) ->
-    # TODO: jsonから成功か否かを取り出してbooleanでreturn
     uri = '/' + @user + '/lights/' + light.toString() + '/state'
     method = 'PUT'
     data =
       'bri': bri
     request.call this, uri, method, data
       .then (result) ->
-        dfd.resolve result
+        res = result[0]["error"] == undefined
+        dfd.resolve res
         return dfd.promise()
       .fail (err) ->
         dfd.reject err
         return dfd.promise()

+  # リクエストのtrue or falseをreturn
   effectTrriget: (light, trigger) ->
-    # TODO: jsonから成功か否かを取り出してbooleanでreturn
     uri = '/' + @user + '/lights/' + light.toString() + '/state'
     method = 'PUT'
     data =
       'effect': if trigger then 'colorloop' else 'none'
     request.call this, uri, method, data
       .then (result) ->
-        dfd.resolve result
+        res = result[0]["error"] == undefined
+        dfd.resolve res
         return dfd.promise()
       .fail (err) ->
         dfd.reject err


Dummy out the tag name for now.

Dummy out the tag name for now.

--- build/tasks/publish-build-task.coffee
+++ build/tasks/publish-build-task.coffee
@@ -55,7 +55,7 @@

     zipAssets buildDir, assets, (error) ->
       return done(error) if error?
-      uploadAssets(null, buildDir, assets, done)
+      uploadAssets({tag_name: '1.1'}, buildDir, assets, done)

     # zipAssets buildDir, assets, (error) ->
     #   return done(error) if error?


removes other debug code

removes other debug code

--- js/inner.coffee
+++ js/inner.coffee
@@ -1117,7 +1117,7 @@
   isPaying: () =>
     @plan?

-class window.Plan extends Obj
+class Plan extends Obj
   constructor: ->
     super

@@ -1383,7 +1383,6 @@

   loadPlans: () =>
     $.getJSON '/api/v1/user/plans', (data) =>
-      window.data = data
       @plans((new Plan(d) for d in data))

   loadPlanFeatures: () =>


Fix setTimeout calls

Fix setTimeout calls

--- js/inner.coffee
+++ js/inner.coffee
@@ -378,9 +378,7 @@
       window.time_taken_projects = Date.now() - start_time
       if @first_login
         @first_login = false
-        setTimeout(
-          () => @loadProjects(),
-          3000)
+        setTimeout(@loadProjects, 3000)

   loadRecentBuilds: () =>


Fix to avoid compiling unused files.

Fix to avoid compiling unused files.

--- app/templates/common/Gruntfile.coffee
+++ app/templates/common/Gruntfile.coffee
@@ -72,7 +72,7 @@
     fileFormat: 'yml'
     baseDir: 'dist'
     outputDir: 'dist'
-    exclude: []
+    exclude: ['components/']
     locales: []
     locale: 'en'
     localesPath: 'locales'


support browserSync for dist folder.

support browserSync for dist folder.

Signed-off-by: Bo-Yi Wu <f80eec1bf98b3feebacc247f35f2b506218004cb@gmail.com>

--- templates/gulpfile.coffee
+++ templates/gulpfile.coffee
@@ -121,6 +121,15 @@
   gulp.watch paths.script + '/**/*.js', reload
   gulp.watch paths.css + '/**/*.css', reload

+# Connect
+gulp.task 'connect:dist', ->
+  browserSync
+    notify: false
+    server:
+      baseDir: [paths.dist]
+
+  gulp.watch paths.dist + '/**/*', reload
+
 gulp.task 'copy', ->
   gulp.src [
     paths.src + '/.htaccess'


Correct misspelling translate

Correct misspelling translate

--- def/th/menu_darwin.cson
+++ def/th/menu_darwin.cson
@@ -17,7 +17,7 @@
       "Keymap…":
         value: "การจับคู่คีย์..."
       "Snippets…":
-        value: "ชุดโค้ด..."
+        value: "ชุดคำสั่ง..."
       "Stylesheet…":
         value: "ชุดรูปแบบ..."
       "Install Shell Commands":
@@ -82,12 +82,12 @@
       "Toggle Comments":
         value: "สลับความเห็น"
       Lines:
-        value: "การจัดแถว"
+        value: "บรรทัด"
         submenu:
           Indent:
             value: "ย่อหน้า"
           Outdent:
-            value: "เยื่องหน้า"
+            value: "เยื้องหน้า"
           "Auto Indent":
             value: "ย่อหน้าอัตโนมัติ"
           "Move Line Up":
@@ -125,7 +125,7 @@
           Transpose:
             value: "ผวนคำ"
       Folding:
-        value: "ปล่อยคำ"
+        value: "การปล่อยคำ"
         submenu:
           Fold:
             value: "ปล่อย"
@@ -154,7 +154,7 @@
           "Fold Level 9":
             value: "ปล่อย 9 ระดับ"
       "Reflow Selection":
-        value: "ปรับลำดับส่วนที่เลือกใหม่"
+        value: "ปรับลำดับการเลือกใหม่"
       Bookmark:
         value: "ที่คั่นหน้า"
         submenu:
@@ -208,7 +208,7 @@
           "Open In Dev Mode…":
             value: "เปิดโหมดนักพัฒนา..."
           "Reload Window":
-            value: "โหลดหน้้าจอใหม่"
+            value: "โหลดหน้้าต่างใหม่"
           "Run Package Specs":
             value: "Run Package Specs"
           "Run Benchmarks":
@@ -220,13 +220,13 @@
       "Decrease Font Size":
         value: "ลดขนาดตัวอักษร"
       "Reset Font Size":
-        value: "ปรับขนาดตัวอักษร"
+        value: "ล้างค่าขนาดตัวอักษร"
       "Toggle Soft Wrap":
         value: "สลับการตัดคำ"
       "Toggle Command Palette":
         value: "สลับถาดคำสั่ง"
       "Toggle Tree View":
-        value: "สลับวิวต้นไม้"
+        value: "สลับมุมมองโครงสร้าง"
   Selection:
     value: "การเลือก"
     submenu:
@@ -322,4 +322,4 @@
       "Search Issues":
         value: "ค้นปัญหา"
       "Welcome Guide":
-        value: "แนวทางต้นรับ"
+        value: "แนวทางต้อนรับ"


small typo

small typo


Add simple confirmation for removing actions


Add update specific user api

Add update specific user api

--- server/api/setupApi.coffee
+++ server/api/setupApi.coffee
@@ -16,7 +16,7 @@

 import logger from '../log'

-import {updateAllResults} from '../calculations/updateResults'
+import updateResults {updateAllResults} from '../calculations/updateResults'
 import downloadMaterials from '../cron/downloadMaterials'
 import * as downloadContests from '../cron/downloadContests'
 import * as downloadSubmits from "../cron/downloadSubmits"
@@ -105,7 +105,13 @@
     app.get '/api/lastComments/:user', wrap (req, res) ->
         res.json(await SubmitComment.findLastByUser(req.params.user))

-    app.get '/api/updateResults', ensureLoggedIn, wrap (req, res) ->
+    app.get '/api/updateResults/:user', ensureLoggedIn, wrap (req, res) ->
+        if not req.user?.admin
+            res.status(403).send('No permissions')
+        await User.updateUser(req.params.user)
+        res.send('OK')
+
+    app.get '/api/updateAllResults', ensureLoggedIn, wrap (req, res) ->
         if not req.user?.admin
             res.status(403).send('No permissions')
         await updateAllResults()


Zoom in for details and parts tasks Rescale the SVG image based on the current task index. Bit of a hack to allow stepping back/forward through the tasks for each marked rectangle.

--- overrides.coffee
+++ overrides.coffee
@@ -60,6 +60,9 @@
   @rescale() if @magnification is 0

 LAST_TASK = false
+index = 0
+INITIAL_STEPS = 2 # number of initial steps before annotating rectangles
+ANNOTATION_STEPS = 2 # number of annotation steps per rectangle
 # moving back and forward through the array of marked SVG rectangles
 classify_page.el.on decisionTree.LOAD_TASK, ({originalEvent: detail: {task}})->
   rectangles = []
@@ -70,6 +73,12 @@
     if tool.attr(subjectViewer.FROM_CURRENT_TASK) == 'true'
       tool.enable()
       tool.select()
+
+  rect_index = parseInt (subjectViewer.taskIndex - INITIAL_STEPS) / ANNOTATION_STEPS
+
+  if rectangles.length > 0
+    tool = rectangles[rect_index]
+    ms.rescale tool.mark.left - 10, tool.mark.top - 10, tool.mark.width + 20, tool.mark.height + 20

   if task.key is 'illustration'
     ms.rescale(0,0,subjectViewer.maxWidth,subjectViewer.maxHeight)
@@ -77,12 +86,10 @@

   if task.key is 'details'
     index = task.value.index
-    tool = rectangles[index]
     if tool.mark.details?
       task.reset
         index: index
         details: tool.mark.details
-    ms.rescale tool.mark.left - 10, tool.mark.top - 10, tool.mark.width + 20, tool.mark.height + 20
     LAST_TASK = index == rectangles.length - 1

   if task.key is 'parts'


Client: Activity: Feed: included showPopularMessagesFlagStore

--- client/activity/lib/flux/index.coffee
+++ client/activity/lib/flux/index.coffee
@@ -36,11 +36,10 @@
     require './stores/channelparticipants/channelparticipantssearchquerystore'
     require './stores/channelparticipants/channelparticipantsdropdownvisibilitystore'
     require './stores/channelparticipants/channelparticipantsselectedindexstore'
-
     require './stores/channelmessageloadermarkersstore'
-
     require './stores/sidebarchannels/sidebarpublicchannelsquerystore'
     require './stores/sidebarchannels/sidebarpublicchannelstabstore'
+    require './stores/showpopularmessagesflagstore'
   ]
   # module stores
   .concat ChatInputModule.stores


fshelper: Added createRecursiveFolder method.

--- client/app/MainApp/fs/fshelper.coffee
+++ client/app/MainApp/fs/fshelper.coffee
@@ -157,6 +157,20 @@

     return instance

+  @createRecursiveFolder = ({ path, vmName }, callback = noop) ->
+    return warn "Pass a path to create folders recursively"  unless path
+    vmName or= KD.getSingleton("vmController").getDefaultVmName()
+
+    KD.getSingleton("kiteController").run {
+      kiteName    : "os"
+      method      : "fs.createDirectory"
+      withArgs    : {
+        recursive : yes
+        path
+      }
+      vmName
+    }, callback
+
   @isValidFileName = (name) ->
     return /^([a-zA-Z]:\\)?[^\x00-\x1F"<>\|:\*\?/]+$/.test name


Fix search input component events - remove click event

--- client/controllers/searchInput.coffee
+++ client/controllers/searchInput.coffee
@@ -27,7 +27,6 @@
   @searching = new ReactiveVar(searching)
   @textFilter = instanceData.textFilter or new ReactiveTable.Filter(instanceData.id, instanceData.props)

-
 Template.searchInput.helpers
   searchString: ->
     Template.instance().textFilter.get()
@@ -50,10 +49,10 @@
         $regex: regexEscape(instance.$(event.target).val())
         $options: 'i'

-  'click .search-icon.toggleable:not(.cancel)
-    , focusin .search-icon': (event, instance) ->
+  'focusin .search-icon.toggleable:not(.cancel)': (event, instance) ->
+    focus = event.type is 'focusin'
     searching = instance.searching
-    searching.set not searching.get()
+    searching.set(not searching.get())
     setTimeout ->
       instance.$(".search").focus()
     , 200


Align session properties

Align session properties

--- client/lanes/workspace/UIState.coffee
+++ client/lanes/workspace/UIState.coffee
@@ -14,12 +14,12 @@
             type: 'string',
             values: [ 'menu-wide', 'menu-narrow', 'menu-hidden' ]
         }
-        root:       'element'
-        menu_view:  'any'
-        width:      'number'
-        height:     'number'
-        layout:   'state'
-        viewport: 'object'
+        root:      'element'
+        menu_view: 'any'
+        width:     'number'
+        height:    'number'
+        layout:    'state'
+        viewport:  'object'
         screen_menu_shown: 'boolean'

     derived:


Split out mock backend, fix out-of-data bug

--- lms/static/coffee/src/staff_grading/staff_grading.coffee
+++ lms/static/coffee/src/staff_grading/staff_grading.coffee
@@ -8,10 +8,57 @@
 state_no_data = "no_data"
 state_error = "error"

+class StaffGradingBackend
+  constructor: (ajax_url, mock_backend) ->
+    @ajax_url = ajax_url
+    @mock_backend = mock_backend
+    if @mock_backend
+      @mock_cnt = 0
+
+  mock: (cmd, data) ->
+    # Return a mock response to cmd and data
+    @mock_cnt++
+    if cmd == 'get_next'
+      response =
+        success: true
+        submission: 'submission! ' + @mock_cnt
+        rubric: 'A rubric!' + @mock_cnt
+
+    else if cmd == 'save_grade'
+      response =
+        success: true
+        submission: 'another submission! ' + @mock_cnt
+        rubric: 'A rubric!' + @mock_cnt
+    else
+      response =
+        success: false
+        error: 'Unknown command ' + cmd
+
+    if @mock_cnt % 5 == 0
+        response =
+          success: true
+          message: 'No more submissions'
+
+
+    if @mock_cnt % 7 == 0
+      response =
+        success: false
+        error: 'An error for testing'
+
+    return response
+
+
+  post: (cmd, data, callback) ->
+    if @mock_backend
+      callback(@mock(cmd, data))
+    else
+      # TODO: replace with postWithPrefix when that's loaded
+      $.post(@ajax_url + cmd, data, callback)
+
+
 class StaffGrading
-  constructor: (mock_backend) ->
-    @el = $('.staff-grading')
-    @ajax_url = @el.data('ajax_url')
+  constructor: (backend) ->
+    @backend = backend

     @error_container = $('.error-container')
     @submission_container = $('.submission-container')
@@ -20,41 +67,11 @@
     @button.click @clicked
     @state = state_no_data

-    @mock_backend = mock_backend
-    if @mock_backend
-      @mock_cnt = 0
-
     @get_next_submission()
-
-  mock: (cmd, data) ->
-    # Return a mock response to cmd and data
-    @mock_cnt++
-    if cmd == 'get_next'
-      response =
-        'success': true
-        'submission': 'submission! ' + @mock_cnt
-        'rubric': 'A rubric!' + @mock_cnt
-    else if cmd == 'save_grade'
-      response =
-        'success': true
-        'submission': 'another submission! ' + @mock_cnt
-        'rubric': 'A rubric!' + @mock_cnt
-    else
-      response =
-        'success': false
-        'error': 'Unknown command ' + cmd
-    return response

   set_button_text: (text) ->
     @button.prop('value', text)
-
-  _post: (cmd, data, callback) ->
-    if @mock_backend
-      callback(@mock(cmd, data))
-    else
-      # TODO: replace with postWithPrefix when that's loaded
-      $.post(@ajax_url + cmd, data, callback)

   ajax_callback: (response) =>
@@ -67,11 +84,11 @@
         @error(response.error)

   get_next_submission: () ->
-    @_post('get_next', {}, @ajax_callback)
+    @backend.post('get_next', {}, @ajax_callback)

   submit_and_get_next: () ->
     data = {eval: '123'}
-    @_post('save_grade', data, @ajax_callback)
+    @backend.post('save_grade', data, @ajax_callback)

   error: (msg) ->
     @error_container.html(msg)
@@ -85,8 +102,8 @@
     @update()

   no_more: () ->
-    @submission_container.html(submission)
-    @rubric_container.html(rubric)
+    @submission_container.html('')
+    @rubric_container.html('')
     @state = state_no_data
     @update()

@@ -117,5 +134,8 @@

 # for now, just create an instance and load it...
-mock_backend = true
-$(document).ready(() -> new StaffGrading(mock_backend))
+mock_backend = true
+ajax_url = $('.staff-grading').data('ajax_url')
+backend = new StaffGradingBackend(ajax_url, mock_backend)
+
+$(document).ready(() -> new StaffGrading(backend))


Fix GET level versions

Fix GET level versions

--- server/routes/index.coffee
+++ server/routes/index.coffee
@@ -125,6 +125,7 @@
   app.get('/db/level/:handle/session', mw.auth.checkHasUser(), mw.levels.upsertSession)
   app.post('/db/level/:handle/patch', mw.auth.checkLoggedIn(), mw.patchable.postPatch(Level, 'level'))
   app.get('/db/level/:handle/patches', mw.patchable.patches(Level))
+  app.get('/db/level/:handle/versions', mw.versions.versions(Level))
   app.get('/db/level/:handle/version/?(:version)?', mw.versions.getLatestVersion(Level))

   LevelComponent = require '../models/LevelComponent'


Show info, warn and error in debug mode

Show info, warn and error in debug mode

--- main.coffee
+++ main.coffee
@@ -32,9 +32,9 @@
         return text

     getFileAndLine: ->
-        stacklist = (new Error()).stack.split('\n').slice(3);
-        nodeReg = /at\s+(.*)\s+\((.*):(\d*):(\d*)\)/gi;
-        browserReg = /at\s+()(.*):(\d*):(\d*)/gi;
+        stacklist = (new Error()).stack.split('\n').slice(3)
+        nodeReg = /at\s+(.*)\s+\((.*):(\d*):(\d*)\)/gi
+        browserReg = /at\s+()(.*):(\d*):(\d*)/gi

         firstLineStack = stacklist[0]
         fileAndLineInfos = nodeReg.exec(firstLineStack) or browserReg2.exec(firstLineStack)
@@ -60,16 +60,20 @@
         text

     info: (texts...) ->
-        console.info @format 'info', texts if process.env.NODE_ENV isnt 'test'
+        if process.env.DEBUG or process.env.NODE_ENV isnt 'test'
+            console.info @format 'info', texts

     warn: (texts...) ->
-        console.warn @format 'warn', texts if process.env.NODE_ENV isnt 'test'
+        if process.env.DEBUG or process.env.NODE_ENV isnt 'test'
+            console.warn @format 'warn', texts

     error: (texts...) ->
-        console.error @format 'error', texts if process.env.NODE_ENV isnt 'test'
+        if process.env.DEBUG or process.env.NODE_ENV isnt 'test'
+            console.error @format 'error', texts

     debug: (texts...) ->
-        console.info @format 'debug', texts if process.env.DEBUG
+        if process.env.DEBUG
+            console.info @format 'debug', texts

     raw: (texts...) ->
         console.log.apply console, texts


Fix for #1150: String interpolation regression

--- lexer.coffee
+++ lexer.coffee
@@ -412,7 +412,7 @@
             return str.slice 0, i + 1
           end = stack[stack.length - 1]
           continue
-      if end is '}' and letter in ['"', "'"]
+      if end is '}' and letter in ['"', "'", '/']
         stack.push end = letter
       else if end is '}' and letter is '{'
         stack.push end = '}'


added save button

added save button

--- imports/ui/menus/AppControls.cjsx
+++ imports/ui/menus/AppControls.cjsx
@@ -1,6 +1,6 @@
 React         = require 'react'

-{ Menu, Button, Icons, Layer, Form, Header, Heading, FormFields, Paragraph, Footer, Button } = require 'grommet'
+{ Box, Menu, Button, Icons, Layer, Form, Header, Heading, FormFields, Paragraph, Footer, Button } = require 'grommet'

 module.exports = React.createClass
@@ -22,8 +22,10 @@
   render: ->
     <span>
       <Menu pad='medium'>
+        <Button onClick={@props.onSaveApp} align='start' plain=true label='Save' icon={<Icons.Base.Save />}></Button>
+        <Button onClick={@showRemoveLayer} align='start' plain=true label='Remove' icon={<Icons.Base.Trash />}></Button>
+        <Box pad='medium'></Box>
         <Button onClick={@props.onStartApp} align='start' plain=true label='Start' icon={<Icons.Base.Play />}></Button>
-        <Button onClick={@showRemoveLayer} align='start' plain=true label='Remove' icon={<Icons.Base.Trash />}></Button>
       </Menu>
       <Layer onClose={@hideRemoveLayer} align='right' closer={true} hidden={not @state.showRemoveAppOverlay}>
        <Form compact=true>


correctly get width and height from canvas

--- bokehjs/src/coffee/common/plot.coffee
+++ bokehjs/src/coffee/common/plot.coffee
@@ -50,7 +50,7 @@
       html = @template(template_data)
       @$el.html(html)

-      @button_bar?.attr('style', "width:#{@canvas.get('canvas_width')}px;")
+      @button_bar?.attr('style', "width:#{@canvas.get('width')}px;")

       @$el.append(@canvas_view.$el)
       @canvas_view.render()
@@ -61,10 +61,10 @@
       @title_props = new text_properties(@, {}, 'title_')

       @view_state = new ViewState({
-        canvas_width:      @canvas.get('canvas_width')
-        canvas_height:     @canvas.get('canvas_height')
-        outer_width:       @canvas.get('canvas_width')
-        outer_height:      @canvas.get('canvas_height')
+        canvas_width:      @canvas.get('width')
+        canvas_height:     @canvas.get('height')
+        outer_width:       @canvas.get('width')
+        outer_height:      @canvas.get('height')
         x_offset:          0
         y_offset:          0
         min_border_top:    (options.min_border_top    ? @mget('min_border_top'))    ? @mget('min_border')
@@ -320,7 +320,7 @@

     _paint_empty: (ctx) ->
       ctx.fillStyle = @mget('border_fill')
-      ctx.fillRect(0, 0,  @canvas_view.mget('canvas_width'), @canvas_view.mget('canvas_height')) # TODO
+      ctx.fillRect(0, 0,  @canvas.get('width'), @canvas.get('height')) # TODO
       ctx.fillStyle = @mget('background_fill')
       ctx.fillRect(
         @view_state.get('border_left'), @view_state.get('border_top'),


Update .ctags pattern for unpacking

Update .ctags pattern for unpacking

--- build/tasks/generate-asar-task.coffee
+++ build/tasks/generate-asar-task.coffee
@@ -10,10 +10,10 @@

     unpack = [
       '*.node'
+      '*.ctags'
       'ctags-darwin'
       'ctags-linux'
       'ctags-win32.exe'
-      '\\.ctags'
     ]
     unpack = "{#{unpack.join(',')}}"


Activity: entryPoint related check reverted

--- client/Social/Activity/AppView.coffee
+++ client/Social/Activity/AppView.coffee
@@ -3,11 +3,10 @@
   {entryPoint, permissions, roles} = KD.config

   isGroup        = -> entryPoint?.type is 'group'
-  isKoding       = -> KD.getGroup().slug is 'koding'
+  isKoding       = -> entryPoint?.slug is 'koding'
   isMember       = -> 'member' in roles
   canListMembers = -> 'list members' in permissions
   isPrivateGroup = -> not isKoding() and isGroup()
-

   constructor:(options = {}, data)->


Client: Activity: Added ChannelNotificationSettingsModule

--- client/activity/lib/flux/index.coffee
+++ client/activity/lib/flux/index.coffee
@@ -1,5 +1,6 @@
 ChatInputModule = require './chatinput'
 CreateChannelModule = require './createchannel'
+ChannelNotificationSettingsModule = require './channelnotificationsettings'

 module.exports =
   getters   : require './getters'
@@ -43,6 +44,7 @@
   # module stores
   .concat ChatInputModule.stores
   .concat CreateChannelModule.stores
+  .concat ChannelNotificationSettingsModule.stores

   register: (reactor) ->
     reactor.registerStores @stores


Don't warn on json parsing error (log to debug channel instead)

Warnings are serious, and a json parsing error in this context isn't a big deal. It can interrupt the command line reporters as well.

--- src/execute-transaction.coffee
+++ src/execute-transaction.coffee
@@ -27,7 +27,7 @@
       try
         parsed = JSON.parse transaction.body
       catch e
-        logger.warn "Error parsing body as json: " + transaction.body
+        logger.debug "Error parsing body as json: " + transaction.body
         parsed = transaction.body
       transaction.body = parsed
     when 'text/html'


use env.subject.constructor instead of @model

--- src/extras/batman.rails.coffee
+++ src/extras/batman.rails.coffee
@@ -119,7 +119,7 @@

   @::before 'create', 'update', (env, next) ->
-    nestedAttributeKeys = @model._encodesNestedAttributesForKeys
+    nestedAttributeKeys = env.subject.constructor._encodesNestedAttributesForKeys
     return next() unless nestedAttributeKeys.length

     # if not serializing as form, the data has already been stringified
@@ -144,7 +144,7 @@
     next()

   @::after 'update', @skipIfError (env, next) ->
-    for key in @model._encodesNestedAttributesForKeys
+    for key in env.subject.constructor._encodesNestedAttributesForKeys
       association = env.subject.reflectOnAssociation(key)
       if !association?
         Batman.developer.error("No assocation was found for nested attribute #{key}")


Fixed so that sidebar drops can be clicked and opened in new tabs.

--- app/assets/javascripts/views/clouds/drops/list_item.js.coffee
+++ app/assets/javascripts/views/clouds/drops/list_item.js.coffee
@@ -9,6 +9,8 @@
   events:
     'click a.drop-title'      : 'visit'
     'click a.drop-preview'    : 'visit'
+    'click a.drop-big-title'  : 'visit'
+    'click a.drop-big-preview': 'visit'
     'click a.drop-vote-up'    : 'voteUp'
     'click a.drop-vote-down'  : 'voteDown'


Scroll to bottom when submitting a message.

--- app/assets/javascripts/web/views/messages_form_view.js.coffee
+++ app/assets/javascripts/web/views/messages_form_view.js.coffee
@@ -16,6 +16,7 @@
     textArea = @get('textArea')

     form.on 'submit', (e) ->
+      view.get('parentView.messagesListView').scrollToBottom()
       view.get('controller').send('saveMessage')
       e.preventDefault()
       return false


'release' mode bug with removing vars declarations fixed

--- index.coffee
+++ index.coffee
@@ -57,7 +57,11 @@
 				namespaces.push 'log'

 			bundle = bundle.replace ///\/\/<(\/)?development>;///g, '//<$1development>'
-			bundle = bundle.replace ///assert, |, assert///g, ''
+			bundle = bundle.replace /, assert,;/g, ', '
+			bundle = bundle.replace /\ assert, |, assert;/g, ' '
+			if opts.removeLogs
+				bundle = bundle.replace /, log,;/g, ', '
+				bundle = bundle.replace /\ log, |, log;/g, ' '
 			cleaner = groundskeeper
 				console: true
 				namespace: namespaces


Fix undefined children bug

Fix undefined children bug

--- index.coffee
+++ index.coffee
@@ -20,7 +20,8 @@
       str

   processWord: (dom, idxPage, idxPar, idxLine) ->
-    @result[idxPage].par[idxPar].line[idxLine].words.push({id: dom.attribs.id, infos: @processBox(dom.attribs.title), data: dom.children[0].children[0].data})
+    if (dom.children && dom.children.length > 0 && dom.children[0].children && dom.children[0].children.length > 0)
+      @result[idxPage].par[idxPar].line[idxLine].words.push({id: dom.attribs.id, infos: @processBox(dom.attribs.title), data: dom.children[0].children[0].data})

   processLine: (dom, idxPage, idxPar) ->
     @result[idxPage].par[idxPar].line.push({id: dom.attribs.id, infos: @processBox(dom.attribs.title), words: []})


Uncomment hammurabi

Uncomment hammurabi

--- index.coffee
+++ index.coffee
@@ -7,7 +7,7 @@
 _ = require 'lodash'
 Request = require 'request'
 FBMessage = require './fb_message'
-#hammurabi = require './hammurabi'
+hammurabi = require './hammurabi'
 Parse = require 'parse/node'
 giphy = require('giphy-api')()
 users = []


Only require task if project has path

Only require task if project has path

--- index.coffee
+++ index.coffee
@@ -1,5 +1,4 @@
 DeferredAtomPackage = require 'deferred-atom-package'
-LoadPathsTask = require './src/load-paths-task'

 module.exports =
 class FuzzyFinder extends DeferredAtomPackage
@@ -16,6 +15,7 @@

     if rootView.project.getPath()?
       callback = (paths) => @projectPaths = paths
+      LoadPathsTask = require 'fuzzy-finder/src/load-paths-task'
       new LoadPathsTask(rootView, callback).start()

   onLoadEvent: (event, instance) ->


chat: remove reduntant jview mixin

chat: remove reduntant jview mixin

--- client/ide/views/chat/chatview.coffee
+++ client/ide/views/chat/chatview.coffee
@@ -1,6 +1,4 @@
 class IDE.ChatView extends KDTabView
-
-  JView.mixin @prototype

   constructor: (options = {}, data)->


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/function_extensions.coffee
+++ src/function_extensions.coffee
@@ -7,15 +7,6 @@
     ran = true

     return memo = this.apply(this, arguments)
-
-  _.once = function(func) {
-    var ran = false, memo;
-    return function() {
-      if (ran) return memo;
-      ran = true;
-      return memo = func.apply(this, arguments);
-    };
-  };

 Function::withBefore = (interception) ->
   method = this


daily

daily

--- application/game/server/boot/routes.coffee
+++ application/game/server/boot/routes.coffee
@@ -2,18 +2,15 @@
 liquid = require('liquid.coffee')
 module.exports = (app, mod) ->

-  app.get "/", (req, res) ->
+  app.all "/", (req, res) ->
     mod.render(res, 'index')
     return

-  app.get "/admin", (req, res) ->
+  app.all "/admin", (req, res) ->
     mod.render(res, 'admin')
     return

-  app.get "/asteroids", (req, res) ->
+  app.all "/asteroids", (req, res) ->
     res.redirect "games/asteroids/asteroids.html"
     return

-  app.post "/asteroids", (req, res) ->
-    res.redirect "games/asteroids/asteroids.html"
-    return


Initial masxasfjkshdfhjkdghjk

Initial masxasfjkshdfhjkdghjk

--- resources/assets/coffee/react/profile-page/badges.coffee
+++ resources/assets/coffee/react/profile-page/badges.coffee
@@ -24,7 +24,7 @@
     super props

     @slideTimer = 5000
-    @initialSlide = Math.max(2000, @props.badges.length * 500)
+    @initialSlide = Math.min(2000, @props.badges.length * 500)
     @timeouts = {}
     @intervals = {}


Handle popover in addition to tooltip

Handle popover in addition to tooltip

--- src/resources-packaged/ops/javascript/orbeon/xforms/control/LHHA.coffee
+++ src/resources-packaged/ops/javascript/orbeon/xforms/control/LHHA.coffee
@@ -4,9 +4,13 @@
     # - LHHA shown as tooltip have the xforms-appearance-tooltip class
     _.each $('.fr-body .xforms-control'), (control) ->
         control = $(control)
-        hintEl = control.find('.xforms-hint')
-        hintEl.addClass('xforms-hidden')
-        hintEl.addClass('xforms-appearance-tooltip')
+        selectorAppearance = _.pairs
+            '.xforms-hint': 'xforms-appearance-tooltip'
+            '.xforms-help': 'xforms-appearance-popover'
+        _.each selectorAppearance, ([selector, appearance]) ->
+            el = control.find(selector)
+            el.addClass('xforms-hidden')
+            el.addClass(appearance)

     # Keep track of whether a tooltip is shown and this is because a control got the focus
     # - used to diactivate hiding/showing tooltip based on mouse movements
@@ -14,43 +18,58 @@

     $('.fr-body').on 'mouseenter mouseleave focusin focusout', '.xforms-control', (event) ->
         controlEl = $(event.target).closest('.xforms-control')
-        tooltipEl = controlEl.find('.xforms-appearance-tooltip')
-        controlHasTooltip = tooltipEl.is('*')
-        if controlHasTooltip
-            Tooltip.init(controlEl, tooltipEl)
+        lhhaEls = controlEl.find('.xforms-appearance-tooltip, .xforms-appearance-popover')
+        controlHasTooltipPopover = lhhaEls.is('*')
+        if controlHasTooltipPopover
+            TooltipPopover.init(controlEl, lhhaEls)
             if ! tooltipShownBecauseOfFocus
-                if event.type == 'mouseenter' then Tooltip.show(controlEl)
-                if event.type == 'mouseleave' then Tooltip.hide(controlEl, event)
+                if event.type == 'mouseenter' then TooltipPopover.show(controlEl)
+                if event.type == 'mouseleave' then TooltipPopover.hide(controlEl, event)
             if event.type == 'focusin'
-                Tooltip.show(controlEl)
+                TooltipPopover.show(controlEl)
                 tooltipShownBecauseOfFocus = true
             if event.type == 'focusout'
-                Tooltip.hide(controlEl, event)
+                TooltipPopover.hide(controlEl, event)
                 tooltipShownBecauseOfFocus = false

-    # Facade for Bootstrap's show/hide
+    # Facade for Bootstrap's Tooltip component
     # - ensures we don't show an already visible tooltip, which would make it "blink"
-    Tooltip =
-        _tooltipShownForEl: null
+    TooltipPopover =
+        DataPrefix: 'xforms-tooltip-popover-'
+        Initialized: @DataPrefix + 'initialized'
+        Methods: @DataPrefix + 'methods'
+        _tooltipPopoverShownForControlEl: null

         # Create the underlying Bootstrap tooltip (but don't show it just yet)
-        init: (controlEl, tooltipEl) ->
-            tooltipAlreadyInitialized = controlEl.data('xforms-tooltip-initialized') == 'true'
-            if ! tooltipAlreadyInitialized
-                controlEl.tooltip
-                    placement: 'bottom',
-                    trigger: 'manual',
-                    title: tooltipEl.text()
-                controlEl.data('xforms-tooltip-initialized', 'true')
+        init: (controlEl, lhhaEls) ->
+            alreadyInitialized = controlEl.data(@Initialized) == 'true'
+            if ! alreadyInitialized
+                methods = []
+                _.each lhhaEls, (lhhaEl) ->
+                    lhhaEl = $(lhhaEl)
+                    if lhhaEl.hasClass('xforms-appearance-tooltip')
+                        controlEl.tooltip
+                            placement: 'bottom',
+                            trigger: 'manual',
+                            title: lhhaEl.text()
+                        methods.push('tooltip')
+                    if lhhaEl.hasClass('xforms-appearance-popover')
+                        controlEl.popover
+                            placement: 'right',
+                            trigger: 'manual',
+                            title: controlEl.find('.xforms-label').text()
+                            content: lhhaEl.text()
+                        methods.push('popover')
+                controlEl.data(@Methods, methods)

         # Show tooltip for control
         # - but don't show if the tooltip is alrady shown for this control
         show: (controlEl) ->
-            sameControl = controlEl.is(@_tooltipShownForEl)
+            sameControl = controlEl.is(@_tooltipPopoverShownForControlEl)
             if ! sameControl
-                @_tooltipShownForEl.tooltip('hide') if @_tooltipShownForEl?
-                controlEl.tooltip('show')
-                @_tooltipShownForEl = controlEl
+                @_callMethods(@_tooltipPopoverShownForControlEl, 'hide') if @_tooltipPopoverShownForControlEl?
+                @_callMethods(controlEl, 'show')
+                @_tooltipPopoverShownForControlEl = controlEl

         # Hide tooltip for control
         # - but don't hide if the element we're going to (if any) is the same as the current one
@@ -59,5 +78,9 @@
             toControlEl = toTargetEl.closest('.xforms-control')
             sameControl = controlEl.is(toControlEl)
             if ! sameControl
-                controlEl.tooltip('hide')
-                @_tooltipShownForEl = null
+                @_callMethods(controlEl, 'hide')
+                @_tooltipPopoverShownForControlEl = null
+
+        _callMethods: (controlEl, arg) ->
+            methods = controlEl.data(@Methods)
+            _.each methods, (method) -> controlEl[method].call(controlEl, arg)


This component is no longer needed now that ListenNetwork sends process errors

--- components/ListenProcessErrors.coffee
+++ components/ListenProcessErrors.coffee
@@ -1,40 +0,0 @@
-noflo = require 'noflo'
-
-exports.getComponent = () ->
-  c = new noflo.Component
-  c.description = 'Listen to process errors on a runtime'
-
-  c.inPorts.add 'runtime',
-    datatype: 'object'
-    description: 'FBP Runtime instance'
-    required: yes
-    process: (event, payload) ->
-      return unless event is 'data'
-      c.updateListener payload
-
-  c.outPorts.add 'process',
-    datatype: 'string'
-    description: 'Process id that generated the error'
-  c.outPorts.add 'message',
-    datatype: 'string'
-    description: 'Error message'
-
-  c.updateListener = (runtime) ->
-    @removeListener()
-    @runtime = runtime
-    @listener = (message) =>
-      return unless message.command is 'processerror'
-      @outPorts.process.send message.payload.id
-      @outPorts.message.send message.payload.error
-      @outPorts.process.disconnect()
-      @outPorts.message.disconnect()
-    @runtime.on 'network', @listener if @runtime
-
-  c.removeListener = () ->
-    return unless @runtime
-    @runtime.removeListener 'network', @listener
-
-  c.shutdown = () ->
-    @removeListener()
-
-  c


test(engine): check fs link with exec

test(engine): check fs link with exec

--- packages/engine/test/actions/fs/link.coffee
+++ packages/engine/test/actions/fs/link.coffee
@@ -40,6 +40,13 @@
         target: "#{tmpdir}/test"
         exec: true
       .should.be.finally.containEql status: false
+      @fs.assert
+        target: "#{tmpdir}/test"
+        content: """
+        #!/bin/bash
+        exec #{__filename} $@
+        """
+        trim: true

   they 'should link dir', ({ssh}) ->
     # Create a non existing link


TeamMembersCommonView: Handle lazy load threshold and list more users.

--- client/admin/lib/views/members/teammemberscommonview.coffee
+++ client/admin/lib/views/members/teammemberscommonview.coffee
@@ -34,12 +34,14 @@
         itemOptions       : listViewItemOptions
       noItemFoundWidget   : noItemFoundWidget
       startWithLazyLoader : yes
-      lazyLoadThreshold   : .90
+      lazyLoadThreshold   : .99
       lazyLoaderOptions   :
         spinnerOptions    :
           size            : width: 28

     @addSubView @listController.getView()
+
+    @listController.on 'LazyLoadThresholdReached', @bound 'fetchMembers'

   fetchMembers: ->


flux/environment: set test machine's type to 'own'

Signed-off-by: Umut Sirin <4055abbc0e40a1254e715b952ccd414607e0b365@gmail.com>

--- client/app/lib/flux/environment/stores/machinesstore.coffee
+++ client/app/lib/flux/environment/stores/machinesstore.coffee
@@ -95,4 +95,5 @@

     machines.setIn [id, 'sharedUsers'], toImmutable users

-  set: (machines, machine) -> machines.set machine._id, toImmutable machine
+  set: (machines, machine) ->
+    machines.set machine._id, toImmutable(machine).set('type', 'own')


allow to pass filename as second arg

allow to pass filename as second arg

--- src/main.coffee
+++ src/main.coffee
@@ -62,9 +62,19 @@
         G.repository[k] = pats

     if print
-      if print == "CSON"
+      if print.match /\.cson$/
+        CSON = require "season"
+        fs   = require "fs"
+
+        fs.writeFileSync print, CSON.stringify(G)
+
+      else if print.match /\.json$/
+        fs.writeFileSync print, JSON.stringify(G, null, "    ")
+
+      else if print == "CSON"
         CSON = require "season"
         process.stdout.write CSON.stringify(G)
+
       else
         process.stdout.write JSON.stringify(G, null, "    ")


Typo.

Typo.

--- src/main.coffee
+++ src/main.coffee
@@ -3,7 +3,7 @@
 # All Rights Reserved.

 app = require "app"
-SpringseedWindow = require './springseed'
+SpringseedWindow = require './Springseed'

 app.on 'ready', ->
   window = new SpringseedWindow()


WebSocket errors should not crash the server.

--- src/main.coffee
+++ src/main.coffee
@@ -170,6 +170,10 @@
       # response.
       socket.send response if socket.readyState is socket.OPEN

+    socket.on 'error', $fiberize (error) ->
+      console.error '[WARN] WebSocket', error
+      socket.close()
+
   # Creates a default user if there is none.
   unless $wait xo.users.exists()
     email = 'admin@admin.net'


re-form main.coffee

re-form main.coffee

--- src/main.coffee
+++ src/main.coffee
@@ -51,27 +51,25 @@

     @private
     ###
-    @startLoopback: ->
+    @startLoopback: -> new Promise (resolve, reject) ->

-        return new Promise (resolve, reject) ->
+        timer = setTimeout ->
+            reject new Error('timeout after 30sec')
+        , 30 * 1000

-            timer = setTimeout ->
-                reject new Error('timeout after 30sec')
-            , 30 * 1000
+        lbProcess = require('child_process').spawn ['node', __dirname + '/../server/server.js']
+        lbProcess.stdout.setEncoding 'utf8'

-            lbProcess = require('child_process').spawn ['node', __dirname + '/../server/server.js']
-            lbProcess.stdout.setEncoding 'utf8'
+        prevChunk = ''

-            prevChunk = ''
+        lbProcess.stdout.on 'data', (chunk) ->
+            data = prevChunk + chunk

-            lbProcess.stdout.on 'data', (chunk) ->
-                data = prevChunk + chunk
+            if data.match('LOOPBACK_WITH_DOMAIN_STARTED')
+                clearTimeout timer
+                resolve()

-                if data.match('LOOPBACK_WITH_DOMAIN_STARTED')
-                    clearTimeout timer
-                    resolve()
-
-                prevChunk = chunk
+            prevChunk = chunk


Fixing Vector.copy; Added isZeroVector

Fixing Vector.copy; Added isZeroVector

--- src/math.coffee
+++ src/math.coffee
@@ -69,10 +69,17 @@
     #
     # @return {Object} a normalized vector
     normalize : () ->
-        if @x == 0 and @y == 0
-            return new timber.Vector(0, 0)
+        return @copy() if @isZeroVector()
         length = this.getLength()
         return new timber.Vector(@x/length, @y/length)
+
+    # Return true if this vector is the zero vector, false otherwise.
+    isZeroVector : () ->
+        return @x == 0 and @y == 0
+
+    # Return a copy of this vector.
+    copy: () ->
+        return new timber.Vector(@x, @y)

     # Return a new vector scaled by the given value.
     #


added more verbose output

added more verbose output

--- src/mist.coffee
+++ src/mist.coffee
@@ -17,19 +17,23 @@
 ninjaProc = process.env.NINJA || 'ninja'

 runNinja = (ninja)->
-  console.log 'running Ninja'
   console.log 'platform:', os.platform()
   switch os.platform()
     when 'darwin' or 'linux'
-      stream = new (require 'stream').Readable
+      console.log 'running Ninja'
       proc = spawn ninjaProc, [ '-vf', '/dev/stdin' ], stdio: [ null,
         process.stdout, process.stderr ]
+      console.log 'uploading translated Mist configuration to Ninja...'
       proc.stdin.write ninja
+      console.log 'upload complete'
       proc.stdin.end()
     else
+      console.log 'streaming translated Mist configuration to disk...'
       tmpFile = tmp.fileSync()
       fs.writeSync tmpFile.fd, ninja
+      console.log 'streaming complete'
       fs.closeSync tmpFile.fd
+      console.log 'running Ninja'
       spawn ninjaProc, [ '-vf', tmpFile.name ], stdio: [ 'pipe',
         process.stdout, process.stderr ]


Improved molo

Improved molo

* Added quite a few comments
* Anonymous module are now possible
* Script queuing works as intended

--- src/molo.coffee
+++ src/molo.coffee
@@ -6,14 +6,20 @@
   queue = {}

   moloFunc = (name, defines) ->
-    return undefined unless name and defines
+    # If name is not a string, it's an anonymous module (Put defines parameter in the correct order)
+    if typeof name isnt 'string' then [name, defines] = [undefined, name]

+    # Get properties from defines if it's an object, else they are no dependencies and take the function as the factory
     if typeof defines is 'function' then define = defines else {define, require: deps, context} = defines

+    # Default parameters, if they have not been defined yet
     deps = [] unless deps?
     context = @ unless context?

+    # If a single dependency has been declared as a string, make it into an array
     if typeof deps is 'string' then deps = [deps]
+
+    # Get the result of cached dependencies
     cacheDeps = []
     for i in deps
       if cache[i]
@@ -22,14 +28,25 @@
         queue[name] = defines
         return undefined

+    # Execute factory function and store it in cache
     cache[name] = define.apply context, cacheDeps

     # Check queue
-    if Object.keys(queue).length > 0
-      for own key, value of queue
-        module key, value
+    queueList = Object.keys(queue)

-      delete queue[key]
+    for q in queueList
+      # Have all required dependencies been loaded?
+      maxDeps = queue[q].require.length
+      curDeps = 0
+
+      (curDeps++ if cache[d]) for d in queue[q].require
+
+      # Reload module and delete item from queue
+      if curDeps is maxDeps
+        modName = q
+        modDefinition = queue[q]
+        delete queue[q]
+        moloFunc modName, modDefinition

     null

@@ -37,11 +54,22 @@

   root.molo = moloFunc
   root.molo.config = moloConfig
+  root.molo.clear = ->
+    cache = {}
+    queue = {}

-  root.module = moloFunc
+  root.molo.delete = (name) ->
+    delete cache[name] if cache[name]
+    delete queue[name] if queue[name]
+
+  # Overwrite module only if it hasn't been defined
+  root.module or= moloFunc

   # Compability to official definition
   root.define = (name, deps, defines) ->
     moloFunc name,
       require: deps,
-      define: defines+      define: defines
+
+  root.define.amd =
+    jQuery: true


cleanup debug statements

cleanup debug statements

Former-commit-id: 98ef48c5340c696e7026578d3e4f232d6368c6ea [formerly 28e947f2bd53fd20718bac6135d2774af170a9bf] [formerly dbf0d3b40c53cd6afb8d698ea7c61f528695d7dc [formerly e7ba431ee5f653927e1c78269f9a17e602020798]] [formerly ab5a8b68024e1258ee98f1b36d9e45184652b2fd [formerly 70f5bf6ee7193f72ec8b72d0fe90dcad32b8b093] [formerly c651aea236a09e9690741f89bf3e5bc0241dd2fe [formerly c651aea236a09e9690741f89bf3e5bc0241dd2fe [formerly 36a45f753020053bcf1cb9645aa7212fec1b2cae]]]]
Former-commit-id: 4546fb7968a97af49b01397db0befb050419c794 [formerly 6ca662274cfc0a44dce10d7aaf263a71b665ca50] [formerly 21582836bef0fe6ad43d040c10a80c0571a43f7f [formerly bb4d674c5d8bed0edf12034eb4ffd993a54e8dd3]]
Former-commit-id: 677dce51b9a7afdfec87306dd7fe082600f20d38 [formerly ffea915eaddeb95bf8502fd3a721c2a24e7c369a]
Former-commit-id: eca01fc38cfae2666866fec5f89dcb5d1429bdd7
Former-commit-id: 95e94b5e501782ffdf1a62250541738a869e2920

--- @client/assets/javascripts/vendor_extensions/jquery_extensions.js.coffee
+++ @client/assets/javascripts/vendor_extensions/jquery_extensions.js.coffee
@@ -46,12 +46,11 @@
         $el[0].style['-moz-transform'] = ""
         return

-      #Get the top of the reference element. If the container moves, would need to move this into scroll handler.
+      #Get the top of the reference element.
       #If the container is translated Y, then this method will fail I believe.
       container_top = options.container.offset().top
       element_top = $el.offset().top - current_translate

-      console.log 'tops', container_top, element_top

       # Need to reset element's height each scroll event because it may have change height
       # since initialization.
@@ -70,26 +69,18 @@
       if is_scrolling_up
         if effective_viewport_top < container_top # if we're scrolled past container top
           new_translate = 0
-          # console.log 1, new_translate
-
         else if effective_viewport_top < element_top + current_translate
           new_translate = effective_viewport_top - element_top
-          # console.log 2, new_translate
-
       else if element_fits_in_viewport
         if effective_viewport_top > element_top + current_translate
           new_translate = effective_viewport_top - element_top
-          # console.log 3, new_translate
-
       else # scrolling down
         container_height = options.container.height()
         container_bottom = container_top + container_height #warning: checking height is performance no-no
         if effective_viewport_bottom > container_bottom #scrolled past container bottom
           new_translate = container_bottom - (element_top + element_height)
-          # console.log 4, new_translate
         else if effective_viewport_bottom > element_top + element_height + current_translate
           new_translate = effective_viewport_bottom - (element_top + element_height)
-          # console.log 5, new_translate

       if new_translate != null
         is_undocking = current_translate == 0 && new_translate != 0
@@ -110,7 +101,6 @@
       last_viewport_top = viewport_top

     update()
-

   $.fn.ensureInView = (options = {}) ->


changed external room type to direct message

--- packages/rocketchat-external/methods.coffee
+++ packages/rocketchat-external/methods.coffee
@@ -38,7 +38,7 @@
 		check message.rid, String
 		check message.token, String

-		user = Meteor.users.findOne Meteor.userId(), fields: username: 1
+		guest = Meteor.users.findOne Meteor.userId(), fields: username: 1

 		room = ChatRoom.findOne message.rid

@@ -52,30 +52,30 @@

 			ChatRoom.insert
 				_id: message.rid
-				name: user.username
+				name: guest.username
 				msgs: 1
 				lm: new Date()
-				usernames: [ operator.username, user.username ]
-				t: 'p'
+				usernames: [ operator.username, guest.username ]
+				t: 'd'
 				ts: new Date()
 				v:
 					token: message.token

 			ChatSubscription.insert
 				rid: message.rid
-				name: user.username
+				name: guest.username
 				alert: true
 				open: true
 				unread: 1
 				u:
 					_id: operator._id
 					username: operator.username
-				t: 'p'
+				t: 'd'

-		room = Meteor.call 'canAccessRoom', message.rid, user._id
+		room = Meteor.call 'canAccessRoom', message.rid, guest._id

 		if not room
 			throw new Meteor.Error 'cannot-acess-room'

-		RocketChat.sendMessage user, message, room
+		RocketChat.sendMessage guest, message, room


Send invite email and notification in the background

--- services/web/app/coffee/Features/Collaborators/CollaboratorsInviteHandler.coffee
+++ services/web/app/coffee/Features/Collaborators/CollaboratorsInviteHandler.coffee
@@ -77,10 +77,12 @@
 				if err?
 					logger.err {err, projectId, sendingUserId: sendingUser._id, email}, "error saving token"
 					return callback(err)
+				# Send email and notification in background
 				CollaboratorsInviteHandler._sendMessages projectId, sendingUser, invite, (err) ->
 					if err?
 						logger.err {projectId, email}, "error sending messages for invite"
-					callback(err, invite)
+				callback(null, invite)
+

 	revokeInvite: (projectId, inviteId, callback=(err)->) ->
 		logger.log {projectId, inviteId}, "removing invite"


Track middle button mouse down events on Linux

--- src/text-editor-component.coffee
+++ src/text-editor-component.coffee
@@ -554,7 +554,11 @@
       scrollViewNode.scrollLeft = 0

   onMouseDown: (event) ->
-    return unless event.button is 0 # only handle the left mouse button
+    unless event.button is 0 or (event.button is 1 and process.platform is 'linux')
+      # Only handle mouse down events for left mouse button on all platforms
+      # and middle mouse button on Linux since it pastes the selection clipboard
+      return
+
     return if event.target?.classList.contains('horizontal-scrollbar')

     {editor} = @props


Add "wheel" scroll event support.

Add "wheel" scroll event support.

--- src/traits/ScrollableView.coffee
+++ src/traits/ScrollableView.coffee
@@ -31,6 +31,12 @@
 scrolling = false

+wheelTimer = null
+
+
+WHEEL_TIME = 100
+
+
 ###*
 # Maximum scrollable height.
 #
@@ -50,15 +56,23 @@
 reference = 0

+startScrolling = (e) ->
+  scrolling = true
+  reference = ypos e
+  max = parseInt(getComputedStyle(@root.firstChild).height) - window.innerHeight
+
+
+stopScrolling = ->
+  scrolling = false
+
+
 ###*
 # `touchstart` handler for scrollable view.
 #
 #
 ###
 onTouchStart = (e) ->
-  scrolling = true
-  reference = ypos e
-  max = parseInt(getComputedStyle(@root.firstChild).height) - window.innerHeight
+  startScrolling.call @, e
   e.preventDefault()
   e.stopPropagation()
   return
@@ -71,7 +85,7 @@
 #
 ###
 onTouchEnd = (e) ->
-  scrolling = false
+  stopScrolling()
   e.preventDefault()
   e.stopPropagation()
   return
@@ -83,7 +97,7 @@
 #
 #
 ###
-onTouchMove = (e) ->
+onMove = (e) ->
   if scrolling
     y = ypos(e)
     delta = reference - y
@@ -93,6 +107,20 @@
   e.preventDefault()
   e.stopPropagation()
   return
+
+
+
+onWheel = (e) ->
+  if wheelTimer then clearTimeout wheelTimer
+
+  if not scrolling then startScrolling.call @, e
+
+  wheelTimer = setTimeout ->
+    stopScrolling()
+  , WHEEL_TIME
+
+  onMove.call @, e
+

@@ -106,7 +134,12 @@
   prefix @root.firstChild, 'transform', "translateY(#{-offset}px)"

-ypos = (e) -> e.targetTouches[0].clientY
+
+ypos = (e) ->
+  if e.targetTouches
+    e.targetTouches[0].clientY
+  else if e instanceof WheelEvent
+    reference - e.deltaY

@@ -131,4 +164,5 @@
     @classes.add SCROLLABLE_CLS
     @addEventListener 'touchstart', onTouchStart, @
     @addEventListener 'touchend', onTouchEnd, @
-    @addEventListener 'touchmove', onTouchMove, @
+    @addEventListener 'touchmove', onMove, @
+    @addEventListener 'wheel', onWheel, @


Include api key for taxon reordering.

Include api key for taxon reordering.

Fixes #6206

--- backend/app/assets/javascripts/spree/backend/taxons.js.coffee
+++ backend/app/assets/javascripts/spree/backend/taxons.js.coffee
@@ -6,6 +6,7 @@
       url: Spree.routes.classifications_api,
       method: 'PUT',
       data:
+        token: Spree.api_key,
         product_id: ui.item.data('product-id'),
         taxon_id: $('#taxon_id').val(),
         position: ui.item.index()


Store 'parts' value on related rectangle tool

--- overrides.coffee
+++ overrides.coffee
@@ -91,8 +91,8 @@
     label = 'Finish'
   decisionTree.currentTask.confirmButton.innerHTML = label if label?

-  if key is 'details'
-    current_tool.mark.details = value
+  current_tool.mark.details = value if key is 'details'
+  current_tool.mark.parts = value if key is 'parts'

 classify_page.on classify_page.LOAD_SUBJECT, (e, subject)->
   ms.rescale 0, 0, subjectViewer.maxWidth, subjectViewer.maxHeight


Workspace: Fixed isJoinedASession flag in CollaborativePane.

--- client/landing/CollaborativeWorkspace/panes/collaborativepane.coffee
+++ client/landing/CollaborativeWorkspace/panes/collaborativepane.coffee
@@ -10,8 +10,8 @@
     @workspace        = @panel.getDelegate()
     @sessionKey       = @getOptions().sessionKey or @createSessionKey()
     @workspaceRef     = @workspace.firebaseRef.child @sessionKey
-    @isJoinedASession = @getOptions().sessionKey
     @amIHost          = @workspace.amIHost()
+    @isJoinedASession = @getOptions().sessionKey and not @amIHost
     @container        = new KDView cssClass: "ws-container"

   createSessionKey: ->


Hide follow buttons for guest in memberviews

--- client/landing/app/Applications/Members.kdapplication/AppView.coffee
+++ client/landing/app/Applications/Members.kdapplication/AppView.coffee
@@ -30,7 +30,8 @@
         height: options.avatarSizes[1]
     , memberData

-    if memberData.profile.nickname is KD.whoami().profile.nickname
+    if (memberData.profile.nickname is KD.whoami().profile.nickname) or \
+        memberData.type is 'unregistered'
     then @followButton = new KDView
     else @followButton = new MemberFollowToggleButton
       style       : "follow-btn"


Couchmuffin findManyById fixed

Couchmuffin findManyById fixed

--- server/src/Component/Builtin/Database/CouchMuffin.coffee
+++ server/src/Component/Builtin/Database/CouchMuffin.coffee
@@ -91,11 +91,10 @@
     findManyById: (params, callback) ->
         @_method = 'findManyById'
         ids = params.ids || null
-        options = params.options || {}
         idsWithPrefix = []
         idsWithPrefix = ("#{@_keyPrefix}#{value}" for value in ids)

-        @_dataSource.bucket.getMulti idsWithPrefix, options, (error, result) ->
+        @_dataSource.bucket.getMulti idsWithPrefix, (error, result) ->
             return callback error if error? and !_.isNumber error
             return callback null, result


DRY up writing to dump-folder in FileWriter

--- services/web/app/coffee/infrastructure/FileWriter.coffee
+++ services/web/app/coffee/infrastructure/FileWriter.coffee
@@ -7,13 +7,18 @@

 module.exports = FileWriter =

-	writeLinesToDisk: (identifier, lines, callback = (error, fsPath)->) ->
-		callback = _.once(callback)
-		fsPath = "#{Settings.path.dumpFolder}/#{identifier}_#{uuid.v4()}"
+	_ensureDumpFolderExists: (callback=(error)->) ->
 		fs.mkdir Settings.path.dumpFolder, (error) ->
 			if error? and error.code != 'EEXIST'
 				# Ignore error about already existing
 				return callback(error)
+			callback(null)
+
+	writeLinesToDisk: (identifier, lines, callback = (error, fsPath)->) ->
+		callback = _.once(callback)
+		fsPath = "#{Settings.path.dumpFolder}/#{identifier}_#{uuid.v4()}"
+		FileWriter._ensureDumpFolderExists (error) ->
+			return callback(error) if error?
 			fs.writeFile fsPath, lines.join('\n'), (error) ->
 				return callback(error) if error?
 				callback(null, fsPath)
@@ -23,11 +28,9 @@
 		fsPath = "#{Settings.path.dumpFolder}/#{identifier}_#{uuid.v4()}"

 		stream.pause()
-		fs.mkdir Settings.path.dumpFolder, (error) ->
+		FileWriter._ensureDumpFolderExists (error) ->
+			return callback(error) if error?
 			stream.resume()
-			if error? and error.code != 'EEXIST'
-				# Ignore error about already existing
-				return callback(error)

 			writeStream = fs.createWriteStream(fsPath)
 			stream.pipe(writeStream)


spec: ignore fragile spec for now

spec: ignore fragile spec for now

Change-Id: I8d23885d7f774c688a1f3894d0edf30e36a997f5
Reviewed-on: https://gerrit.instructure.com/28852
Tested-by: Jenkins <d95b56ce41a2e1ac4cecdd398defd7414407cc08@instructure.com>
Reviewed-by: Bryan Madsen <0a3ba1af216659500dd8f41e29f3979f42452870@instructure.com>
QA-Review: Bryan Madsen <0a3ba1af216659500dd8f41e29f3979f42452870@instructure.com>
Product-Review: Bryan Madsen <0a3ba1af216659500dd8f41e29f3979f42452870@instructure.com>

--- spec/coffeescripts/views/assignments/EditViewSpec.coffee
+++ spec/coffeescripts/views/assignments/EditViewSpec.coffee
@@ -107,26 +107,26 @@
     ENV.GROUP_CATEGORIES = null
     ENV.ASSIGNMENT_GROUPS = null

-  test 'shows a warning when dangerously changing group status', ->
-    # bleh
-    window.addGroupCategory = ->
+  # test 'shows a warning when dangerously changing group status', ->
+  #   # bleh
+  #   window.addGroupCategory = ->

-    checkWarning = (view, showsWarning) ->
-      $("#assignment_toggle_advanced_options").click()
-      equal $(".group_submission_warning").is(":visible"), false
-      $("#assignment_has_group_category").click()
-      equal $(".group_submission_warning").is(":visible"), showsWarning
-      $("#assignment_has_group_category").click()
-      equal $(".group_submission_warning").is(":visible"), false
+  #   checkWarning = (view, showsWarning) ->
+  #     $("#assignment_toggle_advanced_options").click()
+  #     equal $(".group_submission_warning").is(":visible"), false
+  #     $("#assignment_has_group_category").click()
+  #     equal $(".group_submission_warning").is(":visible"), showsWarning
+  #     $("#assignment_has_group_category").click()
+  #     equal $(".group_submission_warning").is(":visible"), false

-    # should warn here
-    view = editView has_submitted_submissions: true
-    checkWarning view, true
+  #   # should warn here
+  #   view = editView has_submitted_submissions: true
+  #   checkWarning view, true

-    # don't show the warning if you start out with a group
-    view = editView has_submitted_submissions: true, group_category_id: 1
-    checkWarning view, false
+  #   # don't show the warning if you start out with a group
+  #   view = editView has_submitted_submissions: true, group_category_id: 1
+  #   checkWarning view, false

-    # don't show the warning if there are no submitted submissions
-    view = editView
-    checkWarning view, false
+  #   # don't show the warning if there are no submitted submissions
+  #   view = editView
+  #   checkWarning view, false


facebook callback change

facebook callback change

--- app/assets/javascripts/facebook.js.coffee
+++ app/assets/javascripts/facebook.js.coffee
@@ -13,7 +13,7 @@
   $('#sign_in').click (e) ->
     e.preventDefault()
     FB.login (response) ->
-      window.location = '/auth/facebook' if response.authResponse
+      window.location = '/auth/facebook/callback' if response.authResponse

   $('#sign_out').click (e) ->
     FB.getLoginStatus (response) ->


Fix start/end position for transitions

Fix start/end position for transitions

--- app/assets/javascripts/projects.js.coffee
+++ app/assets/javascripts/projects.js.coffee
@@ -33,7 +33,11 @@
       .style("visibility", (d) -> "hidden" if d.type == "project")
       .attr("id", (d) -> "task-" + d.taskId)
       .attr("transform", (d) ->
-          "translate(" + source.y0 + "," + source.x0 + ")"
+          if source.y0 == source.y
+            y = source.rightPoint()
+          else
+            y = source.y0
+          "translate(" + y + "," + source.x0 + ")"
         )
       .on("click", (d) ->
         toggle d
@@ -114,9 +118,14 @@
       .style("fill-opacity", 1)

     # Transition exiting nodes to the parent's new position.
-    nodeExit = node.exit().transition().duration(duration).attr("transform", (d) ->
-      "translate(" + source.y + "," + source.x + ")"
-    ).remove()
+    nodeExit = node.exit()
+      .transition()
+      .duration(duration)
+      .attr("transform", (d) ->
+        "translate(" + source.rightPoint() + "," + source.x + ")"
+      )
+      .remove()
+
     nodeExit.select("circle").attr "r", 1e-6
     nodeExit.select("text").style "fill-opacity", 1e-6

@@ -134,7 +143,7 @@
       .attr("d", (d) ->
         o =
           x: source.x0
-          y: source.y0
+          y: source.rightPoint()

         diagonal
           source: o
@@ -148,7 +157,7 @@
     link.exit().transition().duration(duration).attr("d", (d) ->
       o =
         x: source.x
-        y: source.y
+        y: source.rightPoint()

       diagonal
         source: o


Polygon tool always draws a closed shape

--- app/classifier/drawing-tools/polygon.cjsx
+++ app/classifier/drawing-tools/polygon.cjsx
@@ -17,7 +17,7 @@

     defaultValues: ({x, y}) ->
       points: []
-      closed: null
+      closed: false

     initStart: ({x, y}, mark) ->
       mark.points.push {x, y}
@@ -28,7 +28,7 @@
       points: mark.points

     isComplete: (mark) ->
-      mark.closed?
+      mark.closed

   render: ->
     averageScale = (@props.scale.horizontal + @props.scale.vertical) / 2
@@ -52,33 +52,29 @@

     <DrawingToolRoot tool={this}>
       <Draggable onDrag={@handleMainDrag} disabled={@props.disabled}>
-        <g>
-          {if @props.mark.closed is false
-            <polyline points={points} fill="none" strokeWidth={GRAB_STROKE_WIDTH / averageScale} strokeOpacity="0" />}
-          <polyline points={points} fill={'none' unless @props.mark.closed} />
-        </g>
+        <polyline points={points} fill={'none' unless @props.mark.closed} />
       </Draggable>

       {if @props.selected
         <g>
           <DeleteButton tool={this} x={deleteButtonPosition.x} y={deleteButtonPosition.y} />

+          {if not @props.mark.closed and  @props.mark.points.length > 2
+            <line className="guideline" x1={lastPoint.x} y1={lastPoint.y} x2={firstPoint.x} y2={firstPoint.y} />}
+
           {for point, i in @props.mark.points
-            point._key ?= Math.random()
             <DragHandle key={i} x={point.x} y={point.y} scale={@props.scale} onDrag={@handleHandleDrag.bind this, i} />}

-          {unless @props.mark.closed?
+          {unless @props.mark.closed
             <g>
-              {if @props.mark.points.length > 2
-                <line className="guideline" x1={lastPoint.x} y1={lastPoint.y} x2={firstPoint.x} y2={firstPoint.y} />}
-              <circle className="clickable" r={finisherRadius} cx={firstPoint.x} cy={firstPoint.y} onClick={@handleFinishClick.bind this, true} />
-              <circle className="clickable" r={finisherRadius} cx={lastPoint.x} cy={lastPoint.y} onClick={@handleFinishClick.bind this, false} />
+              <circle className="clickable" r={finisherRadius} cx={firstPoint.x} cy={firstPoint.y} stroke="transparent" onClick={@handleFinishClick} />
+              <circle className="clickable" r={finisherRadius} cx={lastPoint.x} cy={lastPoint.y} onClick={@handleFinishClick} />
             </g>}
         </g>}
     </DrawingToolRoot>

-  handleFinishClick: (closed) ->
-    @props.mark.closed = closed
+  handleFinishClick: ->
+    @props.mark.closed = true
     @props.onChange()

   handleMainDrag: (e, d) ->


make pseudonym unique_id validation more generic

Closes: CORE-871

for systems that use non-email login IDs, it doesn’t make sense to
Say the “Email” is already in use.

Test plan:
* verify that the error message that appears when you try to create
  A user with a duplicate login id works for both schools that have
  Non-email logins (Like CAS) as well as email-based ones.

Change-Id: I1ccc1151e8798f963daecb7a5764e89e882f730b
Reviewed-on: https://gerrit.instructure.com/138103
Reviewed-by: Rob Orton <7e09c9d3e96378bf549fc283fd6e1e5b7014cc33@instructure.com>
Tested-by: Jenkins
QA-Review: Jeremy Putnam <b030d6d6f199f844ec424882be473c2ab9adc93b@instructure.com>
Product-Review: Ryan Shaw <ea3cd978650417470535f3a4725b6b5042a6ab59@instructure.com>

--- app/coffeescripts/models/Pseudonym.coffee
+++ app/coffeescripts/models/Pseudonym.coffee
@@ -27,7 +27,7 @@
         too_short:    I18n.t("errors.required", "Required")
         too_long:     I18n.t("errors.too_long", "Can't exceed %{max} characters", {max: 100})
         invalid:      I18n.t("errors.invalid", "May only contain letters, numbers, or the following: %{characters}", {characters: ". + - _ @ ="})
-        taken:        I18n.t("errors.taken", "Email already in use")
+        taken:        I18n.t("errors.taken", "Already in use")
         bad_credentials: I18n.t("errors.bad_credentials", "Invalid username or password")
         not_email:    I18n.t("errors.not_email", "Not a valid email address")
       sis_user_id:


removing console.log

removing console.log

--- src/coffee/controllers/player/player-overview-ctrl.coffee
+++ src/coffee/controllers/player/player-overview-ctrl.coffee
@@ -57,7 +57,6 @@
       base

     $scope.changeTitle = ->
-      console.log 'called'
       API.title.set newTitle: $scope.player.title

     $scope.boughtPets = ->


member activity_scores: cap the scale y-axis at the max MP score

--- static/js/activity_scores.coffee
+++ static/js/activity_scores.coffee
@@ -16,6 +16,7 @@
             return @

         @act_histogram = []
+        max_score = @avg_bin_score + 20

         data_idx = 0
         while data_idx < score_list.length
@@ -37,6 +38,8 @@
             column = [ time, score ]
             @act_histogram.push column

+            max_score = Math.max max_score, score
+
             data_idx += 1

         colors = ["#00c0c0"]
@@ -57,7 +60,7 @@

             yaxis:
                 show: false
-                max: @avg_bin_score * 2
+                max: max_score

             grid:
                 markings: [


Fixed property mapper in model

Fixed property mapper in model

--- development/server/models/User.coffee
+++ development/server/models/User.coffee
@@ -87,27 +87,13 @@
 * Running mongoose middlewars
 * Only updating existing paths, ignoring other stuff
 * Going deep into nested objects
-
-@WARNING: it only works up to one nesting level, struggling to generalise that for more
-
-@TODO This should be generalised and moved to separate module to be reused with other models
 ###
 userSchema.methods.updateDocument = (data, done) ->
   for k,v of userSchema.paths
-    if utils.getAttrByString(data, String(k))?
-
-      # # this doesn't work :(
-      # utils.getAttrByString(@, String(k)) = utils.getAttrByString(data, String(k))
-
-      # ashamed of this solution :(
-      attrs = String(k).split "."
-      if attrs.length == 2
-        @[attrs[0]][attrs[1]] = data[attrs[0]][attrs[1]]
-      else if attrs.length == 1
-        @[attrs[0]] = data[attrs[0]]
-      else
-        done("Can not nest schemas deeper then one level User model.", @)
-
+    # Don't assing mongo internal properties (start with '_') and assing only
+    # properties that are in the original model
+    if String(k).charAt(0) != '_' and utils.getAttrByString(data, String(k))?
+      eval("this.#{k} = data.#{k};")
   @save (err, user) =>
     return done(err, user) if err
     done(null, @)


fix(invariant): Remove event listener with "capture: true"

Fixes https://sentry.nylas.com/sentry/edgehill/group/1831/

--- internal_packages/thread-list/lib/thread-list.cjsx
+++ internal_packages/thread-list/lib/thread-list.cjsx
@@ -165,7 +165,7 @@
     @_onResize()

   componentWillUnmount: =>
-    window.removeEventListener('resize', @_onResize)
+    window.removeEventListener('resize', @_onResize, true)

   render: =>
     if @state.style is 'wide'


Add test that we can add links to an existing [up-nav] and it gets .up-current

--- spec_app/spec/javascripts/up/feedback_spec.js.coffee
+++ spec_app/spec/javascripts/up/feedback_spec.js.coffee
@@ -262,6 +262,20 @@
               expect($popupLink).not.toHaveClass('up-current')
               expect($unrelatedLink).not.toHaveClass('up-current')

+          it "respects links that are added to an existing [up-nav] by a fragment update", asyncSpec (next) ->
+            $nav = affix('.nav[up-nav]')
+            $link = $nav.affix('a[href="/foo"][up-target=".main"]')
+            $more = $nav.affix('.more')
+            up.hello($nav)
+
+            up.extract '.more', '

', history: '/bar'
+
+            next =>
+              $moreLink = $('.more').find('a')
+              expect($moreLink).toExist()
+              expect($moreLink).toHaveClass('up-current')
+
+
     describe '.up-active', ->

       describeCapability 'canPushState', ->


updated version number to 0.1.0-dev

updated version number to 0.1.0-dev

--- lib/elog.coffee
+++ lib/elog.coffee
@@ -9,7 +9,7 @@
   LOG_INFO: 1,
   LOG_DEBUG: 0,

-  VERSION: "0.0.2",
+  VERSION: "0.1.0-dev",

   db: require('./db.coffee').db,
   client: require('./client.coffee').client,


Fill in reply method, even though it appears very underutilized

--- src/quip.coffee
+++ src/quip.coffee
@@ -45,7 +45,9 @@
       return url.toLowerCase().indexOf(ext, url.length - ext.length) > -1

   reply: (envelope, strings...) ->
-    @robot.logger.info "Reply"
+    @robot.logger.info "Sending reply"
+    for msg in strings
+      @send envelope, "#{envelope.user.name}: #{msg}"

   run: ->
     options =


Previous commit comment(36a46f..) should have read: Fixed horrible regexp sins and added parsing(not drawing) of double-headed arrows

--- dramagrama.coffee
+++ dramagrama.coffee
@@ -67,7 +67,6 @@
 			newBlock = @drawer.drawRectangle(null, lineBlock, numChildren)
 			blocksThatIHaveDrawn[parentName] = newBlock;
 			return newBlock
-

 class Parser
 	constructor: ->


Keep icons position same when they destroyed and re-created

--- client/app/Applications/StartTab.kdapplication/AppView.coffee
+++ client/app/Applications/StartTab.kdapplication/AppView.coffee
@@ -204,10 +204,16 @@

   createAppIcon:(app, appData, bulk=no)->

-    @appIcons[app]?.destroy()
-    @appItemContainer.addSubView @appIcons[app] = new StartTabAppThumbView
-      delegate : @
+    oldIcon = @appIcons[app]
+    @appItemContainer.addSubView newIcon = new StartTabAppThumbView
+      delegate : this
     , appData
+
+    if oldIcon
+      newIcon.$().insertAfter oldIcon.$()
+      oldIcon.destroy()
+
+    @appIcons[app] = newIcon

     # To make sure its always the last icon
     @createGetMoreAppsButton()  unless bulk


finish change mysql passwd

finish change mysql passwd

--- core/static/script/panel/panel.coffee
+++ core/static/script/panel/panel.coffee
@@ -29,7 +29,7 @@
         }
         .success ->
           ErrorHandle.flushInfo 'success', '修改成功', ->
-            location.reload t_resources
+            location.reload()

   fpm = $ '#php-fpm'
   fpm.on 'click', (e) ->
@@ -42,10 +42,21 @@
   $ '#nginxSave'
     .on 'click', (e) ->
       e.preventDefault()
-      $.post '/plugin/nginx/update_site/', {
+      $.post '/plugin/nginx/update_site/', JSON.stringify {
         action: 'create'
         type: $('#nginxConfigType').find('.active a').attr('href').substr 1
         config: JSON.parse $('#nginxModal').find('textarea').val()
       }
       .success ->
         location.reload()
+
+  #mysql插件
+  mysql = $ '#mysql-input'
+  mysql.find 'button'
+    .on 'click', (e) ->
+      e.preventDefault()
+      $.post '/plugin/mysql/update_passwd/', JSON.stringify {
+        passwd: (mysql.find 'input').val()
+      }
+      .success ->
+        location.reload()


Refactor update code, introduce bugs, break shit

--- source/coffee/index.coffee
+++ source/coffee/index.coffee
@@ -33,12 +33,16 @@
     @parseNodes = (data, pin, keyed={}) ->
       root = {}

+      console.log "Keyed: ", keyed
+
       # Index node list by ID, merging/updating if keyed was provided
       for node in data.nodes
+        console.log "Node: ", node
         keyed[node.id] = node

       # Step through IDs
       for id of keyed
+        console.log "Id: ", id
         node = keyed[id]
         pid = node.facts?.parent_id
         if pid? # Has parent ID?
@@ -47,10 +51,12 @@
             pnode.children ?= [] # Initialize if first child
             pnode.children.push node # Add to parent's children
           else # We're an orphan (broken data or from previous merge)
+            console.log "Deleting orphan: ", keyed[id], node
             delete keyed[id] # No mercy for orphans!
         else if node.name is "workspace" # Mebbe root node?
           root = node # Point at it
         else # Invalid root node!
+          console.log "Deleting invalid root: ", id, node
           delete keyed[id] # Pew Pew!

       pin keyed if pin? # Update pin with keyed
@@ -72,24 +78,46 @@
         status: "unknown"
       , {}, ko.mapping.fromJS @node, mapping

-    @updateNodes = (url, keys, pin) =>
+    @updateNodes = (data, pin, keys) =>
+      @mapData @parseNodes(data, null, keys), pin, mapping
+
+    @getNodes = (url, pin, keys) =>
       @getData url, (data) =>
-        @mapData @parseNodes(data, keys), pin, mapping
+        @updateNodes data, pin, keys

     @wsTemp = ko.observableArray()
-    @wsItems = ko.computed =>
-      @wsTemp()?[0]?.children ? []
+    @wsItems = ko.computed(=>
+      @wsTemp()?[0]?.children ? []).extend throttle: 200 # Coalesce node changes

     @wsPlans = ko.observableArray()
     @wsKeys = ko.observable()

     @poll = (url, cb, timeout=@config?.timeout?.long) =>
+      rePoll = (data) =>
+        if data?
+          @sKey = data.transaction.session_key
+          @txID = data.transaction.txid
+          console.log "Updating transaction: ", @sKey, @txID
+        # Restart long-poller for new transaction
+        setTimeout (=> @poll "/roush/nodes/updates/#{@sKey}/#{@txID}?poll", cb, timeout), 1
+
       $.ajax
         url: url
-        success: (data) ->
+        success: (data) =>
           cb data
+          rePoll data
+        error: (jqXHR, textStatus, errorThrown) =>
+          switch jqXHR.status
+            when 410 # Gone, cycle txID
+              @getData "/roush/updates", (data) =>
+                rePoll data
+            when 0 # Timeout
+              console.log "Retrying"
+              rePoll()
+            else # Other errors
+              console.log "Error (#{jqXHR.status}): #{errorThrown}"
+              setTimeout (=> @poll url, cb, timeout), 1000 # Throttle retry
         dataType: "json"
-        complete: (=> @poll url, cb, timeout)
         timeout: timeout ? 30000

     # Get config and grab initial set of nodes
@@ -104,10 +132,10 @@
         # Start long-poller
         @poll "/roush/nodes/updates/#{@sKey}/#{@txID}?poll", (data) =>
           console.log "Got update: ", data
-          @updateNodes "/roush/nodes/", @wsKeys, @wsTemp
+          setTimeout @updateNodes(data, @wsKeys, @wsTemp), 1000

         # Load initial data, and poll every config.interval ms
-        @updateNodes "/roush/nodes/", @wsKeys, @wsTemp
+        @getNodes "/roush/nodes/", @wsKeys, @wsTemp

     @siteActive = ntrapy.selector (data) =>
       null # TODO:


send back proper text/javascript content-type for application.js.coffee

--- nodeko.coffee
+++ nodeko.coffee
@@ -38,12 +38,12 @@
             for k, v of options
               cookie += "; #{k}=#{v}"
             res.header('Set-Cookie', cookie)
-          render: (file, opts) ->
+          render: (file, opts, fn) ->
             opts ||= {}
             opts.locals ||= {}
             opts.locals.view = file.replace(/\..*$/,'').replace(/\//,'-')
             opts.locals.ctx = ctx
-            res.render file, opts
+            res.render file, opts, fn
           currentPerson: person
           setCurrentPerson: (person, options) ->
             @cookie 'authKey', person?.authKey(), options
@@ -104,7 +104,8 @@

 get '/*.js', ->
   try
-    @render "#{@req.params[0]}.js.coffee", { layout: false }
+    @render "#{@req.params[0]}.js.coffee", { layout: false }, (error, view) =>
+      @res.send view, { 'Content-Type': 'text/javascript' }
   catch e
     @next()


S48784: AllowedValues using CollectionStore: CardBoard.js

--- iterationtrackingboard/IterationTrackingBoardAppSpec.coffee
+++ iterationtrackingboard/IterationTrackingBoardAppSpec.coffee
@@ -45,6 +45,7 @@
     @ajax.whenQuerying('defect').respondWith()
     @ajax.whenQuerying('defectsuite').respondWith()
     @ajax.whenQuerying('testset').respondWith()
+    @ajax.whenQueryingAllowedValues('userstory', 'ScheduleState').respondWith(["Defined", "In-Progress", "Completed", "Accepted"]);

     @tooltipHelper = new Helpers.TooltipHelper this


Resolve the timeout error.

Resolve the timeout error.

Error: timeout of 2000ms exceeded.

--- tests/youtube-info.coffee
+++ tests/youtube-info.coffee
@@ -11,40 +11,44 @@

   context 'youtube.com url', ->
     beforeEach (done) ->
-      room.user.say 'alice', 'https://www.youtube.com/watch?v=ePoi0_zSnYk'
-      setTimeout ->
+      this.timeout 4000
+      speak = () ->
+        room.user.say 'alice', 'https://www.youtube.com/watch?v=ePoi0_zSnYk'
         done()
-      , 10000
+      setTimeout speak, 3000

     it 'should be able to find title', ->
       expect(room.messages[1][1]).to.contain "Adele's Hello by the Movies"

   context 'youtube.com url with additional values', ->
     beforeEach (done)->
-      room.user.say 'alice', 'https://www.youtube.com/watch?v=iq9DLJfpHd0&feature=youtu.be&ab_channel=CatRe-Tailer'
-      setTimeout ->
+      this.timeout 4000
+      speak = () ->
+        room.user.say 'alice', 'https://www.youtube.com/watch?v=iq9DLJfpHd0&feature=youtu.be&ab_channel=CatRe-Tailer'
         done()
-      , 2000
+      setTimeout speak, 2000

     it 'should be able to find title', ->
       expect(room.messages[1][1]).to.contain "Shia Surprise"

   context 'youtu.be url', ->
     beforeEach (done) ->
-      room.user.say 'alice', 'https://youtu.be/HP66dH1yEZo'
-      setTimeout ->
+      this.timeout 4000
+      speak = () ->
+        room.user.say 'alice', 'https://youtu.be/HP66dH1yEZo'
         done()
-      , 2000
+      setTimeout speak, 2000

     it 'should be able to find title', ->
       expect(room.messages[1][1]).to.contain "R2D2 learns a new trick"

   context 'youtu.be url with additional values', ->
     beforeEach (done) ->
-      room.user.say 'alice', 'https://youtu.be/oVnAVcbMoSM?t=39s'
-      setTimeout ->
+      this.timeout 4000
+      speak = () ->
+        room.user.say 'alice', 'https://youtu.be/oVnAVcbMoSM?t=39s'
         done()
-      , 2000
+      setTimeout speak, 2000

     it 'should be able to find title', ->
       expect(room.messages[1][1]).to.contain "GoPro fall at Garden of the Gods"


add better check for response.coffee xhr

--- lib/assets/javascripts/transponder/response.coffee
+++ lib/assets/javascripts/transponder/response.coffee
@@ -3,7 +3,7 @@

   constructor: ->
     $(document).ajaxComplete (event, xhr, status) =>
-      if xhr.getResponseHeader('Content-Type') is 'application/transmission'
+      if xhr and (xhr.getResponseHeader('Content-Type') is 'application/transmission')
         @payload.raw = JSON.parse(@cleanContent(xhr.responseText))
         element    = @payload.raw[0]
         event_name = @payload.raw[1]


simplify graph reorder logic and switch out order of removal / insertion

--- lib/assets/javascripts/turbograft/turbohead.coffee
+++ lib/assets/javascripts/turbograft/turbohead.coffee
@@ -157,11 +157,11 @@
             markReorderAsFinished(linkToMove, currentLink, linksToPassBy)

             if linksToPassBy.length == 0
+              linkClone = linkToMove.cloneNode()
+              document.head.insertBefore(linkClone, currentLink)
               removeLink(linkToMove, startIndex)
-
-              document.head.insertBefore(linkToMove, activeLinksCopy[i])
-              activeLinksCopy.splice(i, 0, linkToMove)
-              triggerEvent('page:after-link-inserted', linkToMove)
+              activeLinksCopy.splice(i, 0, linkClone)
+              triggerEvent('page:after-link-inserted', linkClone)
               return
           else
             addNewReorder(linkToMove, currentLink, pendingReorders)
@@ -172,15 +172,10 @@
             markReorderAsFinished(linkToMove, currentLink, linksToPassBy)

             if linksToPassBy.length == 0
+              linkClone = linkToMove.cloneNode()
+              document.head.insertBefore(linkClone, currentLink.nextSibling)
               removeLink(linkToMove, startIndex)
-
-              targetIndex = i - 1
-              if targetIndex == activeLinksCopy.length - 1
-                document.head.appendChild(linkToMove)
-                activeLinksCopy.push(linkToMove)
-              else
-                document.head.insertBefore(linkToMove, activeLinksCopy[targetIndex + 1])
-                activeLinksCopy.splice(targetIndex + 1, 0, linkToMove)
+              activeLinksCopy.splice(i, 0, linkToMove)
               triggerEvent('page:after-link-inserted', linkToMove)
               return
           else


remove old titles view

remove old titles view

--- client/code/view/titles.coffee
+++ client/code/view/titles.coffee
@@ -1,128 +0,0 @@
-class Cu.View.Title extends Backbone.View
-  tagName: 'h1'
-
-  initialize: ->
-    @setDocumentTitle()
-
-  render: ->
-    tpl = """#{@options.text}"""
-    @$el.html whitelabel(tpl)
-    @
-
-  setDocumentTitle: (model) =>
-    if model?
-      t = "#{model.get 'displayName'} | "
-    else if @options.text
-      t = "#{@options.text} | "
-    else
-      t = ''
-    window.document.title = """#{t}ScraperWiki"""
-
-class Cu.View.EditableTitle extends Cu.View.Title
-  initialize: ->
-    @model.on 'change', @setDocumentTitle, @
-    @setDocumentTitle(@model)
-    # set this so we can override it in Cu.View.ViewTitle
-    # (where the model to save is in fact the parent dataset's model)
-    @modelToSave = @model
-
-  nameClicked: (e) ->
-    e.preventDefault()
-    $a = @$el.find('.editable')
-    @$el.find('input')
-      .val(@model.get 'displayName')
-      .css('width', $a.width() + 20)
-      .show 0, ->
-        @focus()
-    $a.hide()
-
-  editableNameBlurred: ->
-    $label = @$el.find('.editable')
-    $input = @$el.find('input')
-    @newName = $.trim($input.val())
-    @oldName = $label.text()
-    if @newName == '' or @newName == $label.text()
-      $label.show().next().hide()
-    else
-      $input.hide()
-      $label.text(@newName).show()
-      @model.set 'displayName', @newName
-      @modelToSave.save {},
-        success: =>
-          $label.addClass 'saved'
-          setTimeout ->
-            $label.removeClass 'saved'
-          , 1000
-        error: (e) =>
-          $label.text @oldName
-          @model.set 'displayName', @oldName
-          console.warn 'error saving new name', e
-
-  editableNameEscaped: (e) ->
-    e.preventDefault()
-    @$el.find('.editable').show().next().hide()
-
-  keypressOnEditableName: (event) ->
-    @editableNameBlurred() if event.keyCode is 13
-    @editableNameEscaped() if event.keyCode is 27
-
-
-class Cu.View.DatasetSettingsTitle extends Cu.View.Title
-  initialize: ->
-    @model.on 'change', @render, this
-
-  render: ->
-    tpl = """
-      My Datasets
-      /
-      #{@model.get 'displayName'}
-      /
-      Settings
-    """
-    @$el.html whitelabel(tpl)
-    @
-
-
-class Cu.View.DatasetTitle extends Cu.View.EditableTitle
-  #TODO: create BaseView to extend events, for the DRY
-  events:
-    'click .editable': 'nameClicked'
-    'blur input': 'editableNameBlurred'
-    'keypress input': 'keypressOnEditableName'
-
-  render: ->
-    tpl = """
-      My Datasets
-      /
-      #{@model.get 'displayName'}
-      <input type="text" id="txtName" style="display: none"/>
-    """
-    @$el.html whitelabel(tpl)
-    @
-
-class Cu.View.ViewTitle extends Cu.View.EditableTitle
-  events:
-    'click .editable': 'nameClicked'
-    'blur input': 'editableNameBlurred'
-    'keypress input': 'keypressOnEditableName'
-
-  initialize: ->
-    super()
-    @modelToSave = @model.get 'plugsInTo'
-
-  render: ->
-    tpl = """
-      My Datasets
-      /
-      #{@modelToSave.get 'displayName'}
-      /
-      #{@model.get 'displayName'}
-      <input type="text" id="txtName" style="display: none"/>
-    """
-    @$el.html whitelabel(tpl)
-    @
-
-whitelabel = (tpl) ->
-   if window.user.effective?.logoUrl?
-     tpl = """ #{tpl}"""
-   tpl


Split track initial turn on function body

--- client/app/lib/util/trackInitialTurnOn.coffee
+++ client/app/lib/util/trackInitialTurnOn.coffee
@@ -14,6 +14,10 @@

   initialTurnOn = yes
   analytics.identify nick(), { initialTurnOn }
+  execute machine
+
+
+execute = (machine) ->

   track Tracker.BUTTON_CLICKED


Remove session from cookie set option

Remove session from cookie set option

--- app/assets/javascripts/mixingpanel/cookies.js.coffee
+++ app/assets/javascripts/mixingpanel/cookies.js.coffee
@@ -19,7 +19,6 @@
     domain   = if opts.domain? then "; domain=#{opts.domain}" else ""
     path     = if opts.path? then "; path=#{opts.path}" else ""
     expires  = if opts.expires_at? then "; expires=#{opts.expires_at.toGMTString()}" else ""
-    session  = if opts.session? then "; session=#{opts.session}" else ""

     document.cookie = (keyValue + expires + path + domain + session)
     @[key] = value


Unfocus collaboration spec

Unfocus collaboration spec

--- src/packages/collaboration/spec/collaboration-spec.coffee
+++ src/packages/collaboration/spec/collaboration-spec.coffee
@@ -8,7 +8,7 @@
 ServerHost = 'localhost'
 ServerPort = 8081

-fdescribe "Collaboration", ->
+describe "Collaboration", ->
   describe "when a host and a guest join a channel", ->
     [server, token, userDataByToken] = []
     [leaderSession, leaderStartedHandler, leaderParticipantEnteredHandler, leaderParticipantExitedHandler] = []


need a hand with these specs

need a hand with these specs

--- src/packages/command-panel/spec/command-panel-spec.coffee
+++ src/packages/command-panel/spec/command-panel-spec.coffee
@@ -128,6 +128,9 @@
           beforeEach ->
             expect(commandPanel.previewList).toBeVisible()

+          it  "shows the expand and collapse all buttons", ->
+            expect(commandPanel.find('.expand-collapse')).toBeVisible()
+
           describe "when the preview list is focused", ->
             it "hides the command panel", ->
               expect(commandPanel.previewList).toMatchSelector(':focus')
@@ -465,3 +468,12 @@
         expect(editSession.buffer.getPath()).toBe project.resolve(operation.getPath())
         expect(editSession.getSelectedBufferRange()).toEqual operation.getBufferRange()
         expect(previewList.focus).toHaveBeenCalled()
+
+     describe "when a path in the preview list is clicked", ->
+       it "shows and hides the matches for that path", ->
+         previewList.find('li.path:first-child').mousedown()
+         expect(previewList.find('li.path:first-child ul.matches')).toBeHidden()
+
+         previewList.find('li.path:first-child').mousedown()
+         expect(previewList.find('li.path:first-child ul.matches')).toBeVisible()
+


Fix the link to the userAgent setting in the loadFileGroup func

--- app/assets/javascripts/uploadcare/files/group.coffee
+++ app/assets/javascripts/uploadcare/files/group.coffee
@@ -174,7 +174,7 @@
           pub_key: settings.publicKey
           group_id: id[0]
         },
-        headers: {'X-UC-User-Agent': @settings._userAgent}
+        headers: {'X-UC-User-Agent': settings._userAgent}
       )
         .fail (reason) =>
           if settings.debugUploads


notify about errors

notify about errors

--- app/assets/javascripts/uploadcare/uploader.js.coffee
+++ app/assets/javascripts/uploadcare/uploader.js.coffee
@@ -16,8 +16,11 @@

         uploadDef = $.Deferred ->

-          $(file).on 'uploadcare.api.uploader.load', (e) ->
-            uploadDef.resolve(@fileId)
+          $(file)
+            .on 'uploadcare.api.uploader.load', (e) ->
+              uploadDef.resolve(@fileId)
+            .on 'uploadcare.api.uploader.error', (e) ->
+              uploadDef.reject()

           file.upload(@settings)


Don't do anything on empty hash

Don't do anything on empty hash

--- resources/assets/coffee/turbolinks-overrides.coffee
+++ resources/assets/coffee/turbolinks-overrides.coffee
@@ -21,6 +21,9 @@
 $(document).on 'click', 'a[href^="#"]', (e) ->
   href = e.currentTarget.href
   targetId = decodeURIComponent href[href.indexOf('#') + 1..]
+
+  return if targetId == ''
+
   target = document.getElementById targetId

   return if !target?


Fixing custom field edition

Fixing custom field edition

--- app/coffee/modules/common/custom-field-values.coffee
+++ app/coffee/modules/common/custom-field-values.coffee
@@ -206,8 +206,8 @@
                 $ctrl.updateAttributeValue(attributeValue).then ->
                     render(attributeValue, false)

-        setFocusOnInputField = ->
-            $el.find("input[name='value'], textarea[name='value']").focus()
+        setFocusAndSelectOnInputField = ->
+            $el.find("input[name='value'], textarea[name='value']").focus().select()

         # Bootstrap
         attributeValue = $scope.$eval($attrs.tgCustomAttributeValue)
@@ -218,12 +218,12 @@
             return if not isEditable()
             return if $selectedText.get().length
             render(attributeValue, true)
-            setFocusOnInputField()
+            setFocusAndSelectOnInputField()

         $el.on "click", "a.icon-edit", (event) ->
             event.preventDefault()
             render(attributeValue, true)
-            setFocusOnInputField()
+            setFocusAndSelectOnInputField()

         ## Actions (on edit mode)
         $el.on "keyup", "input[name=value], textarea[name='value']", (event) ->


removing pagination limits

removing pagination limits

--- review_front/app/controllers/project/commits.coffee
+++ review_front/app/controllers/project/commits.coffee
@@ -25,10 +25,8 @@
     toggleAccepted: ->
       @set 'hideAccepted', !(@get('hideAccepted'))

-  queryParams: ['page', 'pageNumber', 'perPage']
+  queryParams: ['page']
   pageBinding: 'content.page'
-  perPageBinding: 'content.perPage'
   page: 1
-  perPage: 25

 `export default ProjectCommitsController`


test: see an area with tracking code

test: see an area with tracking code

--- client/test/lib/dashboard/utilities.coffee
+++ client/test/lib/dashboard/utilities.coffee
@@ -50,3 +50,6 @@
             .waitForElementVisible toggleButtonSelector, 10000
             .click toggleButtonSelector

+      .pause 5000
+      .waitForElementVisible codeBlockSelector, 20000
+      .assert.valueContains textarea, user.teamSlug


Teams: Fixed class selector for teams test.

--- client/test/lib/teams/teamschannels.coffee
+++ client/test/lib/teams/teamschannels.coffee
@@ -47,23 +47,23 @@
       .end()

-  # leaveChannel: (browser) ->
+  leaveChannel: (browser) ->

-  #   user                    = teamsHelpers.loginTeam(browser)
-  #   channelName1            = teamsHelpers.createChannel(browser, user)
-  #   sidebarSectionsSelector = '.activity-sidebar .SidebarSections'
+    user                    = teamsHelpers.loginTeam(browser)
+    channelName1            = teamsHelpers.createChannel(browser, user)
+    sidebarSectionsSelector = '.activity-sidebar .SidebarChannelsSection'

-  #   teamsHelpers.leaveChannel(browser)
-  #   channelName2                  = teamsHelpers.createChannel(browser, user)
-  #   channelLinkOnSidebarSelector2 = "#{sidebarSectionsSelector} a[href='/Channels/#{channelName2}']"
+    teamsHelpers.leaveChannel(browser)
+    channelName2                  = teamsHelpers.createChannel(browser, user)
+    channelLinkOnSidebarSelector2 = "#{sidebarSectionsSelector} a[href='/Channels/#{channelName2}']"

-  #   browser
-  #     .waitForElementVisible  sidebarSectionsSelector, 20000
-  #     .expect.element(sidebarSectionsSelector).text.to.not.contain(channelName1)
+    browser
+      .waitForElementVisible  sidebarSectionsSelector, 20000
+      .expect.element(sidebarSectionsSelector).text.to.not.contain(channelName1)

-  #   browser
-  #     .assert.containsText    channelLinkOnSidebarSelector2, channelName2
-  #     .end()
+    browser
+      .assert.containsText    channelLinkOnSidebarSelector2, channelName2
+      .end()

   joinChannel: (browser) ->


fix indentation broken

fix indentation broken

--- client/views/app/messagePopupConfig.coffee
+++ client/views/app/messagePopupConfig.coffee
@@ -120,17 +120,17 @@
 					# show common used emojis, when use input a single ':'
 					if filter == ''
 						commonEmojis = [
-				            ':laughing:',
-				            ':smiley:',
-				            ':sunglasses:',
-				            ':wink:',
-				            ':innocent:',
-				            ':flushed:',
-				            ':disappointed:',
-				            ':cry:',
-				            ':heart:',
-				            ':broken_heart:',
-				        ]
+					            ':laughing:',
+					            ':smiley:',
+					            ':sunglasses:',
+					            ':wink:',
+					            ':innocent:',
+					            ':flushed:',
+					            ':disappointed:',
+					            ':cry:',
+					            ':heart:',
+					            ':broken_heart:'
+					        ]
 						for shortname in commonEmojis
 							results.push
 								_id: shortname


Fix error on statistics collector when room usernames is undefined

--- packages/rocketchat-statistics/server/functions/get.coffee
+++ packages/rocketchat-statistics/server/functions/get.coffee
@@ -28,15 +28,15 @@
 	# @TODO need to count above differently since the usernames fields will not exist
 	m = ->
 		emit 1,
-			sum: this.usernames.length or 0
-			min: this.usernames.length or 0
-			max: this.usernames.length or 0
+			sum: this.usernames?.length or 0
+			min: this.usernames?.length or 0
+			max: this.usernames?.length or 0
 			count: 1

 		emit this.t,
-			sum: this.usernames.length or 0
-			min: this.usernames.length or 0
-			max: this.usernames.length or 0
+			sum: this.usernames?.length or 0
+			min: this.usernames?.length or 0
+			max: this.usernames?.length or 0
 			count: 1

 	r = (k, v) ->


worksheets -- clean up execute_callbacks on close.

--- salvus/page/syncdoc.coffee
+++ salvus/page/syncdoc.coffee
@@ -366,8 +366,7 @@

     disconnect_from_session: (cb) =>
         @_remove_listeners()
-        if @session_uuid?
-            # no need to re-disconnect (and would cause serious error!)
+        if @session_uuid? # no need to re-disconnect if not connected (and would cause serious error!)
             salvus_client.call
                 timeout : 10
                 message : message.codemirror_disconnect(session_uuid : @session_uuid)
@@ -511,6 +510,7 @@

     disconnect_from_session: (cb) =>
         @_remove_listeners()
+        @_remove_execute_callbacks()
         if @session_uuid?
             # no need to re-disconnect (and would cause serious error!)
             salvus_client.call
@@ -520,7 +520,6 @@

         # store pref in localStorage to not auto-open this file next time
         @editor.local_storage('auto_open', false)
-
         @chat_session?.disconnect_from_session()

     execute_code: (opts) =>
@@ -530,6 +529,10 @@
             preparse : true
             cb       : undefined
         uuid = misc.uuid()
+        if @_execute_callbacks?
+            @_execute_callbacks.push(uuid)
+        else
+            @_execute_callbacks = [uuid]
         salvus_client.send(
             message.codemirror_execute_code
                 id   : uuid
@@ -540,6 +543,12 @@
         )
         if opts.cb?
             salvus_client.execute_callbacks[uuid] = opts.cb
+
+    _remove_execute_callbacks: () =>
+        if @_execute_callbacks?
+            for uuid in @_execute_callbacks
+                delete salvus_client.execute_callbacks[uuid]
+            delete @_execute_callbacks

     introspect_line: (opts) =>
         opts = defaults opts,


make some syncdoc code more robust

make some syncdoc code more robust

--- salvus/page/syncdoc.coffee
+++ salvus/page/syncdoc.coffee
@@ -1837,6 +1837,8 @@
     ##########################################
     get_cell_flagstring: (marker) =>
         pos = marker.find()
+        if not pos?
+            return ''
         return @focused_codemirror().getRange({line:pos.from.line,ch:37},{line:pos.from.line, ch:pos.to.ch-1})

     set_cell_flagstring: (marker, value) =>
@@ -1845,6 +1847,8 @@

     get_cell_uuid: (marker) =>
         pos = marker.find()
+        if not pos?
+            return ''
         return @focused_codemirror().getLine(pos.line).slice(1,38)

     set_cell_flag: (marker, flag) =>


added generate another sentence verbiage

--- app/assets/javascripts/home.coffee
+++ app/assets/javascripts/home.coffee
@@ -30,6 +30,7 @@
         ).reveal 250
         $('#success-container').show 0
         $('#word-map-link').show 0
+        $('#twitter_submit').val("Generate Another Sentence")
       return
     return


Rename short_name to shortName

Rename short_name to shortName

--- server/components/indicatorator/test/getters/gdoc.coffee
+++ server/components/indicatorator/test/getters/gdoc.coffee
@@ -22,7 +22,7 @@
 test("GDoc getter takes an Indicator and queries Google Spreadsheets
   with the indicator's spreadsheet id", (done) ->
   indicator = new Indicator(
-    short_name: 'Key stakeholders identified'
+    shortName: 'Key stakeholders identified'
     indicatorationConfig:
       spreadsheetKey: '123'
   )
@@ -88,7 +88,7 @@

 test(".fetch returns all rows of a sub indicator", (done) ->
   indicator = new Indicator(
-    short_name: 'Key stakeholders identified'
+    shortName: 'Key stakeholders identified'
     indicatorationConfig:
       spreadsheetKey: '123'
   )


Update gif.coffee

Update gif.coffee

--- scripts/gif.coffee
+++ scripts/gif.coffee
@@ -21,6 +21,7 @@
         'survive': 'http://media.giphy.com/media/a8yhMwEUiePqU/giphy.gif',
         'hold on to your butts': 'http://24.media.tumblr.com/tumblr_m48kj1M5tG1r7tfy9o2_500.gif'
         'beard slap': 'http://i.imgur.com/6qLjt.gif'
+        'jump put window': 'http://img.pandawhale.com/26643-Jump-Out-Window-gif-AT8K.gif'

     robot.respond /gif (.*)/i, (msg) ->
         name = gifs[msg.match[1]]