Detect if all courses are the same type

Detect if all courses are the same type

--- tutor/src/flux/course-listing.coffee
+++ tutor/src/flux/course-listing.coffee
@@ -113,6 +113,14 @@

       [past, current, future]

+    typeOfAllCourses: ->
+      uniq = _.uniqBy(@_course_ids, (id) ->
+        CourseStore.get(id).is_concept_coach
+      )
+      if uniq.length is 1
+        if CourseStore.isConceptCoach(uniq[0]) then 'cc' else 'tutor'
+
+
     isAnyTeacher: ->
       courses = @exports.allCoursesWithRoles.call(@)
       _.some(courses, (course) ->


Measure iframe insert

Measure iframe insert

--- app/js/jail_iframe/initializers/xdm.coffee
+++ app/js/jail_iframe/initializers/xdm.coffee
@@ -1,11 +1,12 @@
-frame_container = FactlinkJailRoot.$factlinkCoreContainer[0]
+FactlinkJailRoot.createReceiverEnvoy FactlinkJailRoot.annotatedSiteReceiver
+
 iframe = document.createElement('iframe')
-frame_container.appendChild(iframe)
-FactlinkJailRoot.createReceiverEnvoy FactlinkJailRoot.annotatedSiteReceiver
 iframe.id = "factlink-sidebar-frame"
 iframe.src = "#{FactlinkConfig.api}/client/blank"
+FactlinkJailRoot.$factlinkCoreContainer[0].appendChild(iframe)
+
 FactlinkJailRoot.factlinkCoreEnvoy = FactlinkJailRoot.createSenderEnvoy iframe.contentWindow
-
+FactlinkJailRoot.perf.add_timing_event('inserted core iframe')
 FactlinkJailRoot.openFactlinkModal = (id) -> FactlinkJailRoot.factlinkCoreEnvoy 'showFactlink', id

 FactlinkJailRoot.$sidebarFrame = $(iframe)


Update confirm on delete block

Update confirm on delete block

--- BackofficeBundle/Resources/public/coffee/page/areaView.coffee
+++ BackofficeBundle/Resources/public/coffee/page/areaView.coffee
@@ -110,7 +110,15 @@

   confirmRemoveBlock: (event) ->
     if @area.get("blocks").length > 0
-      @removeBlock event  if confirm("Vous êtes sur le point de supprimer un bloc. Souhaitez-vous poursuivre cette action ?")
+      smartConfirm(
+        titleWhite: 'Delete'
+        titleColorized: 'this block'
+        text: 'The removal will be final'
+        yesCallbackParams:
+          blockView: @
+        yesCallback: (params) ->
+          params.blockView.removeBlock(event)
+      )

   removeBlock: (event) ->
     ul = $(event.target).parents("ul").first()


Increase error reporting sample rate

Increase error reporting sample rate

--- app/views/play/level/tome/SpellView.coffee
+++ app/views/play/level/tome/SpellView.coffee
@@ -903,7 +903,8 @@
     hashValue = aether.raw + aetherProblem.message
     return if hashValue of @savedProblems
     @savedProblems[hashValue] = true
-    return unless Math.random() < 0.01  # Let's only save a tiny fraction of these during HoC to reduce writes.
+    sampleRate = Math.max(1, (me.level()-2) * 2) * 0.01 # Reduce number of errors reported on earlier levels
+    return unless Math.random() < sampleRate

     # Save new problem
     @userCodeProblem = new UserCodeProblem()


fix, vim-mode-plus-user:move-selected-text impacted by #744

--- lib/vim-state.coffee
+++ lib/vim-state.coffee
@@ -224,7 +224,7 @@
     # Intentionally using target.closest('atom-text-editor')
     # Don't use target.getModel() which is work for CustomEvent but not work for mouse event.
     return unless @editorElement is event.target?.closest?('atom-text-editor')
-    return if event.type.startsWith('vim-mode-plus:')
+    return if event.type.startsWith('vim-mode-plus') # to match vim-mode-plus: and vim-mode-plus-user:

     if haveSomeNonEmptySelection(@editor)
       @editorElement.component.updateSync()


:triangular_ruler: passing z, x, y to the callback

--- src/TileGrinder.coffee
+++ src/TileGrinder.coffee
@@ -20,9 +20,7 @@
   config:
     debug: true
     output: "mbtiles"
-
     maxZoom: 14
-    copyAboveZoom: null

   queueSize: 64

@@ -38,7 +36,7 @@
     @protobuf = new Protobuf fs.readFileSync __dirname+"/../proto/vector_tile.desc"

   grind: (source, @destination, @callback) ->
-    @_log "[>] starting to grind"
+    @_log "[>] starting to grind #{source} into #{@destination}"

     @_mustExist source
     .then => @_loadMBTiles source
@@ -74,13 +72,8 @@
           stream.pause()
           paused = true

-        promise = if @config.copyAboveZoom and z >= @config.copyAboveZoom
-          @_loadTile z, x, y
-          .then (buffer) => @_storeTile z, x, y, buffer
-        else
-          @_processTile z, x, y
-
-        promise.finally =>
+        @_processTile z, x, y
+        .finally =>
           queueSpots++
           if paused and queueSpots > 0
             stream.resume()
@@ -136,15 +129,15 @@
     @_loadTile z, x, y
     .then (buffer) => @_unzipIfNeeded original = buffer
     .then (buffer) => @_decodeTile buffer
-    .then (tile) => @_callback tile
+    .then (tile) => @_callback tile, z, x, y
     .then (tile) => @_encodeTile tile
     .then (buffer) => @_gzip buffer
     .then (buffer) =>
       @_trackStats z, x, y, original, buffer
       @_storeTile z, x, y, buffer

-  _callback: (tile) ->
-    @callback tile
+  _callback: (tile, z, x, y) ->
+    @callback tile, z, x, y
     tile

   _storeTile: (z, x, y, buffer) ->


Use button tag

Use button tag

--- src/render/make.coffee
+++ src/render/make.coffee
@@ -8,10 +8,10 @@
   _CHILDREN: items
 }

-rows = (label, items) -> group(label, items, 'div')
-columns = (label, items) -> group(label, items, 'span')
+rows = (label, items) -> group label, items, 'div'
+columns = (label, items) -> group label, items, 'span'
 buttons = (kind, items, my_kind, action) ->
-  body = columns "#{kind}s", items
+  body = group "#{kind}s", items, 'button'
   body.stroke = my.color.line
   body.padding = my_kind.padding
   body.fill = my_kind.background


Fix typo

Fix typo

--- server/konnectors/github.coffee
+++ server/konnectors/github.coffee
@@ -80,7 +80,7 @@
                 callback err, notifContent

-# Procedure to login to Free website.
+# Procedure to login to Github website.
 logIn = (requiredFields, billInfos, data, next) ->
     loginUrl = "https://github.com/session"


Turn off detectEncoding during testing for windows

--- test/docpad.coffee
+++ test/docpad.coffee
@@ -1,7 +1,7 @@
 module.exports =
 	reportStatistics: false
 	reportErrors: false
-	detectEncoding: true
+	detectEncoding: require('safeps').isWindows() is false

 	environments:
 		development:


RewindBuffer: Fix how log is defined

RewindBuffer: Fix how log is defined

We can only get log from opts if it isn't defined, since we could be subclassed
and it could already exist.

--- src/streammachine/rewind_buffer.coffee
+++ src/streammachine/rewind_buffer.coffee
@@ -33,7 +33,9 @@
         @_rmax          = null
         @_rburst        = null

-        @log            = rewind_opts.log
+        # This could already be set if we've subclassed RewindBuffer, so
+        # only set it if it doesn't exist
+        @log            = rewind_opts.log if rewind_opts.log && !@log

         # each listener should be an object that defines obj._offset and
         # obj.writeFrame. We implement RewindBuffer.Listener, but other


Refactor

Refactor

--- src/videojs.chromecastComponent.coffee
+++ src/videojs.chromecastComponent.coffee
@@ -97,23 +97,24 @@
   onMediaStatusUpdate: (event) ->
     return unless @apiMedia

-    console.log @player_
+    console.log @apiMedia
     console.log "##############"

     @currentMediaTime = @apiMedia.currentTime

-    if @apiMedia.playerState is "IDLE"
-      @currentMediaTime = 0
-      @trigger "timeupdate"
-      @onStopAppSuccess()
-    else if @apiMedia.playerState is "PAUSED"
-      return if @paused
-      @player_.pause()
-      @paused = true
-    else if @apiMedia.playerState is "PLAYING"
-      return unless @paused
-      @player_.play()
-      @paused = false
+    switch @apiMedia.playerState
+      when "IDLE"
+        @currentMediaTime = 0
+        @trigger "timeupdate"
+        @onStopAppSuccess()
+      when "PAUSED"
+        return if @paused
+        @player_.pause()
+        @paused = true
+      when "PLAYING"
+        return unless @paused
+        @player_.play()
+        @paused = false

   startProgressTimer: (callback) ->
     if @timer


hub/lti: change LTI module import to use try/catch

--- src/smc-hub/hub.coffee
+++ src/smc-hub/hub.coffee
@@ -80,9 +80,9 @@
 # express http server -- serves some static/dynamic endpoints
 hub_http_server = require('./hub_http_server')

-if fs.existsSync(path_module.join(SMC_ROOT, 'smc-hub', 'lti', 'lti.ts'))
+try
     {LTIService} = require('./lti/lti')
-else
+catch
     # silly stub
     class LTIService
         start: =>


Update colors.coffee

Update colors.coffee

Fixing linting issues and easing enhancements.

--- coffee/colors.coffee
+++ coffee/colors.coffee
@@ -1,27 +1,23 @@
-colors = {
-	# Cool Colors
+colors =
+  # Cool Colors
+  aqua: '#7FDBFF'
+  blue: '#0074D9'
+  lime: '#01FF70'
+  navy: '#001F3F'
+  teal: '#39CCCC'
+  olive: '#3D9970'
+  green: '#2ECC40'

-	aqua: "#7FDBFF",
-	blue: "#0074D9",
-	lime: "#01FF70",
-	navy: "#001F3F",
-	teal: "#39CCCC",
-	olive: "#3D9970",
-	green: "#2ECC40",
+  # Warm Colors
+  red: '#FF4136'
+  maroon: '#85144B'
+  orange: '#FF851B'
+  purple: '#B10DC9'
+  yellow: '#FFDC00'
+  fuchsia: '#F012BE'

-	# Warm Colors
-
-	red: "#FF4136",
-	maroon: "#85144B",
-	orange: "#FF851B",
-	purple: "#B10DC9",
-	yellow: "#FFDC00",
-	fuchsia: "#F012BE",
-
-	# Gray Scale Colors
-
-	gray: "#aaa",
-	white: "#fff",
-	black: "#111",
-	silver: "#ddd"
-};+  # Gray Scale Colors
+  gray: '#aaa'
+  white: '#fff'
+  black: '#111'
+  silver: '#ddd'


emplace missing commas in many oEmbed provider definitions

--- scripts/preview/oembed.coffee
+++ scripts/preview/oembed.coffee
@@ -78,33 +78,33 @@
   # List of providers found at <http://www.oembed.com/#section7>.
   ##

-  provider 'YouTube', 'http://www.youtube.com/oembed'
+  provider 'YouTube', 'http://www.youtube.com/oembed',
     scheme: (uri) -> uri.isInDomain('youtube.com') or uri.host is 'youtu.be'

-  provider 'Flickr', 'http://www.flickr.com/services/oembed/'
+  provider 'Flickr', 'http://www.flickr.com/services/oembed/',
     scheme: (uri) -> uri.isInDomain('flickr.com') and uri.globPath('/photos/*')

-  provider 'Viddler', 'http://lab.viddler.com/services/oembed/'
+  provider 'Viddler', 'http://lab.viddler.com/services/oembed/',
     scheme: (uri) -> uri.isInDomain 'viddler.com'

-  provider 'Qik', 'http://qik.com/api/oembed.{format}'
+  provider 'Qik', 'http://qik.com/api/oembed.{format}',
     scheme: (uri) -> uri.isInDomain 'qik.com'

-  provider 'Revision3', 'http://revision2.com/api/oembed/'
+  provider 'Revision3', 'http://revision2.com/api/oembed/',
     scheme: (uri) -> uri.isInDomain 'revision3.com'

-  provider 'Hulu', 'http://www.hulu.com/api/oembed.{format}'
+  provider 'Hulu', 'http://www.hulu.com/api/oembed.{format}',
     scheme: (uri) -> uri.isInDomain('hulu.com') and uri.globPath('/watch/*')

-  provider 'Vimeo', 'http://www.vimeo.com/api/oembed.{format}'
+  provider 'Vimeo', 'http://www.vimeo.com/api/oembed.{format}',
     scheme: (uri) ->
       uri.isInDomain('vimeo.com') and
       uri.path.search(///^ (?: /groups/.+/videos )? / (\d+) $///i) >= 0

-  provider 'SmugMug', 'http://api.smugmug.com/services/oembed/'
+  provider 'SmugMug', 'http://api.smugmug.com/services/oembed/',
     scheme: (uri) -> uri.isInDomain 'smugmug.com'

-  provider 'SlideShare', 'http://www.slideshare.net/api/oembed/2'
+  provider 'SlideShare', 'http://www.slideshare.net/api/oembed/2',
     scheme: (uri) -> uri.isInDomain('slideshare.net') and uri.globPath('/*/*')

   # Additional providers found on my many voyages across this vast Internet.
@@ -113,13 +113,13 @@
   # <http://code.google.com/p/oohembed/source/browse/app/provider/endpoints.json>.
   ##

-  provider 'Blip.tv', 'http://blip.tv/oembed/'
+  provider 'Blip.tv', 'http://blip.tv/oembed/',
     scheme: (uri) -> uri.isInDomain('blip.tv') and uri.globPath('/file/*')

   provider 'Clikthrough', 'http://clikthrough.com/services/oembed',
     scheme: (uri) -> uri.isInDomain('clikthrough.com') and uri.globPath('/theater/video/*')

-  provider 'DailyMotion', 'http://www.dailymotion.com/api/oembed'
+  provider 'DailyMotion', 'http://www.dailymotion.com/api/oembed',
     scheme: (uri) -> uri.isInDomain 'dailymotion.com'

   provider 'DeviantART', 'http://backend.deviantart.com/oembed',
@@ -129,7 +129,7 @@
   provider 'dotSUB.com', 'http://dotsub.com/services/oembed',
     scheme: (uri) -> uri.isInDomain('dotsub.com') and uri.globPath('/view/*')

-  provider 'Funny or Die', 'http://www.funnyordie.com/oembed'
+  provider 'Funny or Die', 'http://www.funnyordie.com/oembed',
     scheme: (uri) -> uri.isInDomain('funnyordie.com') and uri.globPath('/videos/*')

   provider 'Kinomap', 'http://www.kinomap.com/oembed',
@@ -138,7 +138,7 @@
   provider 'National Film Board of Canada', 'http://www.nfb.ca/remote/services/oembed/',
     scheme: (uri) -> uri.isInDomain('nfb.ca') and uri.globPath('/film/*')

-  provider 'PhotoBucket', 'http://photobucket.com/oembed'
+  provider 'PhotoBucket', 'http://photobucket.com/oembed',
     scheme: (uri) ->
       uri.isInDomain('photobucket.com') and
       (uri.globPath('/albums/*') or uri.globPath('/groups/*'))


Extend timeouts for travis

Extend timeouts for travis

--- test/gitter.coffee
+++ test/gitter.coffee
@@ -10,7 +10,7 @@
 server = http.createServer()

 describe "gitter", ->
-  this.timeout 6000
+  this.timeout 30000
   before (done) ->
     server.listen 3000
     webserver.listen 7000, ->


servers: remove odesk

servers: remove odesk

--- servers/lib/server/index.coffee
+++ servers/lib/server/index.coffee
@@ -114,7 +114,6 @@
 app.get  '/-/api/ssotoken/login'                 , require './handlers/api/ssotokenlogin'
 app.get  '/-/api/logs'                           , require './handlers/api/logs'
 app.get  '/-/image/cache'                        , require './image_cache'
-app.get  '/-/oauth/odesk/callback'               , require './odesk_callback'
 app.get  '/-/oauth/github/callback'              , require './github_callback'
 app.get  '/-/oauth/facebook/callback'            , require './facebook_callback'
 app.get  '/-/oauth/google/callback'              , require './google_callback'


Server: added handler global handlers

Server: added handler global handlers

--- servers/lib/server/index.coffee
+++ servers/lib/server/index.coffee
@@ -61,6 +61,7 @@
 app.post '/-/teams/join'                         , require './handlers/jointeam'
 app.post '/-/teams/early-access'                 , require './handlers/earlyaccess'
 app.post '/-/teams/verify-domain'                , require './handlers/verifyslug'
+app.get  '/-/teams/check-team-invitation'        , require './handlers/teaminvitationchecker'

 # fetches last members of team
 app.all  '/-/team/:name/members'                 , require './handlers/getteammembers'


Fix monocles

Fix monocles

--- apps/artwork/client/monocles.coffee
+++ apps/artwork/client/monocles.coffee
@@ -5,7 +5,6 @@

   initialize: (options) ->
     { @artwork } = options
-    @adjLeftFactor = 24
     @monocleSize = parseInt(@$('.monocle-zoom').css('width').replace 'px', '') / 2
     @preCalc()
     @bindMonocles()
@@ -49,7 +48,7 @@
   positionAndRecalc: ->
     @currentLeft = event.pageX
     @currentTop = event.pageY
-    left = @currentLeft - @left + @monocleSize - @adjLeftFactor
+    left = @currentLeft - @left - @monocleSize
     top = @currentTop - @top - @monocleSize
     @$('.monocle-zoom').css
       'left': left


Turn scaleMin and scaleMax pinchable props to minScale and maxScale

--- framer/LayerPinchable.coffee
+++ framer/LayerPinchable.coffee
@@ -23,8 +23,8 @@

 	@define "scale", @simpleProperty("scale", true)
 	@define "scaleIncrements", @simpleProperty("scaleIncrements", 0)
-	@define "scaleMin", @simpleProperty("scaleMin", 0)
-	@define "scaleMax", @simpleProperty("scaleMax", Number.MAX_VALUE)
+	@define "minScale", @simpleProperty("minScale", 0)
+	@define "maxScale", @simpleProperty("maxScale", Number.MAX_VALUE)
 	@define "scaleFactor", @simpleProperty("scaleFactor", 1)

 	@define "rotate", @simpleProperty("rotate", true)
@@ -65,9 +65,9 @@
 		@layer.y -= yDiff

 		# This is not a great fix, we should add this in the draggable. Basically
-		# we need to account in the draggable for a change of origin relative to
+		# we need to account in the draggable for a change of origin relative to
 		# the dragging start offset.
-
+
 		if @layer._draggable
 			@layer.draggable._layerStartPoint.x -= xDiff
 			@layer.draggable._layerStartPoint.y -= yDiff
@@ -94,7 +94,7 @@
 		if @scale
 			@_scaleStart ?= @layer.scale
 			scale = (((event.scale - 1) * @scaleFactor) + 1) * @_scaleStart
-			scale = Utils.clamp(scale, @scaleMin, @scaleMax) if (@scaleMin and @scaleMax)
+			scale = Utils.clamp(scale, @minScale, @maxScale) if (@minScale and @maxScale)
 			scale = Utils.nearestIncrement(scale, @scaleIncrements) if @scaleIncrements
 			@layer.scale = scale
 			@emit(Events.Scale, event)


Fixed xor

Fixed xor

--- src/builtin/set.coffee
+++ src/builtin/set.coffee
@@ -141,8 +141,9 @@
     return difference

   __xor__: (set) ->
-    xor = @__sub__(set)
-    xor = set.__sub__(xor)
+    diff1 = @__sub__(set)
+    diff2 = set.__sub__(xor)
+    xor = diff1.__or__(diff2)
     return xor

   add: (element) ->


box blur effect added

box blur effect added

--- scripts/coffee/lib/Filter.coffee
+++ scripts/coffee/lib/Filter.coffee
@@ -41,7 +41,7 @@

 	addEffect: (effectName, reinitProgram = yes) ->

-		console.log self.effects
+		# console.log self.effects

 		fx = new self.effects[effectName] @, @_effects.length

@@ -69,7 +69,7 @@

 		vert = @_gila.getVertexShader 'filter-shader-vert', shaders.vert

-		console.log frag = @_getFragShader()
+		frag = @_getFragShader()

 		@_program = @_gila.getProgram vert, frag


add test case for %g

add test case for %g


pluggable.createHookPoint

pluggable.createHookPoint

--- core/pluggable.coffee
+++ core/pluggable.coffee
@@ -58,6 +58,17 @@
       # t_category, t_title, language, content_markdown
       pages: []

+exports.createHookPoint = (hook_name) ->
+  keys = hook_name.split '.'
+
+  pointer = exports.hooks
+
+  for item in keys
+    if pointer[item] == undefined
+      pointer[item] = {}
+
+    pointer = pointer[item]
+
 exports.registerHook = (hook_name, plugin, payload) ->
   keys = hook_name.split '.'
   last_key = keys.pop()
@@ -88,8 +99,6 @@

   return _.filter pointer, (hook) ->
     if hook.plugin_info.type == 'extension'
-      return true
-    else if !hook.require_account
       return true
     else if !account
       return false


Patching client side.

Patching client side.

--- client.coffee
+++ client.coffee
@@ -1,6 +1,12 @@
 # We patch client side as well so that they have the same API.
+# See https://github.com/meteor/meteor/pull/4710
 unless Tracker.Computation::flush
   Tracker.Computation::flush = ->
     return if @_recomputing

     @_recompute()
+
+unless Tracker.Computation::run
+  Tracker.Computation::run = ->
+    @invalidate()
+    @flush()


Discussion: Styling fixes and renderBubble

--- client.coffee
+++ client.coffee
@@ -14,14 +14,14 @@
 renderStory = (storyId) ->
 	story = Db.shared.ref 'stories', storyId
 	Event.showStar story.get('title') if !Db.shared.get('single')
-	Dom.div !->
-		Dom.style margin: '-8px', background: '#fff', borderBottom: '2px solid #ccc', padding: '8px'
+	Ui.top !->
+		Dom.style margin: 0
 		Ui.avatar Plugin.userAvatar(story.get('user')), style: float: 'right'
 		Dom.h2 !->
 			Dom.userText story.get('title'), {br:false}
 		Dom.div !->
 			Dom.userText text if text=story.get('text')
-		renderComments story, [storyId]
+	renderComments story, [storyId]

 renderUpvoter = (path, votesObs, size = 50) ->
 	if path not instanceof Array
@@ -59,19 +59,13 @@
 			parent = parent.ref 'c', path[i]
 		Dom.cls 'reply'

-		Page.setTitle tr("Reply to %1", Plugin.userName(parent.get('user')))
-		#Dom.h2 tr("%1 said:",Plugin.userName(parent.get('user')))
-		title = parent.get('title')
-		if title
-			Dom.div !->
-				Dom.style fontWeight: "bold"
-				Dom.userText title, {br:false}
-		Dom.userText parent.get('text')
-
-		Dom.br()
-		Dom.br()
-
-		#Dom.h2 tr "Your reply:"
+		Ui.top !->
+			Ui.avatar Plugin.userAvatar(parent.get('user')), style: float: 'right'
+			Dom.h2 !->
+				Dom.userText parent.get('title'), {br:false}
+			Dom.div !->
+				Dom.userText text if text=parent.get('text')
+
 		Form.text name: 'reply', text: tr("Your reply")

 		Form.setPageSubmit (data) !->
@@ -83,13 +77,12 @@

 renderComments = (base,path,margin=0,hide=false) !->
 	if path.length is 1
-		Dom.span !->
-			Dom.style color: Plugin.colors().highlight, fontSize: '85%', position: 'relative', padding: '3px 5px', left: '-5px'
+		Dom.div !->
+			Dom.style color: Plugin.colors().highlight, fontSize: '85%', padding: '12px', margin: '0', display: 'inline-block'
 			Dom.text tr("Reply")
 			Dom.onTap  !->
 				reply path

-
 	up = Db.personal.ref('up')
 	base.iterate 'c', (comment) ->
 		Dom.div !->
@@ -152,6 +145,7 @@
 	if storyId = (storyId || +Page.state.get(0))
 		return renderStory storyId

+	Page.setCardBackground()
 	Ui.list !->
 		Db.shared.iterate 'stories', (story) !->
 			Ui.item !->
@@ -164,6 +158,7 @@
 					Dom.div !->
 						Dom.style Flex: 1, margin: '5px 0 5px 8px', color: (if Event.isNew(story.get('time')) then '#5b0' else 'inherit')
 						Dom.userText story.get('title'), {br:false}
+					Event.renderBubble story.key()
 					Dom.div !->
 						Dom.style
 							color: 'white'
@@ -186,11 +181,9 @@
 			Dom.onTap !->
 				Page.nav !->
 					Page.setTitle("New topic")
-					Dom.style padding: '12px'
 					Form.input
 						name: 'title'
 						text: tr 'Title'
-					Dom.br()
 					Form.text
 						name: 'text'
 						text: tr 'Info, details, your opinion, etc...'


crown

crown

--- client.coffee
+++ client.coffee
@@ -86,7 +86,7 @@
 								opacity: 1
 						content: !->
 						Dom.style fontSize: '150%', textAlign: 'center', padding: "30px 5px 15px"
-						Dom.text "Go Ismay! Only 900000 more clicks to go!!"
+						Dom.text "I'll be damned, that is one absurd amount of clicks. Bloody well done, " + App.userName(App.userId()) + "! But remeber: with great clicking power comes great clicking responsibility."

 renderFunny = !->
 	Obs.observe !->
@@ -147,6 +147,7 @@
 				onTap: !-> App.showMemberInfo(userId)
 			Dom.div !->
 				Dom.style marginLeft: '10px', Flex: 1
-				Dom.text App.userName(userId)
+				Dom.text App.userName(userId) + " 👑" if counter.get()>= MAGICNUMBER
+				Dom.text App.userName(userId) if counter.get()< MAGICNUMBER
 			Dom.div !->
 				Dom.text counter.get()


Allow 'mailto:' prefix in EssenceLinks.

Allow 'mailto:' prefix in EssenceLinks.

--- app/assets/javascripts/alchemy/alchemy.link_overlay.js.coffee
+++ app/assets/javascripts/alchemy/alchemy.link_overlay.js.coffee
@@ -199,7 +199,7 @@
       $(element).next().addClass('linked').removeClass('disabled')

     validateURLFormat: (url) ->
-      if url.match(/^(\/|[a-z]+:\/\/)/)
+      if url.match(/^(mailto:|\/|[a-z]+:\/\/)/)
         return true
       else
         return false


Fixed test.

Fixed test.

--- app/assets/javascripts/app/controllers/ticket_overview.coffee
+++ app/assets/javascripts/app/controllers/ticket_overview.coffee
@@ -739,4 +739,4 @@
 App.Config.set( 'ticket/view', TicketOverviewRouter, 'Routes' )
 App.Config.set( 'ticket/view/:view', TicketOverviewRouter, 'Routes' )
 App.Config.set( 'TicketOverview', { controller: 'TicketOverview', authentication: true }, 'permanentTask' )
-App.Config.set( 'TicketOverview', { prio: 1000, parent: '', name: 'Tickets', target: '#ticket/view', role: ['Agent', 'Customer'], class: 'overviews' }, 'NavBar' )
+App.Config.set( 'TicketOverview', { prio: 1000, parent: '', name: 'Overviews', target: '#ticket/view', role: ['Agent', 'Customer'], class: 'overviews' }, 'NavBar' )


Fixed empty editor on edit

Fixed empty editor on edit

--- app/assets/javascripts/ecm/cms/backend/application/ace.coffee
+++ app/assets/javascripts/ecm/cms/backend/application/ace.coffee
@@ -6,7 +6,7 @@
     div_id = "editor_for_#{$(textarea).attr('id')}"

     # Add editor div
-    $(textarea).after("

")
+    $(textarea).after("
#{$(textarea).val()}
")

     # Hide textarea
     $(textarea).css('display', 'none');


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/camera_fade.coffee
+++ src/camera_fade.coffee
@@ -40,7 +40,7 @@
   @param {Color} [color="black"] The color to fade from
   ###
   fadeIn: (options={}) ->
-    {duration} = Object.reverseMerge(options, defaultParams)
+    {alpha, colorduration} = Object.reverseMerge(options, defaultParams)

     configureFade(duration, color, 0)


Always save the buffer, even if it is unmodified

--- src/text-buffer.coffee
+++ src/text-buffer.coffee
@@ -677,7 +677,7 @@

   # Public: Save the buffer.
   save: ->
-    @saveAs(@getPath()) if @isModified()
+    @saveAs(@getPath())

   # Public: Save the buffer at a specific path.
   #


Warn about onDidChange being a bad idea

Warn about onDidChange being a bad idea

--- src/text-buffer.coffee
+++ src/text-buffer.coffee
@@ -202,11 +202,16 @@
     @emitter.on 'will-change', callback

   # Public: Invoke the given callback synchronously when the content of the
-  # buffer changes.
+  # buffer changes. **You should probably not be using this in packages**.
   #
   # Because observers are invoked synchronously, it's important not to perform
   # any expensive operations via this method. Consider {::onDidStopChanging} to
-  # delay expensive operations until after changes stop occurring.
+  # delay expensive operations until after changes stop occurring, or at the
+  # very least use {::onDidChangeText} to invoke your callback once *per
+  # transaction* rather than *once per change*. This will help prevent
+  # performance degredation when users of your package are typing with multiple
+  # cursors, and other scenarios in which multiple changes occur within
+  # transactions.
   #
   # * `callback` {Function} to be called when the buffer changes.
   #   * `event` {Object} with the following keys:


Add initial TextEditor::setEncoding

Add initial TextEditor::setEncoding

--- src/text-editor.coffee
+++ src/text-editor.coffee
@@ -2815,6 +2815,12 @@

   pixelRectForScreenRange: (screenRange) -> @displayBuffer.pixelRectForScreenRange(screenRange)

+  setEncoding: (encoding) ->
+    {file} = @buffer
+    file.setEncoding(encoding)
+    file.read(true).then =>
+      file.emitter.emit('did-change')
+
   # Deprecated: Call {::joinLines} instead.
   joinLine: ->
     deprecate("Use TextEditor::joinLines() instead")


test(view): use data from state to detect direction

--- test/spec/services/view.coffee
+++ test/spec/services/view.coffee
@@ -29,13 +29,16 @@
         url: '/home/second/foo/bar/baz'
       .state 'alien',
         url: '/crazy/route'
+      .state 'up',
+        url: '/whatever'
+        data: up: 'home'
     return

   viewService = null
   state = null
   scope = null

-  home = second = third = fourth = fifth = alien = faroff = null
+  home = second = third = fourth = fifth = alien = faroff = up =null

   beforeEach inject ($rootScope, bpView, $state) ->
@@ -49,6 +52,7 @@
     fifth = state.get 'fifth'
     alien = state.get 'alien'
     faroff = state.get 'faroff'
+    up = state.get 'up'

   describe 'listen', ->
@@ -113,10 +117,12 @@
       expect(viewService.getDirection home, second).toBe 'normal'
       expect(viewService.getDirection second, third).toBe 'normal'
       expect(viewService.getDirection home, faroff).toBe 'normal'
+      expect(viewService.getDirection home, up).toBe 'normal'

     it 'should detect "reverse"', ->
       expect(viewService.getDirection second, home).toBe 'reverse'
       expect(viewService.getDirection faroff, home).toBe 'reverse'
+      expect(viewService.getDirection up, home).toBe 'reverse'

     it 'should detect no direction', ->
       expect(viewService.getDirection fourth, fifth).toBe null


Client: Activity: Deleted unnecessary messages props

--- client/activity/lib/components/channelthreadpane/index.coffee
+++ client/activity/lib/components/channelthreadpane/index.coffee
@@ -25,7 +25,6 @@
   getDataBindings: ->
     return {
       channelThread         : getters.selectedChannelThread
-      channelThreadMessages : getters.selectedChannelThreadMessages
       messageThread         : getters.selectedMessageThread
       messageThreadComments : getters.selectedMessageThreadComments
       channelParticipants   : getters.selectedChannelParticipants
@@ -40,7 +39,6 @@
       showDropTarget        : no
       isComingSoonModalOpen : no
       channelThread         : immutable.Map()
-      channelThreadMessages : immutable.List()
       messageThread         : immutable.Map()
       messageThreadComments : immutable.List()
       channelParticipants   : immutable.List()
@@ -114,9 +112,7 @@

-            <PublicChatPane
-              thread={@state.channelThread}
-              messages={@state.channelThreadMessages} />
+            <PublicChatPane thread={@state.channelThread}/>


Support stack build for only groups not for `koding`

--- client/app/lib/providers/environmentsmachinestatemodal.coffee
+++ client/app/lib/providers/environmentsmachinestatemodal.coffee
@@ -17,6 +17,7 @@
 EnvironmentsModalView   = require './environmentsmodalview'

 whoami                  = require '../util/whoami'
+isKoding                = require 'app/util/isKoding'
 showError               = require '../util/showError'
 trackEvent              = require 'app/util/trackEvent'
 sendDataDogEvent        = require '../util/sendDataDogEvent'
@@ -584,10 +585,11 @@
     target     = @machine
     stack      = computeController.findStackFromMachineId @machine._id

-    if stack and @state is NotInitialized and \
-       @machine.jMachine.generatedFrom?.templateId?
-      action   = 'buildStack'
-      target   = stack
+    unless isKoding()
+      if stack and @state is NotInitialized and \
+         @machine.jMachine.generatedFrom?.templateId?
+        action   = 'buildStack'
+        target   = stack

     computeController.off  "error-#{target._id}"


smaller unicode replacement range for u27ac (➬)


minor fixes

minor fixes

--- scripts-src/application.coffee
+++ scripts-src/application.coffee
@@ -1235,7 +1235,7 @@

     if invertible
       dom.tag "p"
-      if vacuum_cycle.length == 1
+      if rule.is_vacuum_stable()
         dom.text "Rule has stable vacuum."
       else
         dom.text "Rule has periodic vacuum with period #{vacuum_cycle.length}. The period is:"


Fix multi-word compound name lookup

Fix multi-word compound name lookup

--- src/coffee/main.coffee
+++ src/coffee/main.coffee
@@ -11,8 +11,8 @@
   invalidCompoundMessage = $ '.row-error'

   getCompoundName = ->
-    encodeURIComponent(compoundTextBox.val()).replace /\s/g, ''
-  window.getCompoundName = getCompoundName
+    encodeURIComponent compoundTextBox.val().replace(/\s/g, '')
+
   # URL builder functions for API.

   buildUrl = (width, height, foreground, background, name) ->


为update添加failOnNoData选项

为update添加failOnNoData选项

--- src/core/update.coffee
+++ src/core/update.coffee
@@ -122,8 +122,10 @@
 								entity.setState(cola.Entity.STATE_NONE)

 				return responseData.result
+		else if options.failOnNoData isnt false
+			deferred = $.Deferred().reject("NO_DATA")
 		else
-			deferred = $.Deferred().reject("NO_DATA")
+			deferred = $.Deferred().resolve(null)

 	deferred.fail (error)->
 		return if options.silence


implement #60

implement #60

--- menus/agda-mode.cson
+++ menus/agda-mode.cson
@@ -1,3 +1,37 @@
+# See https://atom.io/docs/latest/creating-a-package#menus for more details
+'context-menu':
+    'atom-text-editor' : [
+        'label': 'Agda Mode'
+        'submenu': [
+            { 'label': 'Load', 'command': 'agda-mode:load' }
+            { 'label': 'Quit', 'command': 'agda-mode:quit' }
+            { 'label': 'Kill and Restart', 'command': 'agda-mode:restart' }
+            { 'label': 'Abort Command', 'command': 'agda-mode:abort' }
+            { 'label': 'Give', 'command': 'agda-mode:give' }
+            { 'label': 'Refine', 'command': 'agda-mode:refine' }
+            { 'label': 'Auto', 'command': 'agda-mode:auto' }
+            { 'label': 'Case', 'command': 'agda-mode:case' }
+            { 'label': 'Compile', 'command': 'agda-mode:compile' }
+            { 'label': 'Toggle Display of Implicit Arguments', 'command': 'agda-mode:toggle-display-of-implicit-arguments' }
+            { 'label': 'Solve Constraints', 'command': 'agda-mode:solve-constraints' }
+            { 'label': 'Show Constraints', 'command': 'agda-mode:show-constraints' }
+            { 'label': 'Show Goals', 'command': 'agda-mode:show-goals' }
+            { 'label': 'Next Goal', 'command': 'agda-mode:next-goal' }
+            { 'label': 'Previous Goal', 'command': 'agda-mode:previous-goal' }
+            { 'label': 'Infer Type', 'command': 'agda-mode:infer-type[Simplified]' }
+            { 'label': 'Module Contents', 'command': 'agda-mode:module-contents[Simplified]' }
+            { 'label': 'Why In Scope', 'command': 'agda-mode:why-in-scope' }
+            { 'label': 'Compute Normal Form', 'command': 'agda-mode:compute-normal-form[DefaultCompute]' }
+            { 'label': 'Goal Type', 'command': 'agda-mode:goal-type[Simplified]' }
+            { 'label': 'Context', 'command': 'agda-mode:context[Simplified]' }
+            { 'label': 'Goal Type and Context', 'command': 'agda-mode:goal-type-and-context[Simplified]' }
+            { 'label': 'Goal Type and Inferred Type', 'command': 'agda-mode:goal-type-and-inferred-type[Simplified]' }
+            { 'label': 'Query Unicode Symbol Input Sequence', 'command': 'agda-mode:query-symbol' }
+        ]
+    ]
+
+
+
 # See https://atom.io/docs/latest/creating-a-package#menus for more details
 'menu': [
   {


retry on 302

retry on 302

--- evilbot.coffee
+++ evilbot.coffee
@@ -49,9 +49,9 @@
     body = JSON.stringify(body) if typeof(body) isnt 'string'
     headers['Content-Length'] = body.length

-  request = client.request(method, path, headers)
+  req = client.request(method, path, headers)

-  request.on 'response', (response) ->
+  req.on 'response', (response) ->
     if response.statusCode is 200
       data = ''
       response.setEncoding('utf8')
@@ -64,14 +64,16 @@
           catch e
             body = data
           callback body
+    else if response.statusCode is 302
+      request(method, path, body, callback)
     else
       console.log "#{response.statusCode}: #{path}"
       response.setEncoding('utf8')
       response.on 'data', (chunk) ->
         console.log chunk

-  request.write(body) if method is 'POST' and body
-  request.end()
+  req.write(body) if method is 'POST' and body
+  req.end()

 handlers = []


Cache remote.getCurrentWindow.

Cache remote.getCurrentWindow.

--- renderer/api/lib/remote.coffee
+++ renderer/api/lib/remote.coffee
@@ -68,9 +68,11 @@
   metaToValue meta

 # Get current window object.
+windowCache = null
 exports.getCurrentWindow = ->
+  return windowCache if windowCache?
   meta = ipc.sendChannelSync 'ATOM_BROWSER_CURRENT_WINDOW'
-  metaToValue meta
+  windowCache = metaToValue meta

 # Get a global object in browser.
 exports.getGlobal = (name) ->


With new section chooser, topics are set all at once

--- src/flux/task-plan.coffee
+++ src/flux/task-plan.coffee
@@ -225,6 +225,7 @@
     {page_ids} = @_getClonedSettings(id, 'page_ids')
     @_changeSettings(id, page_ids: sortTopics(page_ids))

+<<<<<<< dcee8c903f73b70baba4c9a7ad123e1f69dc8627
   addTopic: (id, topicId) ->
     {page_ids} = @_getClonedSettings(id, 'page_ids')
     page_ids.push(topicId) unless page_ids.indexOf(topicId) >= 0
@@ -256,7 +257,6 @@
     @_changeSettings(id, {exercise_ids})

   updateExercises: (id, exercise_ids) ->
-    # NOTE.  The previous method set page_ids to null here, but I think that was a bug
     @_changeSettings(id, {exercise_ids})

   moveReading: (id, topicId, step) ->


only ping once

only ping once

--- lib/editor/result.coffee
+++ lib/editor/result.coffee
@@ -25,6 +25,7 @@
   try
     if id = localStorage.getItem 'metrics.userId'
       require('http').get "http://data.junolab.org/hit?id=#{id}&app=ink-result"
+    metrics = ->

 module.exports =
 class Result


miniterm: bugfix -- minial terminal commands in a directory with multiple spaces in it fails

--- salvus/page/project_miniterm.cjsx
+++ salvus/page/project_miniterm.cjsx
@@ -70,7 +70,7 @@
         input = @state.input.trim()
         if not input
             return
-        input0 = input + '\necho $HOME `pwd`'
+        input0 = input + '\necho $HOME "`pwd`"'
         @setState(state:'run')

         @_id = (@_id ? 0) + 1


Fixed Logger tests

Fixed Logger tests

--- src/helpers/logger.coffee
+++ src/helpers/logger.coffee
@@ -75,7 +75,7 @@
         console.log(funHeader("[Ibizan] (#{new Date()}) > ") + fun("#{msg}"))
     @logToChannel: (msg, channel) ->
       if msg
-        if robot and robot.send?
+        if robot and robot.adapter? and robot.adapter.customMessage?
           attachment = {
             channel: channel,
             text: msg


Test pubsub

Test pubsub

--- spec/lanes/models/PubSubSpec.coffee
+++ spec/lanes/models/PubSubSpec.coffee
@@ -8,13 +8,15 @@
         @getModelConfig = ->
             Lanes.Models.PubSub.types.get('/s').records[1]

-    it "checks in/out a model when it binds to a view", ->
+    it "checks in/out a model when it binds to a view", (done) ->
         spyOn(Lanes.Models.PubSub, 'add').and.callThrough()
         spyOn(Lanes.Models.PubSub, 'remove')
         component = Lanes.Test.makeComponent({}, model: @model)
-        expect(Lanes.Models.PubSub.add).toHaveBeenCalledWith(@model)
-        component.data.destroy()
-        expect(Lanes.Models.PubSub.remove).toHaveBeenCalledWith(@model)
+        _.defer =>
+            expect(Lanes.Models.PubSub.add).toHaveBeenCalledWith(@model)
+            component.modelBindings.destroy()
+            expect(Lanes.Models.PubSub.remove).toHaveBeenCalledWith(@model)
+            done()

     it "can retrieve a model after checkin", ->
         record = new @Model(id: 11, foo: 'bar')
@@ -34,12 +36,10 @@
             Lanes.Models.PubSub.remove(@model)
         ).not.toThrowError()

-
     it 'removes models', ->
         Lanes.Models.PubSub.add(@model)
         configs = Lanes.Models.PubSub.types.get('/s').records[1].models
         expect(configs.length).toEqual(1)
-
         Lanes.Models.PubSub.remove(@model)
         expect(configs[0].count).toEqual(1)
         Lanes.Models.PubSub.remove(@model)
@@ -66,8 +66,6 @@
         expect(Lanes.Models.PubSub.mb.subscribe.calls.count()).toEqual(1)
         expect(Lanes.Models.PubSub.mb.unsubscribe.calls.count()).toEqual(0)
         Lanes.Models.PubSub.remove(newModel)
-
         Lanes.Models.PubSub.remove(newModel)
-
         expect(@getModelConfig()).toBeUndefined()
         expect(Lanes.Models.PubSub.mb.unsubscribe.calls.count()).toEqual(1)


Check if pretender is available

Check if pretender is available

--- server/prerender/index.coffee
+++ server/prerender/index.coffee
@@ -2,6 +2,13 @@
 prerenderUrl = 'http://127.0.0.1:' + (process.env.PORT or 3000)

 module.exports = (app) ->
+  # Check if Prerender is available
+  try
+    require.resolve('prerender')
+    require.resolve('prerender-node')
+  catch
+    return
+
   # Start up prerender server
   child = fork('server/prerender/server')
   process.on 'exit', ->


capture #1205 in a test

capture #1205 in a test

--- test/xlform/model.tests.coffee
+++ test/xlform/model.tests.coffee
@@ -160,6 +160,25 @@
       # scsv = @createSurveyCsv ["text,text,text,text"]
       # @dumpAndLoad scsv

+    it "captures required values", ->
+      srv = $model.Survey.loadDict({
+          survey: [
+            {
+              type: 'text',
+              name: 'q1',
+              required: true
+            },
+            {
+              type: 'text',
+              name: 'q2',
+              required: false
+            }
+          ]
+        })
+      exported = srv.toJSON()
+      expect(exported.survey[0]['required']).toEqual('true')
+      expect(exported.survey[1]['required']).toEqual('false')
+
     it "tries a few question types", ->
       srv = @createSurvey ["text,text,text,text"]
       row1 = srv.rows.at(0)


hint keycap have problem to affect original m(=mark)

--- keymaps/vim-mode-plus.cson
+++ keymaps/vim-mode-plus.cson
@@ -130,7 +130,7 @@
   # 'g _': 'vim-mode-plus:snake-case'
   # 'g r':   'vim-mode-plus:reverse'
   # 'ctrl-s': 'vim-mode-plus:transform-string-by-select-list'
-  # 'm r': 'vim-mode-plus:mark-range'
+  # 'g m': 'vim-mode-plus:mark-range'

   'ctrl-a': 'vim-mode-plus:increase'
   'ctrl-x': 'vim-mode-plus:decrease'


Added file name in regex message.

Added file name in regex message.

--- lib/linter-pylama.coffee
+++ lib/linter-pylama.coffee
@@ -8,7 +8,7 @@

 class LinterPylama extends Linter
-  regex: ':(?<line>\\d+):(?<col>\\d+):\\s+((((?<error>E)|(?<warning>[CDFNW]))(?<code>\\d+)(:\\s+|\\s+))|(.*?))(?<message>.+)(\r)?\n'
+  regex: '(?<file>.+):(?<line>\\d+):(?<col>\\d+):\\s+((((?<error>E)|(?<warning>[CDFNW]))(?<code>\\d+)(:\\s+|\\s+))|(.*?))(?<message>.+)(\r)?\n'

   linterName: 'pylama'
   @syntax: 'source.python'


end date bugfix

end date bugfix

--- lib/notifications.coffee
+++ lib/notifications.coffee
@@ -193,7 +193,7 @@
         html += '\n\n'
         text += '\n\n'
         if diffs.length > 1
-          dates = "Changes occurred between #{diffs[0].updated} and #{diffs[diffs.length-1].update}"
+          dates = "Changes occurred between #{diffs[0].updated} and #{diffs[diffs.length-1].updated}"
         else
           dates = "Change occurred on #{diffs[0].updated}"
         html += "
#{dates}
\n"


Forgot a negation

Forgot a negation

We /don't/ want to put in any args if the setting says it is disabled.

--- lib/swank-starter.coffee
+++ lib/swank-starter.coffee
@@ -15,7 +15,7 @@
       return false
     command = @lisp
     args = []
-    if atom.config.get 'atom-slime.advancedSettings.disableSwankArgs'
+    if ! atom.config.get 'atom-slime.advancedSettings.disableSwankArgs'
       args.push '--load' unless command.match(/clisp/)  # CLISP does not have a --load option
       args.push @swank_script
     @process = new BufferedProcess({


Fix bug in fetch-mock that breaks custom Promise usage

--- tests/setup.coffee
+++ tests/setup.coffee
@@ -12,6 +12,7 @@
 	global.Response = realFetchModule.Response

 fetchMock = require('fetch-mock').sandbox(Promise)
+fetchMock.fetchMock.Promise = Promise
 fetchMock.patch = (matcher, response, options) ->
 	options = _.assign({}, options, { method: 'PATCH' })
 	@mock(matcher, response, options)


oops, don't need to check for journal twice

--- server/express/lib/server.coffee
+++ server/express/lib/server.coffee
@@ -393,7 +393,7 @@
           sitemap.push({
             slug     : page.title.replace(/\s/g, '-').replace(/[^A-Za-z0-9-]/g, '').toLowerCase(),
             title    : page.title,
-            date     : page.journal and page.journal and page.journal.length >0 and page.journal.pop().date,
+            date     : page.journal and page.journal.length > 0 and page.journal.pop().date,
             synopsis : synopsis
           })
           numFiles--


WebtermView: if machine is stopped invalidate current session [fixes #88998084]

--- client/app/lib/terminal/webtermview.coffee
+++ client/app/lib/terminal/webtermview.coffee
@@ -86,9 +86,17 @@

     # watch machine state:
     { computeController } = kd.singletons
+    { Stopped, Stopping } = Machine.State
+
     computeController.on "public-#{@getMachine()._id}", (event) =>
-      if event.status in [Machine.State.Stopping, Machine.State.Stopped]
+      if event.status in [Stopping, Stopped]
         @terminal.cursor.stopBlink()
+
+        # If machine is stopped we need to invalidate current sessions
+        # user can decide to create a new one or destroy this one.
+        if event.status is Stopped
+          @messagePane.handleError message: "ErrNoSession"
+

     @setKeyView()


test: user see new page after clicked view guide button

--- client/test/lib/dashboard/utilities.coffee
+++ client/test/lib/dashboard/utilities.coffee
@@ -90,3 +90,9 @@
       .click saveButton
       .waitForElementVisible '.kdnotification', 10000
       .assert.containsText '.kdnotification', saveButtonSaveResponse
+      .pause 5000
+
+      .click viewGuideButton, (result) ->
+
+        if result.state is 'success'
+          helpers.switchBrowser browser, 'https://www.koding.com/docs/chatlio'


Update test to use issuer_id

Update test to use issuer_id

--- test/models/user-test.coffee
+++ test/models/user-test.coffee
@@ -50,7 +50,7 @@
     badge = null
     beforeEach (done)->
       User.where('username').in(['bob','alice']).remove ->
-        badge = new Badge name: 'super badge', issuer: org.id
+        badge = new Badge name: 'super badge', issuer_id: org.id
         badge.save ->
           user = new User username: 'bob'
           user.save ->


:art:

:art:

--- src/layer-iterator.coffee
+++ src/layer-iterator.coffee
@@ -4,20 +4,18 @@
 module.exports =
 class LayerIterator
   constructor: (@layer, sourceIterator) ->
-    @transformDelegate = new TransformDelegate(sourceIterator)
     @position = Point.zero()
     @sourcePosition = Point.zero()
+    @transformDelegate = new TransformDelegate(sourceIterator)

   next: ->
     unless @transformDelegate.bufferedOutputs.length > 0
       @layer.transform.operate(@transformDelegate)
-    @position = @transformDelegate.bufferedPositions.shift()
-    @sourcePosition = @transformDelegate.bufferedSourcePositions.shift()
-    value = @transformDelegate.bufferedOutputs.shift()
-    if value is EOF
+    {@position, @sourcePosition, content} = @transformDelegate.bufferedOutputs.shift()
+    if content is EOF
       {done: true}
     else
-      {value, done: false}
+      {done: false, value: content}

   seek: (position) ->
     @position = Point.zero()
@@ -26,16 +24,16 @@
     return if position.isZero()

     until @position.compare(position) >= 0
-      lastReadPosition = @position
-      lastReadSourcePosition = @sourcePosition
-      {value, done} = @next()
+      lastPosition = @position
+      lastSourcePosition = @sourcePosition
+      {done} = @next()
       return if done

     unless @position.compare(position) is 0
-      overshoot = position.column - lastReadPosition.column
-      lastReadSourcePosition.column += overshoot
+      overshoot = position.column - lastPosition.column
+      lastSourcePosition.column += overshoot
       @position = position
-      @sourcePosition = lastReadSourcePosition
+      @sourcePosition = lastSourcePosition
     @transformDelegate.reset(@position, @sourcePosition)

   seekToSourcePosition: (position) ->
@@ -45,15 +43,15 @@
     return if position.isZero()

     until @sourcePosition.compare(position) >= 0
-      lastReadPosition = @position
-      lastReadSourcePosition = @sourcePosition
-      {value, done} = @next()
+      lastPosition = @position
+      lastSourcePosition = @sourcePosition
+      {done} = @next()
       break if done

-    overshoot = position.column - lastReadSourcePosition.column
-    lastReadPosition.column += overshoot
-    @transformDelegate.reset(lastReadPosition, position)
-    @position = lastReadPosition
+    overshoot = position.column - lastSourcePosition.column
+    lastPosition.column += overshoot
+    @transformDelegate.reset(lastPosition, position)
+    @position = lastPosition
     @sourcePosition = position

   getPosition: ->
@@ -67,21 +65,18 @@
     @reset(Point.zero(), Point.zero())

   reset: (position, sourcePosition) ->
-    @sourceIterator.seek(sourcePosition)
     @position = position
     @sourcePosition = sourcePosition
+    @bufferedOutputs = []
     @bufferedSourceOutput = null
-    @bufferedOutputs = []
-    @bufferedPositions = []
-    @bufferedSourcePositions = []
+    @sourceIterator.seek(sourcePosition)

   read: =>
-    @bufferedSourceOutput ?= @sourceIterator.next().value
+    @bufferedSourceOutput or= @sourceIterator.next().value

   consume: (count) =>
     @sourcePosition.column += count
-    @bufferedSourceOutput = @bufferedSourceOutput.slice(count)
-    @bufferedSourceOutput = null if @bufferedSourceOutput is ""
+    @bufferedSourceOutput = @bufferedSourceOutput.substring(count)

   produce: (output) =>
     switch output
@@ -93,6 +88,8 @@
       else
         @position.column += output.length

-    @bufferedSourcePositions.push(@sourcePosition.copy())
-    @bufferedPositions.push(@position.copy())
-    @bufferedOutputs.push(output)
+    @bufferedOutputs.push(
+      content: output
+      position: @position.copy()
+      sourcePosition: @sourcePosition.copy()
+    )


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/game_object.coffee
+++ src/game_object.coffee
@@ -161,7 +161,7 @@
       I.active = false
   }

-  defaultModules = [Bindable, Bounded, Clampable, Cooldown, Drawable, Durable, Follow, Metered, TimedEvents, Tween]
+  defaultModules = [Bindable, Bounded, Clampable, Cooldown, Drawable, Expirable, Follow, Metered, TimedEvents, Tween]
   modules = defaultModules.concat(I.includedModules.invoke('constantize'))
   modules = modules.without(I.excludedModules.invoke('constantize'))


Of course lodash has it

Of course lodash has it

--- resources/assets/coffee/react/beatmap-discussions/main.coffee
+++ resources/assets/coffee/react/beatmap-discussions/main.coffee
@@ -116,11 +116,7 @@

   indexUsers: (usersArray) ->
-    reducer = (prev, curr) =>
-      prev[curr.id] = curr
-      prev
-
-    usersArray.reduce reducer, {}
+    _.keyBy usersArray, (u) => u.id

   lookupUser: (id) ->


Bug fix for JSONUtils.parse for array type

--- src/lib/json_utils.coffee
+++ src/lib/json_utils.coffee
@@ -1,5 +1,5 @@
 ###
-  backbone-orm.js 0.6.5
+  backbone-orm.js 0.7.1
   Copyright (c) 2013-2014 Vidigami
   License: MIT (http://www.opensource.org/licenses/mit-license.php)
   Source: https://github.com/vidigami/backbone-orm
@@ -49,14 +49,10 @@
   #
   @parse: (obj, model_type) ->
     return JSONUtils.parseDates(obj) unless _.isObject(obj)
-    if _.isArray(obj)
-      result = []
-      result.push(JSONUtils.parseField(value, model_type, key)) for value in obj
-      return result
-    else
-      result = {}
-      result[key] = JSONUtils.parseField(value, model_type, key) for key, value of obj
-      return result
+    return (JSONUtils.parse(value, model_type) for value in obj) if _.isArray(obj)
+    result = {}
+    result[key] = JSONUtils.parseField(value, model_type, key) for key, value of obj
+    return result

   # Deserialze a strict-JSON query to a json format
   #


Fix test

Fix test

--- test/src/sitemap_test.coffee
+++ test/src/sitemap_test.coffee
@@ -57,6 +57,6 @@
       @sitemap.stream.on 'data', (data) ->
         self.sitemap.stream.write(data)

-      @sitemap.addUrl({url:"http://foo.com/foo.html", updatedAt: new Date(), changefreq: 'daily', priority: 1, links: [{rel: 'alternate', href: "http://foo.com/link"}] })
+      @sitemap.addUrl({url:"http://foo.com/foo.html", updatedAt: new Date(), changefreq: 'daily', priority: 1, linkTags: [{rel: 'alternate', href: "http://foo.com/link"}] })
       @sitemap.stream.end()


Work with ObjectId's passed to ProjectGetter

--- services/web/app/coffee/Features/Project/ProjectGetter.coffee
+++ services/web/app/coffee/Features/Project/ProjectGetter.coffee
@@ -10,7 +10,7 @@
 		excludes = {}
 		for i in [1..@EXCLUDE_DEPTH]
 			excludes["rootFolder#{Array(i).join(".folder")}.docs.lines"] = 0
-		db.projects.find _id: ObjectId(project_id), excludes, (error, projects = []) ->
+		db.projects.find _id: ObjectId(project_id.toString()), excludes, (error, projects = []) ->
 			callback error, projects[0]

 	getProject: (query, projection, callback = (error, project) ->) ->


traceback when server-side plugins fail to start

--- server/express/lib/plugins.coffee
+++ server/express/lib/plugins.coffee
@@ -19,7 +19,7 @@
 					plugins[plugin] = require server
 					plugins[plugin].startServer?(params)
 				catch e
-					console.log 'failed to start plugin', plugin, e
+					console.log 'failed to start plugin', plugin, e?.stack or e

 	startServers = (params) ->
 		fs.readdir pluginsdir , (e, plugins) ->


* update test/filter.coffee

* update test/filter.coffee

--- test/filter.coffee
+++ test/filter.coffee
@@ -9,19 +9,20 @@
 fs              = require 'fs'
 path            = fs.path

-module.exports = (Folder)->
+module.exports = (Folder, filterFn, expected)->
   ->
-    filterFn = (file)->path.extname(file.path) is '.md'
+    filterFn ?= (file)->path.extname(file.path) is '.md'
+    expected ?= ['fixtures/folder/index.md']
     it 'should filter files sync default', ->
       dir = Folder path.join(__dirname, 'fixtures', 'folder'), base: __dirname, filter: filterFn
       result = dir.loadSync read:true
       result = result.map (file)->file.relative
-      expect(result).be.deep.equal ['fixtures/folder/index.md']
+      expect(result).be.deep.equal expected
     it 'should filter files sync buffer', ->
       dir = Folder path.join(__dirname, 'fixtures', 'folder'), base: __dirname
       result = dir.loadSync read:true, filter: filterFn
       result = result.map (file)->file.relative
-      expect(result).be.deep.equal ['fixtures/folder/index.md']
+      expect(result).be.deep.equal expected
     it 'should filter files sync stream', (done)->
       dir = Folder path.join(__dirname, 'fixtures', 'folder'), base: __dirname
       result = []
@@ -30,7 +31,7 @@
         result.push file.relative
       .on 'error', (err)->done(err)
       .on 'end', ->
-        expect(result).be.deep.equal ['fixtures/folder/index.md']
+        expect(result).be.deep.equal expected
         done()

     it 'should filter files async default', (done)->
@@ -38,14 +39,14 @@
       dir.load read:true, (err, result)->
         unless err
           result = result.map (file)->file.relative
-          expect(result).be.deep.equal ['fixtures/folder/index.md']
+          expect(result).be.deep.equal expected
         done(err)
     it 'should filter files async buffer', (done)->
       dir = Folder path.join(__dirname, 'fixtures', 'folder'), base: __dirname
       dir.load read:true, filter: filterFn, (err, result)->
         unless err
           result = result.map (file)->file.relative
-          expect(result).be.deep.equal ['fixtures/folder/index.md']
+          expect(result).be.deep.equal expected
         done(err)
     it 'should filter files async stream', (done)->
       dir = Folder path.join(__dirname, 'fixtures', 'folder'), base: __dirname
@@ -56,5 +57,5 @@
           result.push file.relative
         .on 'error', (err)->done(err)
         .on 'end', ->
-          expect(result).be.deep.equal ['fixtures/folder/index.md']
+          expect(result).be.deep.equal expected
           done()


Follow mixin

Follow mixin

--- test/follow.coffee
+++ test/follow.coffee
@@ -25,6 +25,7 @@
     height: 10

   fastEnemy.include(Follow)
-  fastEnemy.foll
+  fastEnemy.follow(player)
+

 module()


SocialWorker: Add fetchPrivateMessageCount and search functions

--- workers/social/lib/social/models/socialapi/message.coffee
+++ workers/social/lib/social/models/socialapi/message.coffee
@@ -47,6 +47,10 @@
         sendPrivateMessageFromBot:
           (signature Object, Function)
         fetchPrivateMessages:
+          (signature Object, Function)
+        fetchPrivateMessageCount:
+          (signature Object, Function)
+        search :
           (signature Object, Function)
         fetch  :
           (signature Object, Function)
@@ -143,6 +147,10 @@
     permissionName: 'list private messages'
     fnName        : 'fetchPrivateMessages'

+  @fetchPrivateMessageCount = secureRequest
+    permissionName: 'list private messages'
+    fnName        : 'fetchPrivateMessageCount'
+
   @fetch = permittedRequest
     permissionName: 'read posts'
     fnName        : 'fetchMessage'
@@ -195,6 +203,8 @@
         client.connection.delegate = account
         sendPrivateMessageHelper client, data, callback

+  # searchChats - browse chats depending on purpose/username fields
+  @search  = secureRequest fnName: 'searchChats'

   # todo-- ask Chris about using validators.own
   # how to implement for this case


VerificationToken: make pins bold

VerificationToken: make pins bold

--- workers/social/lib/social/models/verificationtoken.coffee
+++ workers/social/lib/social/models/verificationtoken.coffee
@@ -111,8 +111,8 @@
         Welcome to Koding!

         You can use the following code to verify your koding.com account:
+          #{pin}

-          #{pin}

         Have a nice day!
         --
@@ -124,7 +124,7 @@

         To verify your new e-mail address you can use the following code:

-          #{pin}
+          #{pin}

         Have a nice day!
         --
@@ -136,7 +136,7 @@

         Here’s your koding.com verification code:

-          #{pin}
+          #{pin}

         Have a nice day!
         --


Added GetALLToLocations and KeepALLFromLocations schemas

--- server/levels/level_schema.coffee
+++ server/levels/level_schema.coffee
@@ -36,9 +36,19 @@
   getToLocations: c.object {title: "Get To Locations", description: "Will be set off when any of the \"who\" touch any of the \"targets\" ", required: ["who", "targets"]},
     who: c.array {title: "Who", description: "The Thangs who must get to the target locations.", minItems: 1}, thang
     targets: c.array {title: "Targets", description: "The target locations to which the Thangs must get.", minItems: 1}, thang
+  getAllToLocations: c.array {title: "Get all to locations", description: "Similar to getToLocations but now a specific \"who\" can have a specific \"target\"",required: ["getToLocation"]},
+  c.object {title: "", description: ""},
+    getToLocation: c.object {title: "Get To Locations", description: "TODO: explain", required: ["who", "targets"]},
+      who: c.array {title: "Who", description: "The Thangs who must get to the target locations.", minItems: 1}, thang
+      targets: c.array {title: "Targets", description: "The target locations to which the Thangs must get.", minItems: 1}, thang
   keepFromLocations: c.object {title: "Keep From Locations", description: "TODO: explain", required: ["who", "targets"]},
     who: c.array {title: "Who", description: "The Thangs who must not get to the target locations.", minItems: 1}, thang
     targets: c.array {title: "Targets", description: "The target locations to which the Thangs must not get.", minItems: 1}, thang
+  keepAllFromLocations: c.array {title: "Keep ALL From Locations", description: "Similar to keepFromLocations but now a specific \"who\" can have a specific \"target\"", required: ["keepFromLocation"]},
+  c.object {title: "", description: ""},
+    keepFromLocation: c.object {title: "Keep From Locations", description: "TODO: explain", required: ["who", "targets"]},
+      who: c.array {title: "Who", description: "The Thangs who must not get to the target locations.", minItems: 1}, thang
+      targets: c.array {title: "Targets", description: "The target locations to which the Thangs must not get.", minItems: 1}, thang
   leaveOffSides: c.object {title: "Leave Off Sides", description: "Sides of the level to get some Thangs to leave across.", required: ["who", "sides"]},
     who: c.array {title: "Who", description: "The Thangs which must leave off the sides of the level.", minItems: 1}, thang
     sides: c.array {title: "Sides", description: "The sides off which the Thangs must leave.", minItems: 1}, side
@@ -242,3 +252,4 @@
 # 3. tv4.addSchema(metaschema.id, metaschema)
 # 4. S = <paste big schema here>
 # 5. tv4.validateMultiple(S, metaschema)   and look for errors
+


More flexibility in passing data to {{partial}}

--- shared/handlebars_helpers.coffee
+++ shared/handlebars_helpers.coffee
@@ -32,7 +32,12 @@
     template = templateFinder.getTemplate(templateName)

     options = block.hash || {}
-    data = options.context || this
+    data = if _.isEmpty(options)
+        this
+      else if options.context
+        options.context
+      else
+        options

     html = template(data)
     new Handlebars.SafeString(html)


reuse name, delete map file after reading it

--- plugin/browserify.coffee
+++ plugin/browserify.coffee
@@ -43,7 +43,8 @@

   # extract the source map content from the generated file to give to Meteor
   # explicitly by piping bundle thru `exorcist`
-  bundle = bundle.pipe exorcist step.fullInputPath+'.map', step.pathForSourceMap
+  mapFileName = step.fullInputPath+'.map'
+  bundle = bundle.pipe exorcist mapFileName, step.pathForSourceMap

   # use Meteor.wrapAsync to wrap `getString` so it's done synchronously
   wrappedFn = Meteor.wrapAsync getString
@@ -53,7 +54,8 @@
     string = wrappedFn bundle

     # read the generated source map from the file
-    sourceMap = fs.readFileSync step.fullInputPath+'.map', 'utf8'
+    sourceMap = fs.readFileSync mapFileName, 'utf8'
+    fs.unlinkSync mapFileName

     # now that we have the compiled result as a string we can add it using CompileStep
     # inside try-catch because this shouldn't run when there's an error.


Title and description parsing now handled as a Transform stream

--- packages/shrub-grunt/dox/dynamic.coffee
+++ packages/shrub-grunt/dox/dynamic.coffee
@@ -41,7 +41,7 @@

     done()

-# Implement a Transform stream fo accumulate TODOs from a source file. Also
+# Implement a Transform stream to accumulate TODOs from a source file. Also
 # caches lines to be able to build context around each TODO item.
 class Todos extends Transform

@@ -69,6 +69,45 @@

       index: todo
       lines: @lines.slice start, end
+
+# Implement a Transform stream to parse title and description for a file.
+class TitleAndDescription extends Transform
+
+  constructor: ->
+    super
+
+    @hasFinishedParsing = false
+
+    @title = ''
+    @description = ''
+
+  _transform: (chunk, encoding, done) ->
+
+    return done() if @hasFinishedParsing
+
+    line = chunk.toString('utf8').trim()
+    return done() if line.length is 0
+
+    if 35 is line.charCodeAt(0)
+
+      if 35 is line.charCodeAt(2)
+        @title = line.substr 4
+
+      else if 42 is line.charCodeAt(2)
+        @description = line.substr 2
+
+      else if @description?
+        @description += ' ' + line.substr 2
+
+      if 42 is @description.charCodeAt @description.length - 1
+        @hasFinishedParsing = true
+        return done()
+
+    else
+
+      @hasFinishedParsing = true
+
+    return done()

 # Implement a transform stream to convert a .coffee file to .litcoffee
 class LitcoffeeConversion extends Transform
@@ -216,6 +255,7 @@
         lineStream.pipe hookImplementations = new HookImplementations()
         lineStream.pipe hookInvocations = new HookInvocations()
         lineStream.pipe todos = new Todos()
+        lineStream.pipe titleAndDescription = new TitleAndDescription()

         fstream.on 'error', reject

@@ -228,6 +268,8 @@
             implementations: hookImplementations.list
             invocations: hookInvocations.list
             todos: todos.withContext()
+            title: titleAndDescription.title
+            description: titleAndDescription.description
           )

     Promise.all typePromises
@@ -502,25 +544,16 @@
     else
       render += "## [#{fileStats.pkg}](source/#{sourcePath})"

-    # Naively parse out the file description. It must be wrapped in asterisks,
-    # i.e. italicized in markdown.
-    #
-    # ###### TODO: This 'chunk' parsing should be done with a Transform like the others.
-    data = fs.readFileSync "docs/source/#{fileStats.file}", 'utf8'
-    chunks = data.split '\n\n'
-
-    title = chunks[0]
-    if 35 is title.charCodeAt 0
+    if fileStats.title?
       render += '\n\n'
       render += '> ' if isSubpackage
-      render += "#{title.substr 2}"
+      render += "#{fileStats.title}"

     render += '\n\n'

-    description = chunks[1] ? ''
-    if 42 is description.charCodeAt 0 and 42 is description.charCodeAt description.length - 1
+    if fileStats.description?
       render += '> ' if isSubpackage
-      render += "#{description}\n\n"
+      render += "#{fileStats.description}\n\n"

     if fileStats.implementations.length > 0
       render += '> ' if isSubpackage


Remove cloak before broadcasting that the skin changed

--- packages/shrub-skin/client/index.coffee
+++ packages/shrub-skin/client/index.coffee
@@ -18,10 +18,7 @@
 			document.getElementsByTagName('head')[0].appendChild cloak

 			promise = skin.change config.get 'packageConfig:shrub-skin:default'
-			promise.then ->
-
-				angular.element('link.initial').remove()
-				skin.removeCloak()
+			promise.then -> angular.element('link.initial').remove()

 	]

@@ -129,7 +126,10 @@

 					$q.when()

-				finalPromise.then -> $rootScope.$broadcast 'shrub.skin.changed'
+				finalPromise.then ->
+
+					service.removeCloak()
+					$rootScope.$broadcast 'shrub.skin.changed'

 			service.removeCloak = ->
 				angular.element('.shrub-skin-cloak').each ->


ignoring url parameters for route matching

--- src/http/router.coffee
+++ src/http/router.coffee
@@ -21,7 +21,12 @@
     else
       if url == '/'
         return false
-      sr = url.split('/')
+
+      if url.indexOf('?') >= 0
+        sr = url.split('?')
+      else
+        sr = url.split('/')
+
       sr.pop()
       newUrl = sr.join('/')
       newUrl = '/' unless newUrl.length > 0


Fixed remove attribute test, added mixed attribute tests

--- tests/scripts/unit/undo-manager.coffee
+++ tests/scripts/unit/undo-manager.coffee
@@ -32,7 +32,12 @@
     undoTests.run('insert attribute', {}, new Tandem.Delta(13, [new Tandem.RetainOp(0, 13, {bold: true})]))

     newInitial = Tandem.Delta.getInitial("The lazy fox\n").compose(new Tandem.Delta(13, [new Tandem.RetainOp(0, 13, {bold: true})]))
-    undoTests.run('remove attribute', {initial: newInitial}, new Tandem.Delta(13, [new Tandem.RetainOp(0, 13)]))
+    undoTests.run('remove attribute', {initial: newInitial}, new Tandem.Delta(13, [new Tandem.RetainOp(0, 13, {bold: false})]))
+
+    undoTests.run('insert color/strike', {}, new Tandem.Delta(13, [new Tandem.RetainOp(0, 13, {color: 'blue', strike: true})]))
+
+    newInitial = Tandem.Delta.getInitial("The lazy fox\n").compose(new Tandem.Delta(13, [new Tandem.RetainOp(0, 13, {color: 'blue', strike: true})]))
+    undoTests.run('remove color/strike', {initial: newInitial}, new Tandem.Delta(13, [new Tandem.RetainOp(0, 13, {color: 'black', strike: false})]))

     # Multi-user
     undoTests.run('remove external insert', {fn: (editor, expectedEditor, delta) -> editor.applyDelta(delta, {source: 'api'})},


EmailTemplates: use new Koding logo and colors for system emails too.

--- workers/emailsender/templates.coffee
+++ workers/emailsender/templates.coffee
@@ -14,7 +14,7 @@

 Templates =

-  linkStyle    : """ style="text-decoration:none; color:#ff9200;" """
+  linkStyle    : """ style="text-decoration:none; color:#1AAF5D;" """
   textTemplate : (content, unsubscribeId, email)->
     """
      Hello,
@@ -40,24 +40,24 @@

-
+
+

-

-

-

+

-
-
+

-

-

-
-
+
+
+

+

+

+

#{content}


Fixing index out of bounds when leaving map

--- src/js/Animator.coffee
+++ src/js/Animator.coffee
@@ -169,13 +169,16 @@
          @marble.targetNode.getNeighbours()[0] is @marble.lastNode
         [bX, bY, bZ] = @marble.currentBlock.getCoordinates()

-        nextBlock = @map.getBlock bX + SIGNUM(tX - lX),
-                                  bY + SIGNUM(tY - lY),
-                                  bZ + SIGNUM(tZ - lZ)
+        nX = bX + SIGNUM(tX - lX)
+        nY = bY + SIGNUM(tY - lY)
+        nZ = bZ + SIGNUM(tZ - lZ)
+
+        if 0 <= nX < @map.size and
+           0 <= nY < @map.size and
+           0 <= nZ < @map.size
+          nextBlock = @map.getBlock nX, nY, nZ

         if nextBlock
-          console.log nextBlock
-
           [@marble.targetNode, @marble.lastNode] = [@marble.lastNode, @marble.targetNode]
           @marble.setTrackSpeed @marble.getTrackSpeed() * Settings.blockDampening
           [tX, tY, tZ] = @marble.targetNode.getCoordinates()


Remove unused require

Remove unused require

--- spec/archive-editor-spec.coffee
+++ spec/archive-editor-spec.coffee
@@ -1,5 +1,4 @@
 path = require 'path'
-{Document} = require 'atom'
 ArchiveEditor = require '../lib/archive-editor'

 describe "ArchiveEditor", ->


Added more jQuery plugins tests.

Added more jQuery plugins tests.

--- spec/jquery.plugins.spec.coffee
+++ spec/jquery.plugins.spec.coffee
@@ -7,17 +7,46 @@
 $ = jQuery

 describe 'jQuery Plugins', ->
-  describe 'when converting an array to bidimensional', ->
-    html_box = null
+  html_box = null
+
+  beforeEach ->
+    html_box = $ '

'
+    html_box.append($('

').append($('

')).append($('

')).append($('

')).append($('

')))
+
+  describe 'when reversing a jQuery array', ->
+    original_array = null
+    array = null

     beforeEach ->
-      html_box = $ '

'
-      html_box.append($('

').append($('

')).append($('

')).append($('

')).append($('

')))
+      array = html_box.find 'li'
+      original_array = html_box.find 'li'
+      array = array.reverse()

+    it 'should return the elements in reverse order', ->
+      for i in [0...array.length]
+        expect(array[i]).toEqual original_array[array.length - 1 - i]
+
+  describe 'when shuffling a jQuery array', ->
+    original_array = null
+    first_copy = null
+    second_copy = null
+
+    beforeEach ->
+      original_array = html_box.find 'li'
+      first_copy = original_array.slice()
+      second_copy = original_array.shuffle()
+
+    it 'should return the same array with shifted the positions', ->
+      expect(second_copy).toEqual original_array
+
+    it 'should not let the original array untouched', ->
+      expect(first_copy).not.toEqual original_array
+
+  describe 'when converting an array to bidimensional', ->
     it 'should return an array with the expected dimensions', ->
-      list_items = html_box.find 'ul li'
+      list_items = html_box.find 'li'
       array = list_items.as2dArray 2
-      expect(array[0][0].get(0)).toEqual(list_items.get(0))
-      expect(array[0][1].get(0)).toEqual(list_items.get(1))
-      expect(array[1][0].get(0)).toEqual(list_items.get(2))
-      expect(array[1][1].get(0)).toEqual(list_items.get(3))
+
+      for i in [0...2]
+        for j in [0...2]
+          expect(array[i][j].get(0)).toEqual list_items.get(i * 2 + j)


add in select to total record count

add in select to total record count

--- shared/ironTableController.coffee
+++ shared/ironTableController.coffee
@@ -153,9 +153,9 @@
                 editOk: @collection().editOk?(record)
                 deleteOk: @collection().deleteOk?(record)

-        recordDisplayStop = @skip() + @increment
-        if recordDisplayStop > @_sess("recordCount")
-            recordDisplayStop = @_sess("recordCount")
+        recordDisplayStop = @skip() + recordData.length
+        #if recordDisplayStop > @_sess("recordCount")
+        #    recordDisplayStop = @_sess("recordCount")

         theData =
             haveData: records? and (records.length > 0 or @_sess("recordCount") > 0)


Call activatePackage on atom.packages

Call activatePackage on atom.packages

--- spec/autocomplete-spec.coffee
+++ spec/autocomplete-spec.coffee
@@ -449,7 +449,7 @@
     expect(autocomplete.list.prop('scrollWidth')).toBe autocomplete.list.width()

   it "includes completions for the scope's completion preferences", ->
-    atom.activatePackage('language-css', sync: true)
+    atom.packages.activatePackage('language-css', sync: true)
     cssEditor = new Editor(editSession: project.openSync('css.css'))
     autocomplete = new AutocompleteView(cssEditor)


Modify issue write another file

Modify issue write another file

--- src/lib/command.coffee
+++ src/lib/command.coffee
@@ -56,10 +56,10 @@
         if !fs.existsSync path.dirname(outPath)
           fs.mkdirSync path.dirname(outPath)

-        tasks.push do(outPath=outPath, name=file.name)->
+        tasks.push do(outPath=outPath, file=file)->
           (cb)->
             fs.writeFile outPath, file.source, (err)->
-              cb(err, [name , outPath])
+              cb(err, [file.name , outPath])

       async.parallel tasks , (err, pathes)->
         throw err if err
@@ -134,7 +134,7 @@
     )

   program
-    .version('0.2.0')
+    .version('0.3.1')
     .option('-f, --fileId <fileId>', "target gas project fileId")
     .option('-c, --config <path>', "config file path", "./gas-config.json")
     .option('-e, --env <env>', 'the enviroment of target sources', "src")


Update results view spec

Update results view spec

--- spec/results-view-spec.coffee
+++ spec/results-view-spec.coffee
@@ -9,7 +9,7 @@
 waitsForPromise = (fn) -> window.waitsForPromise timeout: 30000, fn

 describe 'ResultsView', ->
-  [pack, projectFindView, resultsView, searchPromise] = []
+  [pack, projectFindView, resultsView, searchPromise, workspaceElement] = []

   getExistingResultsPane = ->
     pane = atom.workspaceView.panes.paneForUri(ResultsPaneView.URI)
@@ -24,13 +24,14 @@
     atom.workspaceView.height(1000)
     atom.project.setPaths([path.join(__dirname, 'fixtures')])
     atom.workspaceView.attachToDom()
+    workspaceElement = atom.views.getView(atom.workspace)
     promise = atom.packages.activatePackage("find-and-replace").then ({mainModule}) ->
       mainModule.createViews()
       {projectFindView} = mainModule
       spy = spyOn(projectFindView, 'confirm').andCallFake ->
         searchPromise = spy.originalValue.call(projectFindView)

-    atom.workspaceView.trigger 'project-find:show'
+    atom.commands.dispatch workspaceElement, 'project-find:show'

     waitsForPromise ->
       promise
@@ -38,7 +39,7 @@
   describe "when the result is for a long line", ->
     it "renders the context around the match", ->
       projectFindView.findEditor.setText('ghijkl')
-      projectFindView.trigger 'core:confirm'
+      atom.commands.dispatch projectFindView[0], 'core:confirm'

       waitsForPromise ->
         searchPromise
@@ -54,11 +55,11 @@
     modifiedDelay = null
     beforeEach ->
       projectFindView.findEditor.setText('ghijkl')
-      modifiedDelay = projectFindView.replaceEditor.getEditor().getBuffer().stoppedChangingDelay
+      modifiedDelay = projectFindView.replaceEditor.getModel().getBuffer().stoppedChangingDelay

     it "renders the replacement when doing a search and there is a replacement pattern", ->
       projectFindView.replaceEditor.setText('cats')
-      projectFindView.trigger 'core:confirm'
+      atom.commands.dispatch projectFindView[0], 'core:confirm'

       waitsForPromise ->
         searchPromise
@@ -71,7 +72,7 @@
         expect(resultsView.find('.replacement').text()).toBe 'cats'

     it "renders the replacement when changing the text in the replacement field", ->
-      projectFindView.trigger 'core:confirm'
+      atom.commands.dispatch projectFindView[0], 'core:confirm'

       waitsForPromise ->
         searchPromise
@@ -102,7 +103,7 @@
   describe "when list is scrollable", ->
     it "adds more operations to the DOM when `scrollBottom` nears the `pixelOverdraw`", ->
       projectFindView.findEditor.setText(' ')
-      projectFindView.trigger 'core:confirm'
+      atom.commands.dispatch projectFindView[0], 'core:confirm'

       waitsForPromise ->
         searchPromise
@@ -137,7 +138,7 @@

         expect(resultsView.prop('scrollHeight')).toBeGreaterThan resultsView.height()
         previousScrollHeight = resultsView.prop('scrollHeight')
-        resultsView.trigger 'core:move-to-bottom'
+        atom.commands.dispatch resultsView[0], 'core:move-to-bottom'
         expect(resultsView.find("li").length).toBe resultsView.getPathCount() + resultsView.getMatchCount()

   describe "arrowing through the list", ->
@@ -151,7 +152,7 @@

       runs ->
         projectFindView.findEditor.setText('items')
-        projectFindView.trigger 'core:confirm'
+        atom.commands.dispatch projectFindView[0], 'core:confirm'
         openHandler = jasmine.createSpy("open handler")
         atom.workspace.onDidOpen openHandler

@@ -163,9 +164,9 @@
         resultsView.selectFirstResult()

         # open something in sample.coffee
-        _.times 3, -> resultsView.trigger 'core:move-down'
+        _.times 3, -> atom.commands.dispatch resultsView[0], 'core:move-down'
         openHandler.reset()
-        resultsView.trigger 'core:confirm'
+        atom.commands.dispatch resultsView[0], 'core:confirm'

       waitsFor ->
         openHandler.callCount == 1
@@ -175,9 +176,9 @@

         # open something in sample.js
         resultsView.focus()
-        _.times 6, -> resultsView.trigger 'core:move-down'
+        _.times 6, -> atom.commands.dispatch resultsView[0], 'core:move-down'
         openHandler.reset()
-        resultsView.trigger 'core:confirm'
+        atom.commands.dispatch resultsView[0], 'core:confirm'

       waitsFor ->
         openHandler.callCount == 1
@@ -192,7 +193,7 @@

       runs ->
         projectFindView.findEditor.setText('items')
-        projectFindView.trigger 'core:confirm'
+        atom.commands.dispatch projectFindView[0], 'core:confirm'

       waitsForPromise ->
         searchPromise
@@ -209,7 +210,7 @@

         # moves down for 13 results
         _.times length - 1, ->
-          resultsView.trigger 'core:move-down'
+          atom.commands.dispatch resultsView[0], 'core:move-down'

           selectedItem = resultsView.find('.selected')

@@ -220,7 +221,7 @@

         # stays at the bottom
         _.times 2, ->
-          resultsView.trigger 'core:move-down'
+          atom.commands.dispatch resultsView[0], 'core:move-down'

           selectedItem = resultsView.find('.selected')

@@ -230,7 +231,7 @@

         # moves up to the top
         _.times length - 1, ->
-          resultsView.trigger 'core:move-up'
+          atom.commands.dispatch resultsView[0], 'core:move-up'

           selectedItem = resultsView.find('.selected')

@@ -241,7 +242,7 @@

         # stays at the top
         _.times 2, ->
-          resultsView.trigger 'core:move-up'
+          atom.commands.dispatch resultsView[0], 'core:move-up'

           selectedItem = resultsView.find('.selected')

@@ -252,7 +253,7 @@
     describe "when there are a list of items", ->
       beforeEach ->
         projectFindView.findEditor.setText('items')
-        projectFindView.trigger 'core:confirm'
+        atom.commands.dispatch projectFindView[0], 'core:confirm'
         waitsForPromise -> searchPromise
         runs -> resultsView = getResultsView()

@@ -261,7 +262,7 @@
         resultsView.find('.selected').removeClass('selected')
         resultsView.find('.path:eq(0) .search-result:first').addClass('selected')

-        resultsView.trigger 'core:move-left'
+        atom.commands.dispatch resultsView[0], 'core:move-left'

         selectedItem = resultsView.find('.selected')
         expect(selectedItem).toHaveClass('collapsed')
@@ -272,7 +273,7 @@
         resultsView.find('.selected').removeClass('selected')
         resultsView.find('.path:eq(0)').addClass('selected').addClass('collapsed')

-        resultsView.trigger 'core:move-right'
+        atom.commands.dispatch resultsView[0], 'core:move-right'

         selectedItem = resultsView.find('.selected')
         expect(selectedItem).toHaveClass('search-result')
@@ -282,13 +283,13 @@
         it "doesnt error when the user arrows down", ->
           resultsView.find('.selected').removeClass('selected')
           expect(resultsView.find('.selected')).not.toExist()
-          resultsView.trigger 'core:move-down'
+          atom.commands.dispatch resultsView[0], 'core:move-down'
           expect(resultsView.find('.selected')).toExist()

         it "doesnt error when the user arrows up", ->
           resultsView.find('.selected').removeClass('selected')
           expect(resultsView.find('.selected')).not.toExist()
-          resultsView.trigger 'core:move-up'
+          atom.commands.dispatch resultsView[0], 'core:move-up'
           expect(resultsView.find('.selected')).toExist()

       describe "when there are collapsed results", ->
@@ -297,7 +298,7 @@
           resultsView.find('.path:eq(0) .search-result:last').addClass('selected')
           resultsView.find('.path:eq(1)').view().expand(false)

-          resultsView.trigger 'core:move-down'
+          atom.commands.dispatch resultsView[0], 'core:move-down'

           selectedItem = resultsView.find('.selected')
           expect(selectedItem).toHaveClass('path')
@@ -308,7 +309,7 @@
           resultsView.find('.path:eq(1) .search-result:first').addClass('selected')
           resultsView.find('.path:eq(0)').view().expand(false)

-          resultsView.trigger 'core:move-up'
+          atom.commands.dispatch resultsView[0], 'core:move-up'

           selectedItem = resultsView.find('.selected')
           expect(selectedItem).toHaveClass('path')
@@ -317,7 +318,7 @@
   describe "when the results view is empty", ->
     it "ignores core:confirm events", ->
       projectFindView.findEditor.setText('thiswillnotmatchanythingintheproject')
-      projectFindView.trigger 'core:confirm'
+      atom.commands.dispatch projectFindView[0], 'core:confirm'

       waitsForPromise ->
         searchPromise
@@ -335,7 +336,7 @@

       runs ->
         projectFindView.findEditor.setText('items')
-        projectFindView.trigger 'core:confirm'
+        atom.commands.dispatch projectFindView[0], 'core:confirm'

       waitsForPromise ->
         searchPromise
@@ -345,6 +346,6 @@
         resultsView.selectFirstResult()

     it "copies the selected line onto the clipboard", ->
-      _.times 2, -> resultsView.trigger 'core:move-down'
-      resultsView.trigger 'core:copy'
+      _.times 2, -> atom.commands.dispatch resultsView[0], 'core:move-down'
+      atom.commands.dispatch resultsView[0], 'core:copy'
       expect(atom.clipboard.read()).toBe '    return items if items.length <= 1'


增加批量解除关系和替换现有关系的接口

增加批量解除关系和替换现有关系的接口

--- src/biz/asset_issue_relation.coffee
+++ src/biz/asset_issue_relation.coffee
@@ -5,9 +5,30 @@
 _store = require('./store')
 _BaseEntity = require './BaseEntity'
 _schema = require '../schema/asset_issue_relation.json'
+_async = require 'async'

 #定义一个Project类
 class AssetIssueRelation extends _BaseEntity
-  #empty
+  #解除某个issue下与asset的所有关系
+  unlinkAll: (member, issue_id, callback)->
+    this.entity()
+    .where('issue_id', issue_id)
+    .del()
+    .then ()-> callback null
+
+  #替换掉现有的关系
+  replaceAll: (member, assets, issue_id, callback)->
+    self = this
+    this.unlinkAll member, issue_id, (err)->
+      count = 0
+      #批量插入数据
+      _async.whilst(
+        ()-> count < assets.length
+      ,
+        (done)->
+          relation_data = issue_id: issue_id, asset_id: assets[count++]
+          self.save member, relation_data, done
+        ()-> callback()
+      )

 module.exports = new AssetIssueRelation(_schema)


:penguin: Quit when all windows are closed.

--- src/browser/atom-application.coffee
+++ src/browser/atom-application.coffee
@@ -196,7 +196,7 @@
     @openPathOnEvent('application:open-your-stylesheet', 'atom://.atom/stylesheet')

     app.on 'window-all-closed', ->
-      app.quit() if process.platform is 'win32'
+      app.quit() if process.platform in ['win32', 'linux']

     app.on 'will-quit', =>
       @killAllProcesses()


Fixes template file path

Fixes template file path

--- src/lib/wrapper.coffee
+++ src/lib/wrapper.coffee
@@ -1,5 +1,5 @@
 handlebars = require 'handlebars'
-templateStr = require('fs').readFileSync 'src/template/section.hbt', 'utf8'
+templateStr = require('fs').readFileSync __dirname + '/../template/section.hbt', 'utf8'

 module.exports = (section) ->
   result = ''


log stderr when running docker-compose

log stderr when running docker-compose

--- lib/compose/actions.coffee
+++ lib/compose/actions.coffee
@@ -49,6 +49,7 @@
   else
     console.log 'success', cmd, args
     spawned.stdout.on 'data', stdoutCb
+    spawned.stderr.on 'data', (data) -> console.log 'stderr', data.toString()
     spawned.stdout.on 'end', exitCb

 ensureMkdir = (scriptDir, success) ->


Return early in FakeLinesYardstick for unrendered rows

This allows us to have a more consistent behavior with the real LinesYardstick.

--- spec/fake-lines-yardstick.coffee
+++ spec/fake-lines-yardstick.coffee
@@ -7,6 +7,7 @@

   prepareScreenRowsForMeasurement: ->
     @presenter.getPreMeasurementState()
+    @screenRows = new Set(@presenter.getScreenRows())

   getScopedCharacterWidth: (scopeNames, char) ->
     @getScopedCharacterWidths(scopeNames)[char]
@@ -33,6 +34,8 @@
     top = targetRow * @model.getLineHeightInPixels()
     left = 0
     column = 0
+
+    return {top, left: 0} unless @screenRows.has(screenPosition.row)

     iterator = @model.tokenizedLineForScreenRow(targetRow).getTokenIterator()
     while iterator.next()


overwrite for fixtures store query

overwrite for fixtures store query

[#95231438]

--- spec/javascripts/setup.js.coffee
+++ spec/javascripts/setup.js.coffee
@@ -1,4 +1,16 @@
-Review.ApplicationAdapter = DS.FixtureAdapter.extend({simulateRemoteResponse: false})
-
+Review.ApplicationAdapter = DS.FixtureAdapter.extend({simulateRemoteResponse: true})
+Review.Store = DS.Store.extend
+  revision: 12
+  adapter:  DS.FixtureAdapter
+DS.FixtureAdapter.reopen
+  queryFixtures: (records, query, type) ->
+    records.filter (record) ->
+      for key of query
+        if !query.hasOwnProperty(key)
+          continue
+        value = query[key]
+        if record[key] != value
+          return false
+       true
 Review.setupForTesting()
 Review.injectTestHelpers()


Add ACL Test

Add ACL Test

--- spec/lib/models-generator.coffee
+++ spec/lib/models-generator.coffee
@@ -148,9 +148,11 @@
             assert generated.names.length is 4
             assert typeof generated.config is 'object'

-    describe 'generate, give the relation define', ->
-
-        before ->
+    describe 'generate, when give the relation define, and owner permission is read only', ->
+
+        before ->
+
+            ownerPermission = 'r'

             define =
                 staff:
@@ -169,7 +171,7 @@
                             type: 'hasMany', model: 'staff', foreignKey: 'staffId'
                 job:
                     aclType:
-                        owner: 'r'
+                        owner: ownerPermission
                     name: 'job',
                     plural: 'job',
                     base: 'PersistedModel',
@@ -226,3 +228,60 @@
                 assert.deepEqual acls.splice(8, 4), acl

                 done()
+
+    describe 'generate, when give the relation define, and owner permission is read-write', ->
+
+        before ->
+
+            ownerPermission = 'rw'
+
+            define =
+                staff:
+                    aclType:
+                        owner: 'rwx'
+                    name: 'staff'
+                    plural: 'staff'
+                    base: 'User'
+                    idInjection: true
+                    properties: {}
+                    validations: []
+                    relations:
+                        'job-with-staffId':
+                            type: 'hasMany', model: 'staff', foreignKey: 'staffId'
+                        job:
+                            type: 'hasMany', model: 'staff', foreignKey: 'staffId'
+                job:
+                    aclType:
+                        owner: ownerPermission
+                    name: 'job',
+                    plural: 'job',
+                    base: 'PersistedModel',
+                    idInjection: true,
+                    properties: {},
+                    validations: [],
+                    relations:
+                        staff:
+                            type: 'belongsTo', model: 'staff', foreignKey: 'staffId'
+
+
+            @generator = new ModelsGenerator(define)
+            @generator.destinationDir = __dirname + '/d'
+            @generator.modelConfigGenerator.destinationPath = __dirname + '/d'
+            fs.mkdirsSync __dirname + '/d'
+
+        after ->
+            fs.removeSync __dirname + '/d'
+
+        it 'generate JSON file does not include related models', (done) ->
+
+            @generator.generate()
+
+            fs.readFile __dirname + '/d/staff.json', 'utf8', (err, data) ->
+
+                acls = JSON.parse(data).acls
+
+                acl = []
+
+                assert.deepEqual acls.splice(8, 4), acl
+
+                done()


:green_heart: Fix broken travis test

:green_heart: Fix broken travis test

The component now call `setScrollTop` on its `presenter` and no longer
on the `TextEditorElement`.

--- spec/minimap-element-spec.coffee
+++ spec/minimap-element-spec.coffee
@@ -265,12 +265,12 @@

       describe 'using the mouse scrollwheel over the minimap', ->
         beforeEach ->
-          spyOn(editor, 'setScrollTop').andCallFake ->
+          spyOn(editorElement.component.presenter, 'setScrollTop').andCallFake ->

           mousewheel(minimapElement, 0, 15)

         it 'relays the events to the editor view', ->
-          expect(editor.setScrollTop).toHaveBeenCalled()
+          expect(editorElement.component.presenter.setScrollTop).toHaveBeenCalled()

       describe 'pressing the mouse on the minimap canvas (without scroll animation)', ->
         beforeEach ->


Removed lib/default-handler.coffee.

Removed lib/default-handler.coffee.

--- lib/default-handler.coffee
+++ lib/default-handler.coffee
@@ -1,10 +0,0 @@
-{ replace } = require 'react-router-redux'
-
-createHandler = require './create-router-handler.coffee'
-renderTemplate = require './render-template.coffee'
-
-module.exports = createHandler (req, res, config) ->
-  { props } = config
-  if props
-    renderTemplate config
-      .then res.send.bind res


Remove unused spec-helper-platform

Remove unused spec-helper-platform

--- spec/spec-helper-platform.coffee
+++ spec/spec-helper-platform.coffee
@@ -1,35 +0,0 @@
-path = require 'path'
-fs = require 'fs-plus'
-
-## Platform specific helpers
-module.exports =
-  # Public: Returns true if being run from within Windows
-  isWindows: ->
-    !!process.platform.match /^win/
-
-  # Public: Some files can not exist on Windows filesystems, so we have to
-  # selectively generate our fixtures.
-  #
-  # Returns nothing.
-  generateEvilFiles: ->
-    evilFilesPath = path.join(__dirname, 'fixtures', 'evil-files')
-    fs.removeSync(evilFilesPath) if fs.existsSync(evilFilesPath)
-    fs.mkdirSync(evilFilesPath)
-
-    if @isWindows()
-      filenames = [
-        "a_file_with_utf8.txt"
-        "file with spaces.txt"
-        "utfa\u0306.md"
-      ]
-    else
-      filenames = [
-        "a_file_with_utf8.txt"
-        "file with spaces.txt"
-        "goddam\nnewlines"
-        "quote\".txt"
-        "utfa\u0306.md"
-      ]
-
-    for filename in filenames
-      fs.writeFileSync(path.join(evilFilesPath, filename), 'evil file!', flag: 'w')


better messages on Script tag config

better messages on Script tag config

--- src/client/script-tag-config.coffee
+++ src/client/script-tag-config.coffee
@@ -4,8 +4,10 @@
 dasherize = (str)->
   str.replace /[A-Z]/, (c) ->  "-" + c.toLowerCase()

+
+httpsRegex = /^https:|^\/\//
+
 valid =
-
   regex: (reg)-> (val, key)->
     check = reg.test(val)
     if !check
@@ -13,15 +15,13 @@
     check

   https: (val, key)->
-    check = /^https:|^\/\//.test(val)
-    return check if document.location.protocol == 'https:' and check
+    check = httpsRegex.test(val)
+    return true if check
     logger.warn("[Hull.init] #{key} should be loaded via https. Current value is ", val)
-    check
-    true
+    false

 transform =
-
   url: (url, key)->
     if url && url.length > 0
       a = document.createElement('a')
@@ -53,21 +53,24 @@
     transform: transform.url
     validation: valid.regex(new RegExp("^" + document.location.origin + "/"))
   },
-  debug:   { default: false, transform: transform.bool },
-  verbose: { default: false, transform: transform.bool },
-  embed: { default: true, transform: transform.bool },
-  autoStart: { default: true, transform: transform.bool },
-  accessToken: { default: null }
-  customerId: { default: null }
+  debug       : { default: false, transform: transform.bool },
+  verbose     : { default: false, transform: transform.bool },
+  embed       : { default: true,  transform: transform.bool },
+  autoStart   : { default: true,  transform: transform.bool },
+  accessToken : { default: null }
+  customerId  : { default: null }
 }
+
+getAttribute = (el, k)->
+  el.getAttribute(dasherize(k)) || el.getAttribute(k)

 getParamValue = (el, param, key)->
   keys = [key].concat(param.altKeys || [])
   transform = param.transform || (o)-> o
   value = _.reduce(keys, (ret, k)->
     if ret == null
-      value = transform((el.getAttribute(dasherize(k)) || el.getAttribute(k)), key)
-      valid = value? && (!param.validation  || param.validation(value, k))
+      value = transform(getAttribute(el, k), key)
+      valid = value? && (!param.validation  || param.validation(value, k, el))
       ret = value if valid
     ret
   , null)
@@ -81,7 +84,7 @@
   return unless hull_js_sdk

   # Map known config values to Hull init
-  _.reduce initParams, (config, param, key)->
+  out = _.reduce initParams, (config, param, key)->
     return unless config
     value = getParamValue(hull_js_sdk, param, key)
     if value?
@@ -90,3 +93,5 @@
       config = false
     config
   , {}
+  logger.error("[Hull.init] jsUrl NEEDS be loaded via https if orgUrl is https ") if httpsRegex.test(out.orgUrl) and not httpsRegex.test(out.jsUrl)
+  out


Add support for language-markdown

Add support for language-markdown

`language-markdown` uses `text.md`, rather than `source.gfm`, so this adds the required scope for it to work in that syntax.

--- lib/emojis-provider.coffee
+++ lib/emojis-provider.coffee
@@ -4,7 +4,7 @@
 emoji = require('emoji-images')

 module.exports =
-  selector: '.source.gfm, .text.html, .text.slim, .text.plain, .text.git-commit, .comment, .string, .source.emojicode'
+  selector: '.source.gfm, .text.md .text.html, .text.slim, .text.plain, .text.git-commit, .comment, .string, .source.emojicode'

   wordRegex: /::?[\w\d_\+-]+$/
   emojiFolder: 'atom://autocomplete-emojis/node_modules/emoji-images/pngs'


ListView: ::removeItemByData fixed

ListView: ::removeItemByData fixed

--- src/components/list/listview.coffee
+++ src/components/list/listview.coffee
@@ -61,19 +61,27 @@

     if index?
       @emit 'ItemWasRemoved', @items[index], index
-      item = @items.splice index, 1
-      item[0].destroy()
-      return
+      [item] = @items.splice index, 1
+      item.destroy()
+      return yes
     else
       {dataPath} = @getOptions()
-      # fix it w/ indexOf
+
+      destroy = (item, index) =>
+        @emit 'ItemWasRemoved', item, i
+        @items.splice i, 1
+        item.destroy()
+        return yes
+
       for item, i in @items
-        if itemInstance.getId() is item.getId() or \
-           itemData?[dataPath] is item.getData()?[dataPath]
-          @emit 'ItemWasRemoved', item, i
-          @items.splice i, 1
-          item.destroy()
-          return
+        if itemInstance
+          if itemInstance.getId() is item.getId()
+            destroy item, i
+            break
+        else if itemData
+          if itemData?[dataPath] is item.getData()?[dataPath]
+            destroy item, i
+            break

   removeItemByData: (itemData) -> @removeItem null, itemData


Rephrase 'hold is not possible' message Use a friendlier message, suggested by Melissa of tnrdlib.bc.ca

--- src/opac/edit_hold.coffee
+++ src/opac/edit_hold.coffee
@@ -267,13 +267,8 @@
 					else
 						hide_form.call @
 						@publish 'prompt', [
-							'The hold is not possible'
-							'''
-							The server doesn't say why.
-							It could be because the library you wanted to pick the item up from
-							is not part of the same system the item is from.
-							It could also be a number of other reasons.
-							'''
+							'This title is not eligible for a hold.'
+							'Please ask your friendly library staff for assistance.'
 						]

 			return false


don't generate config-dev.php - ProcessWire will not complete install

--- app/generator.coffee
+++ app/generator.coffee
@@ -95,8 +95,6 @@
 				shell.ls('-A', "site/templates/\!root/*").forEach (file) ->
 					shell.mv '-f', file, '.'
 				shell.rm '-rf', "site/templates/\!root"
-			at dest('src/site'), =>
-				shell.cp 'config.php', 'config-dev.php'

 			@log.ok('OK')


Using process.mainModule.filename instead of passing __filename

--- command.coffee
+++ command.coffee
@@ -53,7 +53,7 @@
 # Run `coffee` by parsing passed options and determining what action to take.
 # Many flags cause us to divert before compiling anything. Flags passed after
 # `--` will be passed verbatim to your script as arguments in `process.argv`
-exports.run = (coffeePath) ->
+exports.run = ->
   parseOptions()
   return forkNode()                      if opts.nodejs
   return usage()                         if opts.help
@@ -67,7 +67,7 @@
     opts.literals = sources.splice(1).concat opts.literals
   process.ARGV = process.argv = process.argv.slice(0, 2).concat opts.literals
   process.argv[0] = 'coffee'
-  process.execPath = coffeePath
+  process.execPath = process.mainModule.filename
   compileScripts()

 # Asynchronously read in each CoffeeScript in a list of source files and