Update jsdoc documentation

Update jsdoc documentation

--- UoACalendarClient.coffee
+++ UoACalendarClient.coffee
@@ -7,7 +7,7 @@
   * UoACalendarClient instantiation.
   *
   * @name UoACalendarClient#DEFAULT_HOST
-  * @type String
+  * @type string
   * @default sitcalprd01.its.auckland.ac.nz
   ###

@@ -17,7 +17,7 @@
   * The default TCP port used by the client if no `port` variable is specified during UoACalendarClient instantiation.
   *
   * @name UoACalendarClient#DEFAULT_PORT
-  * @type Number
+  * @type number
   * @default 345
   ###

@@ -29,10 +29,21 @@
   * that can contain one or more of the following fields.
   *
   * @class
-  * @param {String} apiToken - Sets the user's API token used for authentication purposes.
-  * @param {String=} host - Authentication server to which the client will connect. Should *NOT* include the URL schema as it defaults to `http`. Defaults to `DEFAULT_HOST`.
-  * @param {Number=} port - TCP port from the host to which the client will connect. Defaults to `DEFAULT_PORT`.
+  * @param {string} apiToken - Sets the user's API token used for authentication purposes.
+  * @param {string=} host - Authentication server to which the client will connect. Should *NOT* include the URL schema as it defaults to `http`. Defaults to `DEFAULT_HOST`.
+  * @param {number=} port - TCP port from the host to which the client will connect. Defaults to `DEFAULT_PORT`.
   * @alias UoACalendarClient
+  *
+  * @example
Minimal example of how to instantiate a UoACalendarClient. Default host and port used.

+  * var client = new UoACalendarClient("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJvcmlnX2lhdCI6MTQyMjQ5ODk0OSwiZXhwIjoxNDI" +
+  *                                     "yNDk5MjQ5LCJ1c2VyX2lkIjoyLCJ1c2VybmFtZSI6ImRldmVsb3BlciIsImVtYWlsIjoidGVzdEBhdWN" +
+  *                                     "rbGFuZC5hYy5ueiJ9.7jLkEBovT2HvT2noL4xdIhddaY8wpZpEVYEDHnnNm1Y");
+  *
+  * @example
Example where host and port specified.

+  * var client = new UoACalendarClient("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJvcmlnX2lhdCI6MTQyMjQ5ODk0OSwiZXhwIjoxNDI" +
+  *                                     "yNDk5MjQ5LCJ1c2VyX2lkIjoyLCJ1c2VybmFtZSI6ImRldmVsb3BlciIsImVtYWlsIjoidGVzdEBhdWN" +
+  *                                     "rbGFuZC5hYy5ueiJ9.7jLkEBovT2HvT2noL4xdIhddaY8wpZpEVYEDHnnNm1Y",
+  *                                     "sitcalprd01.its.auckland.ac.nz", 345);
   ###

   constructor: (apiToken, host, port) ->
@@ -43,7 +54,10 @@
   ###*
   * Return host used by UoACalendarClient instance.
   *
-  * @returns {String}
+  * @returns {string}
+  *
+  * @example
+  * var host = client.getHost();
   ###

   getHost: () ->
@@ -52,7 +66,10 @@
   ###*
   * Return port used by UoACalendarClient instance.
   *
-  * @returns {Number}
+  * @returns {number}
+  *
+  * @example
+  * var host = client.getPort();
   ###

   getPort: () ->
@@ -62,6 +79,9 @@
   * Return apiToken used by UoACalendarClient instance.
   *
   * @returns {string}
+  *
+  * @example
+  * var apiToken = client.getApiToken();
   ###

   getApiToken: () ->
@@ -70,11 +90,11 @@
   ###*
   * Sends http request to calendar backend
   *
-  * @param {String} path
-  * @param {String} method
-  * @param {String} data
-  * @param {String} onSuccess
-  * @param {String} onError
+  * @param {string} path
+  * @param {string} method
+  * @param {string} data
+  * @param {string} onSuccess
+  * @param {string} onError
   * @private
   ###

@@ -145,206 +165,505 @@
   ###*
   * Retrieve full list of calendars
   *
-  * @param {UoACalendarClient~listCalendarsSuccess} onSuccess - Success callback function
-  * @param {UoACalendarClient~listCalendarsError} onError - Error callback function
+  * @param {onListCalendarsSuccess} onSuccess - Success callback function
+  * @param {onListCalendarsError} onError - Error callback function
+  * @example
+  * client.listCalendars(
+  *   function(res, data) // onSuccess callback
+  *   {
+  *     console.log(res);
+  *     console.log(data);
+  *   },
+  *   function(res, data) // onError callback
+  *   {
+  *     console.log(res);
+  *     console.log(data);
+  *   }
+  * );
   ###

   listCalendars: (onSuccess, onError) ->
     @sendRequest('/calendars/', 'GET', 0, onSuccess, onError)

   ###*
-  * @callback UoACalendarClient~listCalendarsSuccess
-  * @param {res} Response
-  * @param {data} Deserialized data
-  ###
-
-  ###*
-  * @callback UoACalendarClient~listCalendarsError
-  * @param {res} Response
-  * @param {data} ?
+  * Callback, returns an array of calendars.
+  *
+  * @callback onListCalendarsSuccess
+  * @param {HttpResponse} res - HTTP response object.
+  * @param {ErrorMessage} data - An array of calendar JSON objects, with name and id key value pairs.
+  *
+  * @example
Example output

+  * res: {"offset":5216,"readable":true,"_events":{},"statusCode":200,"headers":{"content-type":"application/json"}}
+  * data: [{"name":"Public holidays","id":1},{"name":"CompSci exam dates","id":2},{"name":"Movie release dates","id":3}]
+  ###
+
+  ###*
+  * Callback function, executed when adding calendar failed.
+  *
+  * @callback onListCalendarsError
+  * @param {HttpResponse} res - HTTP response
+  * @param {ErrorMessage} data - Error message.
+  *
+  * @example
Example output

+  * todo: insert example
   ###

   ###*
   * Retrieve a particular calendar
-  * @param {Number} id - id of the calendar
-  * @param {UoACalendarClient~getCalendarSuccess} onSuccess - Success callback function
-  * @param {UoACalendarClient~getCalendarError} onError - Error callback function
+  * @param {number} id - id of the calendar
+  * @param {onGetCalendarSuccess} onSuccess - Success callback function
+  * @param {onGetCalendarError} onError - Error callback function
+  * @example
+  * var calendarId = 1;
+  * client.getCalendar(calendarId,
+  *   function(res, data) // onSuccess callback
+  *   {
+  *     console.log(res);
+  *     console.log(data);
+  *   },
+  *   function(res, data) // onError callback
+  *   {
+  *     console.log(res);
+  *     console.log(data);
+  *   }
+  * );
   ###

   getCalendar: (id, onSuccess, onError) ->
     @sendRequest('/calendars/' + id + '/', 'GET', 0, onSuccess, onError)

   ###*
-  * @callback UoACalendarClient~getCalendarSuccess
-  * @param {res} Response
-  * @param {data} Deserialized data
-  ###
-
-  ###*
-  * @callback UoACalendarClient~getCalendarError
-  * @param {res} Response
-  * @param {data} ?
+  * Callback, returns particular calendar including its name and id. TODO: return custom fields here.
+  *
+  * @callback onGetCalendarSuccess
+  * @param {HttpResponse} res - HTTP response
+  * @param {Object.<{name: string, id: number}>} data - calendar with name and id
+  *
+  * @example
Example output

+  * res: {"offset":5216,"readable":true,"_events":{},"statusCode":200,"headers":{"content-type":"application/json"}}
+  * data: {"name":"My Calendar","id":84}
+  ###
+
+  ###*
+  * @callback onGetCalendarError
+  * @param {HttpResponse} res - HTTP response
+  * @param {ErrorMessage} data - detail of what went wrong
+  *
+  * @example
Item doesn't exist

+  * res: {"offset":22,"readable":true,"_events":{},"statusCode":404,"headers":{"content-type":"application/json"}}
+  * data: {detail: "Not found"}
+  *
+  * @example
No permission to access item

+  * res: {"offset":63,"readable":true,"_events":{},"statusCode":403,"headers":{"content-type":"application/json"}}
+  * data: {detail: "You do not have permission to perform this action."}
   ###

   ###*
   * Add a new calendar providing the new calendar's name
-  * @param {String} name - Name of calendar
-  * @param {UoACalendarClient~addCalendarSuccess} onSuccess - Success callback function
-  * @param {UoACalendarClient~addCalendarError} onError - Error callback function
+  * @param {string} name - Name of calendar
+  * @param {onAddCalendarSuccess} onSuccess - Success callback function
+  * @param {onAddCalendarError} onError - Error callback function
+  *
+  * @example
+  * var name = "My calendar";
+  * client.addCalendar(name,
+  *   function(res, data) // onSuccess callback
+  *   {
+  *     console.log(res);
+  *     console.log(data);
+  *   },
+  *   function(res, data) // onError callback
+  *   {
+  *     console.log(res);
+  *     console.log(data);
+  *   }
+  * );
   ###

   addCalendar: (name, onSuccess, onError) ->
     @sendRequest('/calendars/', 'POST', {name: name}, onSuccess, onError)

   ###*
-  * @callback UoACalendarClient~addCalendarSuccess
-  * @param {res} Response
-  * @param {data} Deserialized data
-  ###
-
-  ###*
-  * @callback UoACalendarClient~addCalendarError
-  * @param {res} Response
-  * @param {data} ?
+  * Callback function, executed when calendar successfully added.
+  *
+  * @callback onAddCalendarSuccess
+  * @param {HttpResponse} res - HTTP response
+  * @param {Array.<{name: string, id: number}>} data - An array of calendar JSON objects, with name and id key value pairs.
+  *
+  * @example
Example output

+  * res: {"offset":26,"readable":true,"_events":{},"statusCode":201,"headers":{"content-type":"application/json"}}
+  * data: {"name":"My calendar","id":194}
+  ###
+
+  ###*
+  * Callback function, executed when adding calendar failed.
+  *
+  * @callback onAddCalendarError
+  * @param {HttpResponse} res - HTTP response.
+  * @param {ErrorMessage} data - Error message.
+  *
+  * @example
Example output

+  * todo: insert example
   ###

   ###*
   * Delete an existing calendar given its id
-  * @param {Number} id - id of calendar
-  * @param {UoACalendarClient~deleteCalendarSuccess} onSuccess - Success callback function
-  * @param {UoACalendarClient~deleteCalendarError} onError - Error callback function
+  * @param {number} id - id of calendar
+  * @param {onDeleteCalendarSuccess} onSuccess - Success callback function
+  * @param {onDeleteCalendarError} onError - Error callback function
+  *
+  * @example
+  * var calendarId = 1;
+  * client.deleteCalendar(calendarId,
+  *   function(res, data) // onSuccess callback
+  *   {
+  *     console.log(res);
+  *     console.log(data);
+  *   },
+  *   function(res, data) // onError callback
+  *   {
+  *     console.log(res);
+  *     console.log(data);
+  *   }
+  * );
   ###

   deleteCalendar: (id, onSuccess, onError) ->
     @sendRequest('/calendars/' + id + '/', 'DELETE', {}, onSuccess, onError)

   ###*
-  * @callback UoACalendarClient~deleteCalendarSuccess
-  * @param {res} Response
-  * @param {data} Deserialized data
-  ###
-
-  ###*
-  * @callback UoACalendarClient~deleteCalendarError
-  * @param {res} Response
-  * @param {data} ?
+  * Callback, executed when calendar successfully deleted.
+  *
+  * @callback onDeleteCalendarSuccess
+  * @param {HttpResponse} res - HTTP response
+  * @param {Object.<{}>} data - Empty JSON object
+  *
+  * @example
Example output

+  * res: {"offset":0,"readable":true,"_events":{},"statusCode":204,"headers":{"content-type":"text/plain; charset=UTF-8"}}
+  * data: {}
+  ###
+
+  ###*
+  * Callback function, executed when delete calendar failed.
+  *
+  * @callback onDeleteCalendarError
+  * @param {HttpResponse} res - HTTP response
+  * @param {ErrorMessage} data - Error message.
+  *
+  * @example
Example output

+  * todo: insert example
   ###

   ###*
   * Retrieve the full list of events of an existing calendar given its id
-  * @param {Number} calendarId - Id of calendar
-  * @param {UoACalendarClient~listEventsSuccess} onSuccess - Success callback function
-  * @param {UoACalendarClient~listEventsError} onError - Error callback function
+  * @param {number} calendarId - Id of calendar
+  * @param {onListEventsSuccess} onSuccess - Success callback function
+  * @param {onListEventsError} onError - Error callback function
+  *
+  * @example
+  * var calendarId = 1;
+  * client.listEvents(calendarId,
+  *   function(res, data) // onSuccess callback
+  *   {
+  *     console.log(res);
+  *     console.log(data);
+  *   },
+  *   function(res, data) // onError callback
+  *   {
+  *     console.log(res);
+  *     console.log(data);
+  *   }
+  * );
   ###

   listEvents: (calendarId, onSuccess, onError) ->
     @sendRequest('/calendars/' + calendarId + '/events/', 'GET', 0, onSuccess, onError)

   ###*
-  * @callback UoACalendarClient~listEventsSuccess
-  * @param {res} Response
-  * @param {data} Deserialized events dictionary
-  ###
-
-  ###*
-  * @callback UoACalendarClient~listEventsError
-  * @param {res} Response
-  * @param {data} ?
+  * Callback, executed when list events successful, data parameter contains list of events.
+  *
+  * @callback onListEventsSuccess
+  * @param {HttpResponse} res - HTTP response
+  * @param {Array.<{id:number, title: string, description: string, start: string, end: string, allDay: boolean, url: string, status: string, reminder: string, todo: boolean, location: string, summary: string, lastUpdate: string}>} data - An array of events
+  *
+  * @example
Example output

+  * res: {"offset":2,"readable":true,"_events":{},"statusCode":200,"headers":{"content-type":"application/json"}}
+  * data: [{"id":3,"title":"The Force Awakens","description":"","start":"2014-12-18T21:40:00Z","end":"2014-12-18T22:00:00Z",
+  *   "allDay":false,"url":"http://www.starwars.com/the-force-awakens/trailers/","status":null,"reminder":null,
+  *   "todo":false,"location":null,"summary":null,"lastUpdate":"2014-12-16T21:26:04Z"},
+  *  {"id":5,"title":"HoloLens","description":"","start":"2014-12-17T04:00:00Z","end":"2014-12-17T05:00:00Z",
+  *   "allDay":false,"url":"http://www.microsoft.com/microsoft-hololens/en-us","status":null,"reminder":null,
+  *   "todo":false,"location":null,"summary":null,"lastUpdate":"2014-12-18T20:54:58Z"}]
+  ###
+
+  ###*
+  * Callback function, executed when list events failed.
+  *
+  * @callback onListEventsError
+  * @param {HttpResponse} res - HTTP response
+  * @param {ErrorMessage} data - Error message.
+  *
+  * @example
Example output

+  * todo: insert example
   ###

   ###*
   * Add an event to a particular calendar. Your own data fields can be added to the JSON event object, for
   * example, if you wanted to add an 'exercise' type to an event do the following: {"title": "My calendar", "exercise": "Run"}
   *
-  * @param {Number} calendarId - Id of calendar
+  * @param {number} calendarId - Id of calendar
   * @param {Object} event - Event JSON object
-  * @param {String} event.title - Event title
-  * @param {String=} event.description - Event description
-  * @param {String=} event.location - Event location
-  * @param {String=} event.summary - Event summary
-  * @param {Date=} event.start - Event start date & time
+  * @param {string} event.title - Event title
+  * @param {string=} event.description - Event description
+  * @param {string=} event.location - Event location
+  * @param {string=} event.summary - Event summary
+  * @param {Date} event.start - Event start date & time
   * @param {Date=} event.end - Event end date & time
-  * @param {String=} event.status - Event status
-  * @param {String=} event.reminder - Event reminder
-  * @param {Boolean=} event.todo - Whether event still to be done
-  * @param {Boolean=} event.allDay - Whether event occurs all day
-  * @param {String=} event.url - Event url
-  * @param {UoACalendarClient~addEventSuccess} onSuccess - Success callback function
-  * @param {UoACalendarClient~addEventError} onError - Error callback function
+  * @param {string=} event.status - Event status
+  * @param {string=} event.reminder - Event reminder
+  * @param {boolean=} event.todo - Whether event still to be done
+  * @param {boolean=} event.allDay - Whether event occurs all day
+  * @param {string=} event.url - Event url
+  * @param {onAddEventSuccess} onSuccess - Success callback function
+  * @param {onAddEventError} onError - Error callback function
+  *
+  * @example
Minimal example of how to add an event to a calendar, only title is mandatory.

+  * var calendarId = 1;
+  * var event = {"title": "Auckland Marathon", "start": new Date(2016, 11, 1, 6, 0, 0)};
+  * client.addEvent(calendarId, event,
+  *   function(res, data) // onSuccess callback
+  *   {
+  *     console.log(res);
+  *     console.log(data);
+  *   },
+  *   function(res, data) // onError callback
+  *   {
+  *     console.log(res);
+  *     console.log(data);
+  *   }
+  * );
+  *
+  * @example
Complete example of how to add an event to a calendar.

+  * var calendarId = 1;
+  * var event = {"title": "Auckland Marathon", "description": "Running marathon today",
+  *              "location": "King Edward Parade, Devonport", "summary": "Marathon",
+  *              "start": new Date(2016, 11, 1, 6, 0, 0), "end": new Date(2016, 11, 1, 8, 30, 0),
+  *              "status": "active", "todo": true, "addDay": false, "url": "https://www.aucklandmarathon.co.nz/"};
+  * client.addEvent(calendarId, event,
+  *   function(res, data) // onSuccess callback
+  *   {
+  *     console.log(res);
+  *     console.log(data);
+  *   },
+  *   function(res, data) // onError callback
+  *   {
+  *     console.log(res);
+  *     console.log(data);
+  *   }
+  * );
+  *
+  * @example
Example of adding an event with a custom data field. The data field 'exercise' is custom.

+  * var calendarId = 1;
+  * var event = {"title": "Auckland Marathon", "start": new Date(2016, 11, 1, 6, 0, 0), "exercise": "run"};
+  * client.addEvent(calendarId, event,
+  *   function(res, data) // onSuccess callback
+  *   {
+  *     console.log(res);
+  *     console.log(data);
+  *   },
+  *   function(res, data) // onError callback
+  *   {
+  *     console.log(res);
+  *     console.log(data);
+  *   }
+  * );
   ###

   addEvent: (calendarId, event, onSuccess, onError) ->
     @sendRequest('/calendars/' + calendarId + '/events/', 'POST', event, onSuccess, onError)

   ###*
-  * Event object
-  *
-  * @class Event
-  ###
-
-  ###*
-  * @callback UoACalendarClient~addEventSuccess
-  * @param {res} Response
-  * @param {data} Deserialized events dictionary
-  ###
-
-  ###*
-  * @callback UoACalendarClient~addEventError
-  * @param {res} Response
-  * @param {data} ?
-  ###
+  * Callback, executed when event added successfully.
+  *
+  * @callback onAddEventSuccess
+  * @param {HttpResponse} res - HTTP response
+  * @param {Object.<{id:number, title: string, description: string, start: string, end: string, allDay: boolean, url: string, status: string, reminder: string, todo: boolean, location: string, summary: string, lastUpdate: string}>} data - JSON object with event id
+  *
+  * @example
Example output

+  * res: {"offset":243,"readable":true,"_events":{},"statusCode":201,"headers":{"content-type":"application/json"}}
+  * data: {"id":2699,"title":"cool event","description":null,"start":"2015-11-17T23:49:18.747000Z","end":null,
+  *        "allDay":false,"url":null,"status":null,"reminder":null,"todo":false,"location":null,"summary":null,
+  *        "lastUpdate":"2015-11-17T23:49:18.899897Z"}
+  ###
+
+  ###*
+  * Callback function, executed when adding an event failed.
+  *
+  * @callback onAddEventError
+  * @param {HttpResponse} res - HTTP response
+  * @param {ErrorMessage} data - Error message.
+  *
+  * @example
Example output when title and start date not specified

+  * res: {"offset":73,"readable":true,"_events":{},"statusCode":400,"headers":{"content-type":"application/json"}}
+  * data: {"title":"This field is required.", "start":"This field is required."} todo: remove arrays from
+  *
+  * @example
Example output when calendar doesn't exist

+  * res: {"offset":73,"readable":true,"_events":{},"statusCode":400,"headers":{"content-type":"application/json"}}
+  * data: {detail: "Calendar with id -1 doesn't exist"} todo: chang error message
+  *
+  * @example
Example where date has wrong format

+  * res: {"offset":122,"readable":true,"_events":{},"statusCode":400,"headers":{"content-type":"application/json"}}
+  * data: {"start":"Datetime has wrong format. Use one of these formats instead: YYYY-MM-DDThh:mm[:ss[.uuuuuu]][+HH:MM|-HH:MM|Z]"}
+  ###
+
+  #add event success
+  #{"offset":37,"readable":true,"_events":{},"statusCode":400,"headers":{"content-type":"application/json"}}
+  #"{\"start\":[\"This field is required.\"]}"

   ###*
   * Delete an existing event from a calendar giving their ids
   *
-  * @param {Number} calendarId - id of calendar
-  * @param {Number} eventId - id of event
-  * @param {UoACalendarClient~deleteEventSuccess} onSuccess - Success callback function
-  * @param {UoACalendarClient~deleteEventError} onError - Error callback function
+  * @param {number} calendarId - id of calendar
+  * @param {number} eventId - id of event
+  * @param {onDeleteEventSuccess} onSuccess - Success callback function
+  * @param {onDeleteEventError} onError - Error callback function
+  *
+  * @example
+  * var calendarId = 1;
+  * var eventId = 2;
+  * client.deleteEvent(calendarId, eventId,
+  *   function(res, data) // onSuccess callback
+  *   {
+  *     console.log(res);
+  *     console.log(data);
+  *   },
+  *   function(res, data) // onError callback
+  *   {
+  *     console.log(res);
+  *     console.log(data);
+  *   }
+  * );
   ###

   deleteEvent: (calendarId, eventId, onSuccess, onError) ->
     @sendRequest('/calendars/' + calendarId + '/events/' + eventId + '/', 'DELETE', 0, onSuccess, onError)

-# Update an existing event from a calendar giving their IDs
-#
-# ```javascript
-# client.updateEvent(calendarId, eventId, { title: "Event Title", ... }
-#   // onSuccess callback
-#   function(res, data) {
-#       // response
-#       console.log(res);
-#       // deserialized updated event object data
-#       console.log(data);
-#   },
-#   // onError callback
-#   function(res, data) {
-#       ...
-#   }
-# );
-# ```
-#
+  ###*
+  * Callback, executed when event successfully deleted.
+  *
+  * @callback onDeleteEventSuccess
+  * @param {HttpResponse} res - HTTP response
+  * @param {Array} data - empty array
+  *
+  * @example
Example output

+  * res: {"offset":2,"readable":true,"_events":{},"statusCode":200,"headers":{"content-type":"application/json"}}
+  * data: []
+  ###
+
+  ###*
+  * Callback function, executed when deleting an event failed.
+  *
+  * @callback onDeleteEventError
+  * @param {HttpResponse} res - HTTP response
+  * @param {ErrorMessage} data - Error message.
+  *
+  * @example
Example output

+  * todo: insert example
+  ###
+
+
+  ###*
+  *  Update an existing event from a calendar giving their IDs
+  *
+  * @param {number} calendarId - id of calendar
+  * @param {number} eventId - id of event
+  * @param {Object} event - Event JSON object
+  * @param {string} event.title - Event title
+  * @param {string=} event.description - Event description
+  * @param {string=} event.location - Event location
+  * @param {string=} event.summary - Event summary
+  * @param {Date} event.start - Event start date & time
+  * @param {Date=} event.end - Event end date & time
+  * @param {string=} event.status - Event status
+  * @param {string=} event.reminder - Event reminder
+  * @param {boolean=} event.todo - Whether event still to be done
+  * @param {boolean=} event.allDay - Whether event occurs all day
+  * @param {string=} event.url - Event url
+  * @param {onUpdateEventSuccess} onSuccess - Success callback function
+  * @param {onUpdateEventError} onError - Error callback function
+  *
+  * @example
+  * var calendarId = 1;
+  * var eventId = 2;
+  * var event = {"title": "Auckland Marathon", "description": "Running the marathon now!"};
+  * client.updateEvent(calendarId, eventId, event,
+  *   function(res, data) // onSuccess callback
+  *   {
+  *     console.log(res);
+  *     console.log(data);
+  *   },
+  *   function(res, data) // onError callback
+  *   {
+  *     console.log(res);
+  *     console.log(data);
+  *   }
+  * );
+  ###
+
   updateEvent: (calendarId, eventId, event, onSuccess, onError) ->
     @sendRequest('/calendars/' + calendarId + '/events/' + eventId + '/', 'PATCH', event, onSuccess, onError)

-# Find events from an existing calendar within a given time range
-#
-# ```javascript
-# client.updateEvent(calendarId, new Date(1977, 5, 25), Date.now(),
-#   // onSuccess callback
-#   function(res, data) {
-#       // response
-#       console.log(res);
-#       // deserialized events dictionary
-#       console.log(data);
-#   },
-#   // onError callback
-#   function(res, data) {
-#       ...
-#   }
-# );
-# ```
-#
+  ###*
+  * Callback, when event updated.
+  *
+  * @callback onUpdateEventSuccess
+  * @param {HttpResponse} res - HTTP response
+  * @param {Array.<{}>} data - ?
+  *
+  * @example
Example output

+  * res: {"offset":0,"readable":true,"_events":{},"statusCode":204,"headers":{"content-type":"text/plain; charset=UTF-8"}}
+  * data: [{},
+  *        {}]
+  ###
+
+  ###*
+  * Callback function, executed event update failed.
+  *
+  * @callback onUpdateEventError
+  * @param {HttpResponse} res - HTTP response
+  * @param {ErrorMessage} data - Error message.
+  *
+  * @example
Example output

+  * todo: insert example
+  ###
+
+  ###*
+  *  Find events from an existing calendar within a given time range
+  *
+  * @param {number} calendarId - id of calendar
+  * @param {Date} startDate - date to begin searching
+  * @param {Date} endDate - date to stop searching
+  * @param {onFindEventsSuccess} onSuccess - Success callback function
+  * @param {onFindEventsError} onError - Error callback function
+  *
+  * @example
Find all events in calendar 1 between the day star wars first screened and now.

+  * var calendarId = 1;
+  * var startDate = new Date(1977, 5, 25); //date star wars first screened!
+  * var endDate = new Date(); //current date and time
+  * client.findEvents(calendarId, startDate, endDate,
+  *   function(res, data) // onSuccess callback
+  *   {
+  *     console.log(res);
+  *     console.log(data);
+  *   },
+  *   function(res, data) // onError callback
+  *   {
+  *     console.log(res);
+  *     console.log(data);
+  *   }
+  * );
+  ###
+
   findEvents: (calendarId, startDate, endDate, onSuccess, onError) ->
     toUTCString = (date) ->
       return (new Date(date.getTime() + date.getTimezoneOffset() * 60000)).toISOString()
@@ -354,6 +673,51 @@
       onSuccess, onError
     )

+  ###*
+  * Callback, returns events from a particular calendar within a date range.
+  *
+  * @callback onFindEventsSuccess
+  * @param {HttpResponse} res - HTTP response
+  * @param {Array.<{}>} data - Array of events
+  *
+  * @example
Example output

+  * res: {"offset":0,"readable":true,"_events":{},"statusCode":204,"headers":{"content-type":"text/plain; charset=UTF-8"}}
+  * data: [{},
+  *        {}]
+  ###
+
+  ###*
+  * Callback function, executed when finding events failed.
+  *
+  * @callback onFindEventsError
+  * @param {HttpResponse} res - HTTP response
+  * @param {ErrorMessage} data - Error message.
+  *
+  * @example
Example output

+  * todo: insert example
+  ###
+
+  ###*
+  * @typedef {Object} HttpResponse
+  * @property {number} offset -
+  * @property {boolean} readable -
+  * @property {number} statusCode - See [HTTP status codes]{@link https://en.wikipedia.org/wiki/List_of_HTTP_status_codes}
+  * @property {Object} headers -
+  * @property {string} headers.content-type -
+  *
+  * @example
+  * {"offset":30,"readable":true,"_events":{},"statusCode":200,"headers":{"content-type":"application/json"}}
+  ###
+
+  ###*
+  *
+  * @typedef {Object} ErrorMessage
+  * @property {string} detail - Reason for error.
+  *
+  * @example
+  * {detail: "You do not have permission to perform this action."}
+  ###
+
 exports.UoACalendarClient = UoACalendarClient

 module.exports = (config) ->


client/app: remove searchcontroller test

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

--- client/app/test/searchcontroller.test.coffee
+++ client/app/test/searchcontroller.test.coffee
@@ -1,229 +0,0 @@
-kd      = require 'kd'
-mock    = require '../../mocks/mockingjay'
-nick    = require 'app/util/nick'
-expect  = require 'expect'
-remote  = require 'app/remote'
-
-SearchController   = require 'app/searchcontroller'
-{ mainController } = kd.singletons
-
-SEED         = 'tu'
-SEED_OPTIONS =
-  showCurrentUser : no
-
-
-describe 'kd.singletons.search', ->
-
-  afterEach -> expect.restoreSpies()
-
-  describe '::searchAccounts', ->
-
-    it 'should call search method with arguments', (done) ->
-
-      mainController.ready ->
-
-        mockAccount  = mock.getMockAccount()
-        { nickname } = mockAccount.profile
-
-        mock.search.getIndex.toReturnIndex()
-
-        { search }   = kd.singletons
-        search.ready = yes
-
-        spy = expect.spyOn(search, 'search').andCallThrough()
-
-        search.searchAccounts SEED, SEED_OPTIONS
-
-        args = spy.calls.first.arguments
-
-        expect(args[0]).toBe 'accounts'
-        expect(args[1]).toBe SEED
-
-        options = args[2]
-
-        expect(options.hitsPerPage).toBe 10
-        expect(options.restrictSearchableAttributes).toEqual [ 'nick' ]
-
-        done()
-
-
-    it 'should filter current user from the results', (done) ->
-
-      mainController.ready ->
-
-        mockAccount  = mock.getMockAccount()
-        { nickname } = mockAccount.profile
-
-        mock.search.getIndex.toReturnIndex()
-
-        { search }   = kd.singletons
-        search.ready = yes
-
-        nickSpy     = expect.createSpy().andReturn nickname
-        revertNick  = SearchController.__set__ 'nick', nickSpy
-
-        search
-          .searchAccounts SEED, SEED_OPTIONS
-          .then (data) ->
-
-            found = no
-
-            for acc in data when acc.nick is nickname
-              found = yes
-
-            expect(found).toBe no
-
-            revertNick()
-            done()
-
-    it 'should not filter current user if showCurrentUser is yes', (done) ->
-
-      mainController.ready ->
-
-        mockAccount  = mock.getMockAccount()
-        { nickname } = mockAccount.profile
-
-        mock.remote.cacheableAsync.toReturnPassedParam mockAccount
-        mock.search.getIndex.toReturnIndex()
-
-        { search }   = kd.singletons
-        search.ready = yes
-
-        nickSpy     = expect.createSpy().andReturn nickname
-        revertNick  = SearchController.__set__ 'nick', nickSpy
-
-        SEED_OPTIONS.showCurrentUser = yes
-
-        search
-          .searchAccounts SEED, SEED_OPTIONS
-          .then (data) ->
-
-            found = no
-
-            for acc in data when acc.profile.nickname is nickname
-              found = yes
-
-            expect(found).toBe yes
-
-            revertNick()
-            done()
-
-
-    it 'should search mongo if algolia fails', (done) ->
-
-      mainController.ready ->
-
-        { search }   = kd.singletons
-        search.ready = yes
-
-        mock.search.getIndex.toReturnIndex no
-
-        expect.spyOn search, 'searchAccountsMongo'
-
-        search
-          .searchAccounts SEED, SEED_OPTIONS
-          .catch ->
-            expect(search.searchAccountsMongo).toHaveBeenCalledWith SEED, SEED_OPTIONS
-            done()
-
-
-    it 'should search mongo if algolia is not ready', (done) ->
-
-      mainController.ready ->
-
-        { search }   = kd.singletons
-        search.ready = no
-
-        expect.spyOn search, 'searchAccountsMongo'
-
-        search
-          .searchAccounts SEED, SEED_OPTIONS
-          .catch ->
-            expect(search.searchAccountsMongo).toHaveBeenCalledWith SEED, SEED_OPTIONS
-            done()
-
-
-
-  describe '::searchAccountsMongo', ->
-
-    it 'should remove "@" character from the seed', (done) ->
-
-      { search } = kd.singletons
-      seed       = '@kodinguser'
-
-      mock.remote.api.JAccount.one.toReturnAccount()
-      mock.groups.getCurrentGroup.toReturnGroup()
-
-      spy = expect.spyOn remote.api.JAccount, 'one'
-
-      search.searchAccountsMongo(seed).then ->
-        expect(spy).toHaveBeenCalledWith {
-          'profile.nickname': 'kodinguser'
-        }
-        done()
-
-
-    it 'should filter current user from the results', (done) ->
-
-      { search } = kd.singletons
-
-      mockAccount = mock.getMockAccount()
-      mockGroup   = mock.getMockGroup()
-      nickname    = mockAccount.profile.nickname
-
-      mock.remote.api.JAccount.one.toReturnAccount()
-      mock.groups.getCurrentGroup.toReturnGroup()
-
-      expect.spyOn(mockGroup, 'isMember').andCall (account, callback) ->
-        callback null, yes
-
-      nickSpy     = expect.createSpy().andReturn nickname
-      revertNick  = SearchController.__set__ 'nick', nickSpy
-
-      search
-        .searchAccountsMongo SEED
-        .then (account) ->
-
-          found = no
-
-          for acc in account when acc.profile.nickname is nickname
-            found = yes
-
-          expect(found).toBe no
-
-          revertNick()
-          done()
-
-
-    it 'should not filter current user from the results if showCurrentUser is yes', (done) ->
-
-      { search } = kd.singletons
-
-      mockAccount = mock.getMockAccount()
-      mockGroup   = mock.getMockGroup()
-      nickname    = mockAccount.profile.nickname
-
-      mock.remote.api.JAccount.one.toReturnAccount()
-      mock.groups.getCurrentGroup.toReturnGroup()
-
-      expect.spyOn(mockGroup, 'isMember').andCall (account, callback) ->
-        callback null, yes
-
-      nickSpy     = expect.createSpy().andReturn nickname
-      revertNick  = SearchController.__set__ 'nick', nickSpy
-
-      SEED_OPTIONS.showCurrentUser = yes
-
-      search
-        .searchAccountsMongo SEED, SEED_OPTIONS
-        .then (account) ->
-
-          found = no
-
-          for acc in account when acc.profile.nickname is nickname
-            found = yes
-
-          expect(found).toBe yes
-
-          revertNick()
-          done()


'c' does not need to be defined in strip because it will be handled in rstrip and lstrip if it's ==null

--- pystr.coffee
+++ pystr.coffee
@@ -38,5 +38,5 @@

 # Strip characters from the front and end of the string, if no characters are passed in white space will be stripped.
 # Use the native `trim` if it's available and only whitespace is being stripped.
-String::strip = (c) -> return @trim() if not c and @trim; c = c or '\\s'; @lstrip(c).rstrip(c)
+String::strip = (c) -> return @trim() if not c and @trim; @lstrip(c).rstrip(c)


Add internal loading/unloading functions

--- assets/js/plugins.coffee
+++ assets/js/plugins.coffee
@@ -1,3 +1,8 @@
+# TODO: Handle dependencies automatically for the notion of "enabled" plugins.
+#       pluginEnabled  - Return true if it's a dependency of something enabled
+#       enablePlugin   - Enable required dependencies first
+#       disablePlugin  - Unload any dependency which isn't explicitly enabled or required by another enabled plugin.
+
 if module?
   global._ = require('lodash')
   global.tv4 = require('tv4')
@@ -9,7 +14,7 @@
 (() ->
   # class for exposing plugin API
   class PluginApi
-    constructor: (@view, @plugin_metadata) ->
+    constructor: (@view, @plugin_metadata, @pluginManager) ->
       @name = @plugin_metadata.name
       @data = @view.data
       @cursor = @view.cursor
@@ -27,6 +32,23 @@

     getData: (key) ->
       @data.store.getPluginData @name, key
+
+    getPlugin: (name) ->
+      @pluginManager.getPlugin name
+
+    panic: _.once () =>
+      alert "Plugin '#{@name}' has encountered a major problem. Please report this problem to the plugin author."
+      @pluginManager.disablePlugin @name
+
+  # Core plugins are always enabled and can never be disabled (e.g. bold/italic)
+  CORE_PLUGINS = {
+  }
+
+  # Other plugins are maintained in a list of "enabled plugins", which default to this value. Plugins are disabled by default.
+  DEFAULT_ENABLED_PLUGINS = {
+    "Hello World coffee": true
+    "Hello World js": true
+  }

   PLUGIN_SCHEMA = {
     title: "Plugin metadata schema"
@@ -58,7 +80,7 @@
         items: { type: "string" }
         default: []
       }
-      data_version: {
+      dataVersion: {
         description: "Version of data format for plugin"
         type: "integer"
         default: 1
@@ -75,7 +97,8 @@
           data[prop] = prop_info['default']

   class PluginsManager
-    constructor: () ->
+    constructor: (options) ->
+      @enabledPlugins = DEFAULT_ENABLED_PLUGINS # Will be overridden before plugin loading, during 'resolveView'
       @plugins = {}
       @pluginDependencies = new DependencyGraph

@@ -88,36 +111,88 @@

     resolveView: (view) ->
       @view = view
-      @pluginDependencies.resolve '_view', true
-
-    load: (plugin_metadata, cb) ->
-      api = new PluginApi @view, plugin_metadata
-
-      # validate data version
-      dataVersion = do api.getDataVersion
-      unless dataVersion?
-        api.setDataVersion plugin_metadata.data_version
-        dataVersion = plugin_metadata.data_version
-      # TODO: Come up with some migration system for both vimflowy and plugins
-      errors.assert_equals dataVersion, plugin_metadata.data_version,
-        "Plugin data versions are not identical, please contact the plugin author for migration support"
-
-      Logger.logger.info "Plugin #{plugin_metadata.name} loading"
-      cb(api)
-      Logger.logger.info "Plugin #{plugin_metadata.name} loaded"
-
-    register: (plugin_metadata, cb) ->
+      enabledPlugins = view.settings.getSetting "enabledPlugins"
+      if enabledPlugins?
+        @enabledPlugins = enabledPlugins
+      @pluginDependencies.resolve '_view', view
+
+    getPluginNames: () ->
+      _.keys @plugins
+
+    status: (name) -> # Returns one of: Unregistered Registered Loading (Disabled|Loaded)
+      @plugins[name]?.status || "Unregistered"
+
+    enablePlugin: (name) ->
+      @enabledPlugins[name] = true
+      @view.settings.setSetting "enabledPlugins", @enabledPlugins
+      if (@status name) in ["Disabled"] # If it's ready to load
+        @load @plugins[name]
+
+    disablePlugin: (name) ->
+      delete @enabledPlugins[name]
+      @view.settings.setSetting "enabledPlugins", @enabledPlugins
+      if (@status name) in ["Loaded"] and not @pluginEnabled name # calling disablePlugin CORE-PLUGIN still does not disable it
+        @unload @plugins[name]
+
+    getPlugin: (name) ->
+      @plugins[name]?.value
+
+    pluginEnabled: (name) ->
+      (name of CORE_PLUGINS) or (name of @enabledPlugins)
+
+    unload: (plugin) ->
+      if plugin.unload
+        plugin.unload plugin.value
+      else
+        # Default unload method, which is to tell the user to refresh
+        @_displayUnloadAlert ?= _.once (plugin) ->
+          alert "The plugin '#{plugin.name}' was disabled. Refresh to unload all disabled plugins."
+        @_displayUnloadAlert plugin
+      delete plugin.value
+      plugin.status = "Disabled"
+
+    load: (plugin) ->
+      if @pluginEnabled plugin.name
+        api = new PluginApi @view, plugin, @
+
+        # validate data version
+        dataVersion = do api.getDataVersion
+        unless dataVersion?
+          api.setDataVersion plugin.dataVersion
+          dataVersion = plugin.dataVersion
+        # TODO: Come up with some migration system for both vimflowy and plugins
+        errors.assert_equals dataVersion, plugin.dataVersion,
+          "Plugin data versions are not identical, please contact the plugin author for migration support"
+
+        Logger.logger.info "Plugin #{plugin.name} loading"
+        plugin.value = plugin.enable api
+        @pluginDependencies.resolve plugin.name, plugin.value
+        plugin.status = "Loaded"
+        Logger.logger.info "Plugin #{plugin.name} loaded"
+      else
+        plugin.status = "Disabled"
+
+    register: (plugin_metadata, enable, disable) ->
       @validate plugin_metadata
-
-      @plugins[plugin_metadata.name] = plugin_metadata
-      Logger.logger.info "Plugin #{plugin_metadata.name} registered"
-
-      dependencies = _.clone (_.result plugin_metadata, 'dependencies', [])
-      dependencies.push '_view'
+      errors.assert enable?, "Plugin #{plugin_metadata.name} needs to register with a callback"
+
+      # Create the plugin object
+      # Plugin stores all data about a plugin, including metadata
+      # plugin.value contains the actual resolved value
+      plugin = _.cloneDeep plugin_metadata
+      plugin.originalMetadata = plugin_metadata
+      @plugins[plugin.name] = plugin
+      plugin.enable = enable
+      plugin.disable = disable
+      plugin.dependencies = _.clone (_.result plugin_metadata, 'dependencies', [])
+      plugin.dependencies.push '_view'
+      Logger.logger.info "Plugin #{plugin.name} registered"

       # Load the plugin after all its dependencies have loaded
-      @pluginDependencies.add plugin_metadata.name, dependencies, () =>
-        @load plugin_metadata, cb
+      plugin.status = "Registered"
+      (@pluginDependencies.add plugin.name, plugin.dependencies).then () =>
+        plugin.status = "Loading"
+        @load plugin

   Plugins = new PluginsManager


DNDUploader: fix overwrite question modal stack

--- client/finder/commonviews/DNDUploader.coffee
+++ client/finder/commonviews/DNDUploader.coffee
@@ -224,28 +224,32 @@
         else

           modalStack.addModal modal = new KDModalView
-            overlay : no
-            title   : "Overwrite File?"
-            content : """
-

-            You already have the file #{fsFileItem.path}. Do you want
-            to overwrite it?
-

+
+            overlay        : no
+            title          : "Overwrite File?"
+            content        : """
+

+                You already have the file #{fsFileItem.path}. Do you want
+                to overwrite it?
+

             """
-            buttons :
-              Overwrite:
-                cssClass: "modal-clean-green"
-                callback: =>
+
+            buttons        :
+
+              overwrite    :
+                cssClass   : "modal-clean-green"
+                callback   : =>
                   @saveFile fsFileItem, contents
                   modal.destroy()
-              cancel:
-                cssClass: "modal-cancel"
-                callback: ->
-                  modal.destroy()
-              "cancel all":
-                cssClass: "modal-cancel"
-                callback: ->
-                  modalStack.destroy()
+
+              cancel       :
+                cssClass   : "modal-cancel"
+                callback   : -> modal.destroy()
+
+              "cancel all" :
+                cssClass   : "modal-cancel"
+                callback   : -> modalStack.destroy()
+

     fsFolderItem.exists (err, exists)->


:bug: Fix bug in spec

:bug: Fix bug in spec

--- spec/latex-spec.coffee
+++ spec/latex-spec.coffee
@@ -1,4 +1,5 @@
 helpers = require './spec-helpers'
+fs = require 'fs-plus'
 Latex = require '../lib/latex'
 {Opener} = require './stubs'


Add color to inherited style attributes

Add color to inherited style attributes

--- vendor/assets/javascripts/mercury/regions/simple.js.coffee
+++ vendor/assets/javascripts/mercury/regions/simple.js.coffee
@@ -23,16 +23,17 @@
     @textarea = jQuery('<textarea>', @document).val(value)
     @textarea.attr('class', @element.attr('class')).addClass('mercury-textarea')
     @textarea.css
-      border: 0,
-      background: 'transparent',
-      display: 'block',
-      'overflow-y': 'hidden',
-      width: width,
-      height: height,
-      fontFamily: 'inherit',
-      fontSize: 'inherit',
-      fontWeight: 'inherit',
-      fontStyle: 'inherit',
+      border: 0
+      background: 'transparent'
+      display: 'block'
+      'overflow-y': 'hidden'
+      width: width
+      height: height
+      fontFamily: 'inherit'
+      fontSize: 'inherit'
+      fontWeight: 'inherit'
+      fontStyle: 'inherit'
+      color: 'inherit'
     @element.addClass(Mercury.config.regions.className)
     @element.empty().append(@textarea)


Updates atom packages

Updates atom packages

--- atom.symlink/config.cson
+++ atom.symlink/config.cson
@@ -19,6 +19,8 @@
     ]
     disabledPackages: [
       "jshint"
+      "atom-jshint"
+      "linter-jshint"
     ]
   "exception-reporting":
     userId: "927ad9ee-b6a5-4937-bc3c-31ee92e96c24"
@@ -61,6 +63,11 @@
   emmet: {}
   "linter-scss-lint":
     executablePath: "/usr/bin/scss-lint"
+  "tree-view": {}
+  tabs:
+    usePreviewTabs: true
+  "linter-eslint":
+    disableWhenNoEslintrcFileInPath: true
 ".js.jsx.source":
   editor:
     preferredLineLength: 100


Use Menlo in Atom

Use Menlo in Atom

--- atom.symlink/config.cson
+++ atom.symlink/config.cson
@@ -1,41 +1,35 @@
-{
-  "editor": {
-    "fontSize": 15,
-    "showIndentGuide": true
-  },
-  "core": {
-    "excludeVcsIgnoredPaths": true,
-    "ignoredNames": [
-      ".bundle",
-      ".git",
-      "log",
-      "repositories",
-      "tmp",
-      "vendor"
-    ],
-    "themes": [
-      "unity-ui",
-      "glacier-syntax"
-    ],
-    "hideGitIgnoredFiles": true,
-    "disabledPackages": [
-      "background-tips",
-      "exception-reporting",
-      "metrics"
-    ]
-  },
-  "spell-check": {
-    "grammars": [
-      "text.plain",
-      "source.gfm",
-      "text.git-commit"
-    ]
-  },
-  "welcome": {
-    "showOnStartup": false
-  },
-  "release-notes": {
-    "viewedVersion": "0.85.0"
-  },
-  "tree-view": {}
-}
+'editor':
+  'fontSize': 15
+  'showIndentGuide': true
+  'fontFamily': 'Menlo'
+'core':
+  'excludeVcsIgnoredPaths': true
+  'ignoredNames': [
+    '.bundle'
+    '.git'
+    'log'
+    'repositories'
+    'tmp'
+    'vendor'
+  ]
+  'themes': [
+    'unity-ui'
+    'glacier-syntax'
+  ]
+  'hideGitIgnoredFiles': true
+  'disabledPackages': [
+    'background-tips'
+    'exception-reporting'
+    'metrics'
+  ]
+'spell-check':
+  'grammars': [
+    'text.plain'
+    'source.gfm'
+    'text.git-commit'
+  ]
+'welcome':
+  'showOnStartup': false
+'release-notes':
+  'viewedVersion': '0.85.0'
+'tree-view': {}


Atom configuration

Atom configuration

--- atom.symlink/config.cson
+++ atom.symlink/config.cson
@@ -1,4 +1,6 @@
 "*":
+  "autocomplete-plus":
+    confirmCompletion: "tab"
   core:
     ignoredNames: [
       ".git"
@@ -19,8 +21,9 @@
   "exception-reporting":
     userId: "c48ac152-7102-ae12-c9d6-2aafd0240b51"
   "tree-view":
-    autoReveal: true
     hideIgnoredNames: true
     hideVcsIgnoredFiles: true
   welcome:
     showOnStartup: false
+  whitespace:
+    ignoreWhitespaceOnCurrentLine: false


add prettier

add prettier

--- atom.symlink/config.cson
+++ atom.symlink/config.cson
@@ -26,6 +26,9 @@
     panelDisplayMode: "closed"
   "linter-eslint":
     useGlobalEslint: true
+  "prettier-atom":
+    formatOnSaveOptions:
+      enabled: true
   "release-notes":
     viewedVersion: "0.89.0"
   "spell-check":


Modify test to check for inorder bookmarks traversal.

--- tests/unit_tests/completion_test.coffee
+++ tests/unit_tests/completion_test.coffee
@@ -6,6 +6,7 @@

 context "bookmark completer",
   setup ->
+    @bookmark3 = { title: "bookmark3", url: "bookmark3.com" }
     @bookmark2 = { title: "bookmark2", url: "bookmark2.com" }
     @bookmark1 = { title: "bookmark1", url: "bookmark1.com", children: [@bookmark2] }
     global.chrome.bookmarks =
@@ -13,9 +14,9 @@

     @completer = new BookmarkCompleter()

-  should "flatten a list of bookmarks", ->
-    result = @completer.traverseBookmarks([@bookmark1])
-    assert.arrayEqual [@bookmark1, @bookmark2], @completer.traverseBookmarks([@bookmark1])
+  should "flatten a list of bookmarks with inorder traversal", ->
+    result = @completer.traverseBookmarks([@bookmark1, @bookmark3])
+    assert.arrayEqual [@bookmark1, @bookmark2, @bookmark3], @completer.traverseBookmarks([@bookmark1])

   should "return matching bookmarks when searching", ->
     @completer.refresh()


Individualizing mock's infos

Individualizing mock's infos

--- tests/unit/polvo.coffee
+++ tests/unit/polvo.coffee
@@ -4,16 +4,16 @@

 polvo = require '../../lib/polvo'

-
-base = path.join __dirname, '..', 'mocks', 'basic'
-files =
-  pack: path.join base, 'package.json'
-  app: path.join base, 'src', 'app', 'app.coffee'
-  config: path.join base, 'polvo.yml'
-  js: path.join base, 'public', 'app.js'
-  css: path.join base, 'public', 'app.css'
-
-config = """
+# basic mock
+basic = path.join __dirname, '..', 'mocks', 'basic'
+basic_files =
+  basic_pack: path.join basic, 'package.json'
+  app: path.join basic, 'src', 'app', 'app.coffee'
+  basic_config: path.join basic, 'polvo.yml'
+  js: path.join basic, 'public', 'app.js'
+  css: path.join basic, 'public', 'app.css'
+
+basic_config = """
 server:
   port: 8080
   root: ./public
@@ -31,18 +31,22 @@
 boot: src/app/app
 """

-pack = '{"name": "basic"}'
+basic_pack = '{"name": "basic"}'
+
+
+# npm mock
+

 describe '[polvo]', ->

   before ->
-    fs.unlinkSync files.config if fs.existsSync files.config
-    fs.unlinkSync files.pack if fs.existsSync files.pack
-    fs.unlinkSync files.js if fs.existsSync files.js
-    fs.unlinkSync files.css if fs.existsSync files.css
-
-    fs.writeFileSync files.config, config
+    fs.unlinkSync basic_files.basic_config if fs.existsSync basic_files.basic_config
+    fs.unlinkSync basic_files.basic_pack if fs.existsSync basic_files.basic_pack
+    fs.unlinkSync basic_files.js if fs.existsSync basic_files.js
+    fs.unlinkSync basic_files.css if fs.existsSync basic_files.css
+
+    fs.writeFileSync basic_files.basic_config, basic_config

   afterEach ->
     mods = [
@@ -58,17 +62,17 @@
       delete require.cache[mod]

   after ->
-    fs.unlinkSync files.config if fs.existsSync files.config
-    fs.unlinkSync files.pack if fs.existsSync files.pack
-    fs.unlinkSync files.js if fs.existsSync files.js
-    fs.unlinkSync files.css if fs.existsSync files.css
+    fs.unlinkSync basic_files.basic_config if fs.existsSync basic_files.basic_config
+    fs.unlinkSync basic_files.basic_pack if fs.existsSync basic_files.basic_pack
+    fs.unlinkSync basic_files.js if fs.existsSync basic_files.js
+    fs.unlinkSync basic_files.css if fs.existsSync basic_files.css

   it 'should alert about no `package.json` file plugins during compile', ->
     errors = outs = 0
     checker = /^info app doesn't have a `package.json`/m

-    options = compile: true, base: base
+    options = compile: true, base: basic
     stdio =
       nocolor: true
       err:(msg)-> errors++
@@ -87,7 +91,7 @@
     errors = outs = 0
     checker = /✓ public\/app\.(js|css).+$/m

-    options = compile: true, base: base
+    options = compile: true, base: basic
     stdio =
       nocolor: true
       err:(msg) -> errors++
@@ -95,7 +99,7 @@
         outs++
         checker.test(msg).should.be.true

-    fs.writeFileSync files.pack, pack
+    fs.writeFileSync basic_files.basic_pack, basic_pack
     compile = polvo options, stdio

     outs.should.equal 2
@@ -106,13 +110,13 @@
     errors = outs = 0
     checker = /✓ public\/app\.(js|css).+$/m

-    options = release: true, base: base
+    options = release: true, base: basic
     stdio =
       out:(msg) -> checker.test(msg).should.be.true
       err:(msg) -> errors++
       nocolor: true

-    fs.writeFileSync files.pack, pack
+    fs.writeFileSync basic_files.basic_pack, basic_pack
     release = polvo options, stdio
     errors.should.equal 0

@@ -127,7 +131,7 @@
         outs++
         version.should.equal require('../../package.json').version

-    fs.writeFileSync files.pack, pack
+    fs.writeFileSync basic_files.basic_pack, basic_pack
     version = polvo options, stdio

     outs.should.equal 1
@@ -151,7 +155,7 @@
       /✓ public\/app\.js/
     ]

-    options = watch: 'true', base: base
+    options = watch: 'true', base: basic
     stdio =
       out:(msg) ->
         checkers.shift().test(msg).should.be.true
@@ -163,25 +167,25 @@
       err:(msg) -> errors++
       nocolor: true

-    fs.writeFileSync files.pack, pack
-    backup = fs.readFileSync files.app
+    fs.writeFileSync basic_files.basic_pack, basic_pack
+    backup = fs.readFileSync basic_files.app

     polvo = require '../../lib/polvo'
     watch = polvo options, stdio

     # editing
     new setTimeout ->
-      fs.appendFileSync files.app, '\n\na = 1\n'
+      fs.appendFileSync basic_files.app, '\n\na = 1\n'
     , 1000

     # deleting
     new setTimeout ->
-      fs.unlinkSync files.app
+      fs.unlinkSync basic_files.app
     , 2000

     # creating
     new setTimeout ->
-      fs.writeFileSync files.app, backup
+      fs.writeFileSync basic_files.app, backup
     , 3000

   it 'should start app and serve it', (done)->
@@ -196,7 +200,7 @@

     server = null

-    options = compile:true, server: 'true', base: base
+    options = compile:true, server: 'true', base: basic
     stdio =
       out:(msg) ->
         checkers.shift().test(msg).should.be.true
@@ -211,8 +215,8 @@
       err:(msg) -> errors++
       nocolor: true

-    fs.writeFileSync files.pack, pack
-    backup = fs.readFileSync files.app
+    fs.writeFileSync basic_files.basic_pack, basic_pack
+    backup = fs.readFileSync basic_files.app

     polvo = require '../../lib/polvo'
     server = polvo options, stdio


clean

clean

--- src/coffee/directives/container.coffee
+++ src/coffee/directives/container.coffee
@@ -15,4 +15,4 @@
         definition: '='
       template: templates.container
     }
-  )+  )


store notebook list in local storage and load it on startup

--- app/assets/javascripts/app.coffee
+++ app/assets/javascripts/app.coffee
@@ -51,6 +51,9 @@

     # get note list from remote
     noteManager.fetch_note_list()
+
+    # get notebook list from local
+    noteManager.reload_local_notebook_list()

     # get notebook list from remote
     noteManager.fetch_notebook_list()
@@ -729,8 +732,9 @@
     SYNCED_ALL: 2
     MODIFIED: 3

-  SETTINGS_KEY:
-    CURRENT_NOTE_GUID: 'note_manager.settings.current_note.guid'
+  STORAGE_KEY:
+    CURRENT_NOTE_GUID: 'note_manager.current_note.guid'
+    NOTEBOOK_LIST: 'note_manager.notebook_list'

   # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~| Current Note >
@@ -846,7 +850,7 @@
   # load previous note if exists, otherwise make a new note and set it current note
   # ------------------------------------------------------------
   init_current_note: () =>
-    previous_note_guid = localStorageService.get(@SETTINGS_KEY.CURRENT_NOTE_GUID)
+    previous_note_guid = localStorageService.get(@STORAGE_KEY.CURRENT_NOTE_GUID)
     if not previous_note_guid?
       previous_note_guid = "INVALID_GUID"
     p = @find_note_by_guid(previous_note_guid).then (note) =>
@@ -939,7 +943,11 @@

   _set_notebook_list: (notebook_list) =>
     @notebook_list = notebook_list
+    localStorageService.set(@STORAGE_KEY.NOTEBOOK_LIST, notebook_list)
     @notebook_list_changed(notebook_list)
+
+  reload_local_notebook_list: () =>
+    @_set_notebook_list(localStorageService.get(@STORAGE_KEY.NOTEBOOK_LIST))

   fetch_notebook_list: =>
     @load_remote_notebooks().then(
@@ -969,7 +977,7 @@
   on_current_note_switched: (listener) =>
     @current_note_switched_listeners.push(listener)
   current_note_switched: (new_note_guid) =>
-    localStorageService.set(@SETTINGS_KEY.CURRENT_NOTE_GUID, new_note_guid)
+    localStorageService.set(@STORAGE_KEY.CURRENT_NOTE_GUID, new_note_guid)

     for l in @current_note_switched_listeners
       l(new_note_guid)
@@ -981,7 +989,7 @@
   on_current_note_guid_modified: (listener) =>
     @current_note_guid_modified_listeners.push(listener)
   current_note_guid_modified: (old_guid, new_guid) =>
-    localStorageService.set(@SETTINGS_KEY.CURRENT_NOTE_GUID, new_guid)
+    localStorageService.set(@STORAGE_KEY.CURRENT_NOTE_GUID, new_guid)

     for l in @current_note_guid_modified_listeners
       l(old_guid, new_guid)


make new note upon launched

make new note upon launched

--- app/assets/javascripts/app.coffee
+++ app/assets/javascripts/app.coffee
@@ -54,6 +54,17 @@

     # sync scroll
     scrollSyncor.syncScroll(vm.ace_editor, $('#md_html_div'))
+
+    # make new note
+    noteManager.make_new_note().then(
+      (note) =>
+        console.log('New note made: ' + JSON.stringify(note))
+        vm.note.guid = note.guid
+        vm.note.title = note.title
+        vm.set_md(note.md)
+      (error) =>
+        alert('make_new_note() failed!')
+    )

     # get note list
     vm.refresh_all_notes()
@@ -606,8 +617,8 @@
 # Service: noteManager
 # ----------------------------------------------------------------------------------------------------------------------
 markever.factory 'noteManager',
-['dbProvider', 'apiClient', 'imageManager',
-(dbProvider, apiClient, imageManager) -> new class NoteManager
+['uuid2', 'dbProvider', 'apiClient', 'imageManager',
+(uuid2, dbProvider, apiClient, imageManager) -> new class NoteManager

   # Status of a note:
   # 1. new: note with a generated guid, not attached to remote
@@ -782,6 +793,24 @@
     return new Promise (resolve, reject) =>
       resolve(@db_server.notes.query().all().execute())

+  # ------------------------------------------------------------
+  # make a new note to db
+  #
+  # db_server safe. will get db_server itself
+  # return promise
+  # ------------------------------------------------------------
+  make_new_note: () =>
+    console.log('make_new_note() invoking')
+    return new Promise (resolve, reject) =>
+      p = dbProvider.get_db_server_promise().then (server) =>
+        server.notes.add({
+          guid: uuid2.newguid() + '-new'
+          title: 'New Note - Markever'
+          md: ''
+          status: @NOTE_STATUS.NEW
+        })
+      resolve(p)
+
   delete_note: (guid) =>
     console.log('delete_note(' + guid + ') invoked')
     return @find_note_by_guid(guid).then(


Use setting instead of hard-coding port

Use setting instead of hard-coding port

--- services/chat/config/settings.defaults.coffee
+++ services/chat/config/settings.defaults.coffee
@@ -6,7 +6,7 @@

 	apis:
 		web:
-			url: "http://#{process.env['WEB_HOST'] || "localhost"}:3000"
+			url: "http://#{process.env['WEB_HOST'] || "localhost"}:#{process.env['WEB_PORT'] or 3000}"
 			user: "sharelatex"
 			pass: "password"


Customizable CommentLink body

Customizable CommentLink body

--- app/talk/comment-link.cjsx
+++ app/talk/comment-link.cjsx
@@ -64,7 +64,9 @@
                   page: pageOfComment,
                   comment: comment.id
                 }
-            {projectCommentUrl}
+
+              {@props.children ? projectCommentUrl}
+
           }</PromiseRenderer>

         else
@@ -73,6 +75,8 @@
             @makeHref 'talk-discussion',
               {board: discussion.board_id, discussion: discussion.id},
               {page: pageOfComment, comment: comment.id}
-          {mainTalkCommentUrl}
+
+            {@props.children ? mainTalkCommentUrl}
+
           }


Pollers should be assigned to the model

Pollers should be assigned to the model

--- assets/javascripts/routes/index_route.js.coffee
+++ assets/javascripts/routes/index_route.js.coffee
@@ -1,38 +1,26 @@
 App.IndexRoute = App.AuthenticatedRoute.extend
   setupController: (controller, model)->
-    stateData   = {}
-    stateIds = []
     activeState = null

     # Loop thru all room states
     #   find the first joined room to load
     #   start pollers for all joined rooms
     model.forEach (item)=>
-      stateId = item.get("id")
-      stateIds.push(stateId)

       # This is to make sure the first active room is loaded
       if item.get("joined") == true
         activeState = item if !activeState?

-        messagePoller = new App.MessagePoller()
-        messagePoller.store  = @store
-        messagePoller.setRoom item.get("room")
-        messagePoller.start()
+        item.messagePoller = new App.MessagePoller()
+        item.messagePoller.store  = @store
+        item.messagePoller.setRoom item.get("room")
+        item.messagePoller.start()

-        usersPoller = new App.UsersPoller()
-        usersPoller.store = @store
-        usersPoller.setRoom item.get("room")
-        usersPoller.start()
+        item.usersPoller = new App.UsersPoller()
+        item.usersPoller.store = @store
+        item.usersPoller.setRoom item.get("room")
+        item.usersPoller.start()

-        stateData[stateId] =
-          messagePoller: messagePoller
-          usersPoller: usersPoller
-      else
-        stateData[stateId] = {}
-
-    controller.set("stateIds", stateIds)
-    controller.set("stateData", stateData)
     controller.set("activeState", activeState)
     @_super(controller, model)

@@ -41,7 +29,6 @@
     @store.find("room_user_state")

   deactivate: ->
-    stateData = @controller.get("stateData")
-    for stateId, state of stateData
-      state.messagePoller.stop() if state.messagePoller
-      state.usersPoller.stop() if state.usersPoller
+    @controller.get("model").forEach (item)=>
+      item.messagePoller.stop() if item.messagePoller
+      item.usersPoller.stop() if item.usersPoller


Lame fix

Lame fix

--- lib/util/configuration-view.coffee
+++ lib/util/configuration-view.coffee
@@ -30,6 +30,7 @@

   createFieldRow: (id, type, label) ->
     row = document.createElement("tr")
+    row.classList.add("native-key-bindings") # workaround Atom bug
     row.appendChild @createLabel(id, label)
     row.appendChild @createField(id, type)
     return row
@@ -85,7 +86,7 @@
   getValues: () ->
     values = {}
     @fields.forEach (x) ->
-      console.log x
+      # console.log x
       type = x.getAttribute("type")
       values[x.id] = x.value if type in ["text","password"]
       values[x.id] = (x.checked == true) if type == "checkbox"


Record activation and load time for each package

--- src/atom.coffee
+++ src/atom.coffee
@@ -34,7 +34,9 @@
   activatePackage: (name, options) ->
     if pack = @loadPackage(name, options)
       @activePackages[pack.name] = pack
+      startTime = new Date().getTime()
       pack.activate(options)
+      pack.activateTime = new Date().getTime() - startTime
       pack

   deactivatePackages: ->
@@ -67,7 +69,9 @@

     if packagePath = @resolvePackagePath(name)
       return pack if pack = @getLoadedPackage(name)
+      startTime = new Date().getTime()
       pack = Package.load(packagePath, options)
+      pack.loadTime = new Date().getTime() - startTime
       if pack.metadata.theme
         @themes.register(pack)
       else


Deserialize grammar on editor window start

--- src/atom.coffee
+++ src/atom.coffee
@@ -251,9 +251,6 @@
   #
   # Call after this instance has been assigned to the `atom` global.
   initialize: ->
-    if grammarOverridesByPath = @state.grammars?.grammarOverridesByPath
-      @grammars.grammarOverridesByPath = grammarOverridesByPath
-
     @windowEventHandler = new WindowEventHandler

   ###
@@ -749,6 +746,9 @@
     delete @state.packageStates

   deserializeEditorWindow: ->
+    if grammarOverridesByPath = @state.grammars?.grammarOverridesByPath
+      @grammars.grammarOverridesByPath = grammarOverridesByPath
+
     @deserializePackageStates()
     @deserializeProject()
     @deserializeWorkspace()


add latex picker #320

add latex picker #320

--- chrome/content/zotero-better-bibtex/cayw.coffee
+++ chrome/content/zotero-better-bibtex/cayw.coffee
@@ -1,4 +1,27 @@
-Zotero.BetterBibTeX.CAYW = {}
+Zotero.BetterBibTeX.CAYW =
+  shortLocator:
+    article: "art."
+    chapter: "ch."
+    subchapter: "subch."
+    column: "col."
+    figure: "fig."
+    line: "l."
+    note: "n."
+    issue: "no."
+    opus: "op."
+    page: "p."
+    paragraph: "para."
+    subparagraph: "subpara."
+    part: "pt."
+    rule: "r."
+    section: "sec."
+    subsection: "subsec."
+    Section: "Sec."
+    'sub verbo': "sv."
+    schedule: "sch."
+    title: "tit."
+    verse: "vrs."
+    volume: "vol."

 class Zotero.BetterBibTeX.CAYW.Document
   constructor: ->
@@ -64,6 +87,10 @@
     for own key of @config
       @config[key] = config[key] if config[key]

+    if @config.format.match(/^cite/)
+      @config.command = @config.format
+      @config.format = 'latex'
+
   getItems: -> Q.fcall(-> [])

   accept: (progressCallback) ->
@@ -81,7 +108,12 @@
       citations.push(citation)

     if Zotero.BetterBibTeX.CAYW.Formatter[@config.format]
-      formatted = Zotero.BetterBibTeX.CAYW.Formatter[@config.format].call(null, citations)
+      try
+        formatted = Zotero.BetterBibTeX.CAYW.Formatter[@config.format].call(null, citations, @config)
+      catch err
+        Zotero.BetterBibTeX.error('cayw:', err)
+        alert('Could not format references: ' + err.msg)
+        formatted = ''
     else
       formatted = []
       for citation in citations
@@ -98,6 +130,45 @@
     @doc.activate()

 Zotero.BetterBibTeX.CAYW.Formatter = {
+  latex: (citations, config) ->
+    config.command ||= 'cite'
+
+    return '' if citations.length == 0
+
+    state = {
+      prefix: 0
+      suffix: 0
+      'suppress-author': 0
+      locator: 0
+      label: 0
+    }
+    if citations.length > 1
+      for citation in citations
+        for own k of citation
+          state[k] ?= 0
+          state[k]++
+
+    if state.suffix == 0 && state.prefix == 0 && state.locator == 0 && state['suppress-author'] in [0, citations.length]
+      # simple case where everything can be put in a single cite
+      return "\\#{if citations[0]['suppress-author'] then 'citeyear' else config.command}{#{(citation.citekey for citation in citations).join(',')}}"
+
+    formatted = ''
+    for citation in citations
+      formatted += ' ' + citation.prefix + ' ' if citation.prefix
+      formatted += "\\"
+      formatted += if citation['suppress-author'] then 'citeyear' else config.command
+
+      switch
+        when citation.locator && citation.suffix
+          formatted += '[' + Zotero.BetterBibTeX.CAYW.shortLocator[citation.label] + ' ' + citation.locator + ', ' + citation.suffix + ']'
+        when citation.locator
+          formatted += '[' + Zotero.BetterBibTeX.CAYW.shortLocator[citation.label] + ' ' + citation.locator + ']'
+        when citation.suffix
+          formatted += '[' + citation.suffix + ']'
+      formatted += '{' + citation.citekey + '}'
+
+    return formatted.trim()
+
   mmd: (citations) ->
     formatted = []
     for citation in citations
@@ -121,30 +192,6 @@
     return '[' + citation.join(';') + ']'

   'scannable-cite': (citations) ->
-    scannableCiteLocator: {
-      article: "art."
-      chapter: "ch."
-      subchapter: "subch."
-      column: "col."
-      figure: "fig."
-      line: "l."
-      note: "n."
-      issue: "no."
-      opus: "op."
-      page: "p."
-      paragraph: "para."
-      subparagraph: "subpara."
-      part: "pt."
-      rule: "r."
-      section: "sec."
-      subsection: "subsec."
-      Section: "Sec."
-      'sub verbo': "sv."
-      schedule: "sch."
-      title: "tit."
-      verse: "vrs."
-      volume: "vol."
-    }

     class Mem
       constructor: (@isLegal) ->
@@ -171,7 +218,7 @@
         when item.libraryID then "zg:#{item.libraryID}:#{item.key}"
         when Zotero.userID then "zu:#{Zotero.userID}:#{item.key}"
         else "zu:0:#{item.key}"
-      locator = if citation.locator then "#{scannableCiteLocator[citation.label]} #{citation.locator}" else ''
+      locator = if citation.locator then "#{Zotero.BetterBibTeX.CAYW.shortLocator[citation.label]} #{citation.locator}" else ''
       citation.prefix ?= ''
       citation.suffix ?= ''


Fixed clear

Fixed clear

--- core/app/backbone/models/current_user.coffee
+++ core/app/backbone/models/current_user.coffee
@@ -9,10 +9,14 @@
   url: -> '/api/beta/current_user/password'
   isNew: -> false

+  # Weirdly, this is not default behaviour
+  clear: ->
+    @set @defaults
+
   validate: (attributes, options) ->
-    if attributes.current_password?.length == 0
+    if attributes.current_password.length == 0
       'No current password'
-    else if  attributes.password?.length < 6
+    else if  attributes.password.length < 6
       'New password to short' # seems to be enforced by Devise
     else if attributes.password_confirmation != attributes.password
       'Confirmation does not match'


Default to post entity when entity not part of mention

--- lib/assets/javascripts/models/post.js.coffee
+++ lib/assets/javascripts/models/post.js.coffee
@@ -112,6 +112,9 @@

   parseAttributes: (attrs) =>
     attrs.permissions ?= { public: true }
+    if attrs.mentions
+      for m in attrs.mentions
+        m.entity ?= attrs.entity
     super(attrs)
     @updateRepostFlag(@get('type'))
     @updateMentionedPosts(@get('mentions'))


recording when sim is correctly marked

recording when sim is correctly marked

--- app/controllers/classifier.coffee
+++ app/controllers/classifier.coffee
@@ -106,7 +106,9 @@
     @yDown            = null
     @isAnnotatable    = true

-    @isTrainingSubject = false
+    @isTrainingSubject  = false
+    @isLensMarked       = false
+
     @ctx = undefined

     @warningDialog.close()
@@ -557,7 +559,11 @@

       # Push subject to Talk collection
       @trigger 'addToTalk', @classification.subject.zooniverse_id
-
+
+    # Training image with simulation
+    if @isTrainingSubject
+      @classification.annotate {simFound: @isLensMarked}
+
     @classification.send()

     # Empty SVG element
@@ -601,7 +607,6 @@
       training = @classification.subject.metadata.training
       trainingType = training.type

-      # TODO: Do we have more than these categories?
       if trainingType in ['lensed galaxy', 'lensed quasar']

         # Get the location for the dialog
@@ -609,12 +614,11 @@
         y = 1 - (training.y / @subjectDimension)

         # Check if any annotation over lens
-        isLensMarked = false
         for index, annotation of @annotations
-          isLensMarked = @checkImageMask(annotation.x, annotation.y)
-          break if isLensMarked
-
-        if isLensMarked
+          @isLensMarked = @checkImageMask(annotation.x, annotation.y)
+          break if @isLensMarked
+
+        if @isLensMarked
           @tutorial = @createSimulationFoundFeedback(e, trainingType, x, y)
         else
           @tutorial = @createSimulationMissedFeedback(e, trainingType, x, y)


show planet details

show planet details

--- app/controllers/classifier.coffee
+++ app/controllers/classifier.coffee
@@ -583,17 +583,25 @@
       else
         $(".sim_details").hide()

+      if @knownPlanetPresent()
+        @showPlanetDetails()
+      else
+        $(".planet_details").hide()
+
       @hideMarkingButtons()
       @nextSubjectButton.show()
       @setGuestObsContent()
       @classifySummary.fadeIn(150)

   showSimDetails:=>
-
     $(".sim_details").show()
     $(".sim_details .planet-rad").html(@planet_rad + " Earth Radi")
     $(".sim_details .planet-period").html(@planet_period + " days")

+  showPlanetDetails:=>
+    $(".planet_details").show()
+    $(".planet_details .planet-rad").html(@simulation.metadata.planet_rad + " Earth Radi")
+    $(".planet_details .planet-period").html(@simulation.metadata.planet_period + " days")

   checkSupplementalTutorial: ->
     for classification_count in @supTutIntervals
@@ -731,6 +739,9 @@
     else
       return false

+  knownPlanetPresent:->
+    @subject.metadata.known_planet?
+
   setGuestObsContent:=>
     # console.log GuestObsContent
     i = Math.floor(Math.random()*GuestObsContent.length)


do it the coffee way

do it the coffee way

--- src/controllers/mixins/resource.coffee
+++ src/controllers/mixins/resource.coffee
@@ -128,15 +128,13 @@
           This would sort ascending by createdAt and descending by title. Ascending is assumed by default
     ###
     buildConditionsFromParameters: (parameters) ->
-
       ###
           Some parameter names are reserved and have a special meaning
       ###
       reservedParams = ["sort","include","includes","limit","offset","method","controller","action","format"]

       # only accept parameters that represent an attribute for where conditions
-      allowedWhereAttributes = []
-      allowedWhereAttributes.push key for key,value of App[@resourceOptions.resourceClass].metadata().attributes
+      allowedWhereAttributes = _.keys(App[@resourceOptions.resourceClass].metadata().attributes)

       conditions = {}

@@ -152,12 +150,10 @@
               # we sort ascending by default
               # a minus in front of the attribute sorts descending
               if firstChar isnt "-"
-                unless conditions.asc?
-                  conditions.asc = []
+                conditions.asc or= []
                 conditions.asc.push value
               else if firstChar is "-"
-                unless conditions.desc?
-                    conditions.desc = []
+                conditions.desc or= []
                 conditions.desc.push value[1..-1]

           if key is "limit" or key is "offset"
@@ -168,15 +164,13 @@
             # includes can contain multiple classes
             includeParams = value.split(",")
             for value in includeParams
-              unless conditions.includes?
-                  conditions.includes = []
+              conditions.includes or= []
               conditions.includes.push value
           continue
         if key not in allowedWhereAttributes
           continue
         # all other parameter names are treated as where conditions
-        unless conditions.where?
-          conditions.where = {}
+        conditions.where or= {}
         firstChar = value[0]
         checksForEquality = firstChar isnt ">" and firstChar isnt "<"
         if not checksForEquality
@@ -191,6 +185,7 @@
         else
           conditions.where[key] = value

+
       conditions

     applyConditionsToScope: (scope, conditions) ->


Activity: no need to send updatelastSeenTime or glance

--- client/Social/Activity/views/messagepane.coffee
+++ client/Social/Activity/views/messagepane.coffee
@@ -104,6 +104,9 @@
     item = app.getView().sidebar.selectedItem

     return  unless item.count
+    # no need to send updatelastSeenTime or glance
+    # when checking publicfeeds
+    return  if typeConstant is 'group'

     if typeConstant is 'post'
     then socialapi.channel.glancePinnedPost   messageId : id, ->


Add mergeDeep benchmarks (#16)

Add mergeDeep benchmarks (#16)

--- tools/benchmarks.coffee
+++ tools/benchmarks.coffee
@@ -46,6 +46,7 @@
   merge: (obj1, obj2) ->
     for key in Object.keys obj2
       obj1[key] = obj2[key]
+  mergeDeep: (obj1, obj2) -> _.merge(obj1, obj2)
   initArr: -> _.cloneDeep INITIAL_ARRAY
   getAt: (arr, idx) -> arr[idx]
   setAt: (arr, idx, val) -> arr[idx] = val; arr
@@ -61,6 +62,7 @@
   getIn: _getIn
   setIn: (obj, path, val) -> timm.setIn obj, path, val
   merge: (obj1, obj2) -> timm.merge obj1, obj2
+  mergeDeep: (obj1, obj2) -> timm.mergeDeep obj1, obj2
   initArr: -> _.cloneDeep INITIAL_ARRAY
   getAt: (arr, idx) -> arr[idx]
   setAt: (arr, idx, val) -> timm.replaceAt arr, idx, val
@@ -74,6 +76,7 @@
   getIn: (obj, path) -> obj.getIn path
   setIn: (obj, path, val) -> obj.setIn path, val
   merge: (obj1, obj2) -> obj1.merge obj2
+  mergeDeep: (obj1, obj2) -> obj1.mergeDeep obj2
   initArr: -> Immutable.List INITIAL_ARRAY   # shallow
   getAt: (arr, idx) -> arr.get idx
   setAt: (arr, idx, val) -> arr.set idx, val
@@ -87,6 +90,7 @@
   getIn: _getIn
   setIn: (obj, path, val) -> obj.setIn path, val
   merge: (obj1, obj2) -> obj1.merge obj2
+  mergeDeep: (obj1, obj2) -> obj1.merge obj2, {deep: true}
   initArr: -> Seamless INITIAL_ARRAY
   getAt: (arr, idx) -> arr[idx]
   setAt: (arr, idx, val) -> arr.set idx, val
@@ -95,10 +99,10 @@
   return solution.set obj, 'toggle', not(solution.get obj, 'toggle')

 _addResult = (results, condition) ->
-  results.push if condition then chalk.green.bold('P') else chalk.green.red('F')
+  results.push if condition then chalk.green.bold('P') else chalk.red('F')
 _verify = (solution) ->
   results = []
-  {init, get, set, setDeep, getIn, setIn, merge, initArr, getAt, setAt} = solution
+  {init, get, set, setDeep, getIn, setIn, merge, mergeDeep, initArr, getAt, setAt} = solution

   # Initial conditions
   obj = init()
@@ -159,6 +163,17 @@
   obj2 = merge obj, {c: 5, f: null}
   _addResult results, (obj2 isnt obj)
   _addResult results, (get(obj2, 'd') is get(obj, 'd'))
+  _addResult results, (get(obj2, 'c') is 5)
+  _addResult results, (get(obj2, 'f') is null)
+  results.push '-'
+
+  # Deep merge
+  obj2 = mergeDeep obj, {c: 5, f: null, d: {d9: {b: {a: 1}}}}
+  changedPath = ['d', 'd9', 'b', 'a']
+  unchangedPath = ['d', 'd9', 'b', 'b']
+  _addResult results, (obj2 isnt obj)
+  _addResult results, (getIn(obj2, changedPath) is 1)
+  _addResult results, (getIn(obj2, unchangedPath) is getIn(obj, unchangedPath))
   _addResult results, (get(obj2, 'c') is 5)
   _addResult results, (get(obj2, 'f') is null)
   results.push '-'
@@ -219,6 +234,12 @@
     for n in [0...N]
       obj2 = solution.merge(obj, MERGE_OBJ)
     return
+  obj = solution.init()
+  MERGE_DEEP_OBJ = {c: 5, f: null, d: {d9: {b: {a: 1}}}}
+  _test "Object: mergeDeep (x#{N})", ->
+    for n in [0...N]
+      obj2 = solution.mergeDeep(obj, MERGE_DEEP_OBJ)
+    return
   arr = solution.initArr()
   _test "Array: read (x#{N})", ->
     for n in [0...N]


Using the incorrect var in endingForDay()

--- src/scripts/github-status.coffee
+++ src/scripts/github-status.coffee
@@ -41,7 +41,7 @@
 endingForDay = (dayOfMonth) ->
   if dayOfMonth >= 11 and dayOfMonth <= 13
     return "th"
-  switch n % 10
+  switch dayOfMonth % 10
     when 1 then return "st"
     when 2 then return "nd"
     when 3 then return "rd"


Remove unused function

Remove unused function

--- resources/assets/coffee/react/mp-history/main.coffee
+++ resources/assets/coffee/react/mp-history/main.coffee
@@ -11,10 +11,6 @@
   FETCH_LIMIT = 100
   MAXIMUM_EVENTS = 500
   REFRESH_TIMEOUT = 10000
-
-  findLastGame = (events) ->
-    _.findLast events, (e) -> e.game?
-

   constructor: (props) ->
     super props


add created at to intercom group serialization

--- lineman/app/services/intercom_service.coffee
+++ lineman/app/services/intercom_service.coffee
@@ -14,6 +14,7 @@
        user_id: CurrentUser.id
        created_at: CurrentUser.createdAt
        angular_ui: true
+       locale: CurrentUser.locale

     shutdown: ->
       return unless @available()
@@ -36,6 +37,7 @@
           creator_id: group.creatorId
           visible_to: group.visibleTo
           cohort_id: group.cohortId
+          created_at: group.createdAt

     contactUs: ->
       return unless @available()


fixing regexp

fixing regexp

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

 				# if there is a class inside the file
-				if /(class\s)(\S+)/g.test raw
+				if /(class)+\s+(\w+)($|\s+extends\s+\w+$)/g.test raw

 					# if the file is not in the root src folder (outside any
 					# folder/package )


Make autocomplete dropdown dissappear when user clicks in input box

--- static/coffee/completions.coffee
+++ static/coffee/completions.coffee
@@ -239,9 +239,9 @@
       if subtext.match(/\(\)$/) isnt null
         inputor.caret('pos', subtext.length - 1)

-    # do react to clicking inside expressions
+    # hide autocomplete if user clicks in the input
     inputor.off('click.atwhoInner').on 'click.atwhoInner', (e) ->
-      $.noop()
+      inputor.atwho('hide')

     # check for possible inserts when a key is pressed
     inputor.off('keyup.atwhoInner').on 'keyup.atwhoInner', (e) ->


Make the dateline responsive.

Make the dateline responsive.

--- app/javascripts/directives.coffee
+++ app/javascripts/directives.coffee
@@ -45,39 +45,76 @@
 directives.directive 'qiDateline', ['Helpers', (Helpers) ->
   # Formats the dateline from the given data.
   format = (scope, element, attrs) ->
+    # Reformats the chart.
+    # @param chart the SVG chart element
+    # @param scales
+    # @param config the {x : {scale, axes: [{element, axis}, ...},
+    #                    y : ...} data structure
+    resize = (chart, config) ->
+      chartNode = chart.node()
+      # The parent element.
+      parent = chartNode.parentNode
+      # The chart height and width.
+      height = $(parent).height()
+      width = $(parent).width()
+      # Resize the chart.
+      config.x.scale.range([0, width])
+      for {element: elt, axis: axis} in config.x.axes
+        elt.attr('transform', "translate(0, #{ height })")
+        elt.call(axis)
+      if config.y
+        config.y.scale.range([height, 0])
+        for {element: elt, axis: axis} in config.y.axes
+          elt.call(axis)
+
+    # The element which contains the chart.
+    parent = d3.select(element[0])
+    # The visits to display.
     data = scope.data
+    # The visit dates.
     dates =
         obj[attrs.attribute] for obj in data
-    width = attrs.width or element.parent().width()
-    height = attrs.height or element.parent().height()

-    x = d3.scale.linear()
+    # The x axis scale.
+    x_scale = d3.scale.linear()
     domain = [_.min(dates), _.max(dates)]
-    x.domain(domain).range([0, width])
+    x_scale.domain(domain)
+
+    # The SVG chart.
+    chart = parent.append('svg')

     # The dates are displayed along the x axis bottom.
     bot_axis = d3.svg.axis()
-    bot_axis.scale(x).orient('bottom').tickValues(dates)
+    bot_axis.scale(x_scale).orient('bottom').tickValues(dates)
       .tickFormat(Helpers.dateFormat)
+    bottom = chart.append('g').attr('class', 'x axis')

+    # The visit numbers are displayed along the x axis top.
     top_axis = d3.svg.axis()
-    top_axis.scale(x).orient('top').tickValues(dates)
+    top_axis.scale(x_scale).orient('top').tickValues(dates)
       .tickFormat(Helpers.indexFormatter(dates, 1))
-
-    svg = d3.select(element[0]).append('svg')
-      .attr('width', width - 140)
-      .attr('height', height)
-    svg.append('g').attr('class', 'x axis')
-      .attr('transform', 'translate(50, ' + height + ')')
-      .call(bot_axis)
-    top = svg.append('g').attr('class', 'x axis')
-      .attr('transform', 'translate(50, ' + height + ')')
-      .call(top_axis)
+    top = chart.append('g').attr('class', 'x axis')

+    # The chart data structure.
+    config =
+      x:
+        scale: x_scale
+        axes: [
+          {element: bottom, axis: bot_axis}
+          {element: top, axis: top_axis}
+        ]
+
+    # The visit hyperlinks.
     hrefs =
       scope.href(obj) for obj in data
+    # Replace the visit text with hyperlinks.
+    Helpers.d3Hyperlink(top, hrefs)

-    Helpers.d3Hyperlink(top, hrefs)
+    # Make the chart responsive.
+    d3.select(window).on('resize', resize(chart, config))
+
+    # Set the initial chart size.
+    resize(chart, config)

   # Make the directive.
   restrict: 'E'


use ApiErrorHandler on public api

use ApiErrorHandler on public api

--- services/web/app/coffee/infrastructure/Server.coffee
+++ services/web/app/coffee/infrastructure/Server.coffee
@@ -173,6 +173,7 @@
 if enableWebRouter or notDefined(enableWebRouter)
 	logger.info("providing web router");
 	app.use(publicApiRouter) # public API goes with web router for public access
+	app.use(ErrorController.handleApiError)
 	app.use(webRouter)
 	app.use(ErrorController.handleError)


Remove PromiseRenderer from new discussion form

--- app/talk/discussion-new-form.cjsx
+++ app/talk/discussion-new-form.cjsx
@@ -6,7 +6,6 @@
 discussionValidations = require './lib/discussion-validations'
 talkClient = require 'panoptes-client/lib/talk-client'
 Loading = require '../components/loading-indicator'
-PromiseRenderer = require '../components/promise-renderer'
 projectSection = require '../talk/lib/project-section'

 module.exports = React.createClass
@@ -20,6 +19,23 @@
   getInitialState: ->
     discussionValidationErrors: []
     loading: false
+    boards: []
+
+  componentDidMount: ->
+    @updateBoards @props.subject
+
+  componentWillReceiveProps: (newProps) ->
+    @updateBoards newProps.subject if newProps.subject isnt @props.subject
+
+  updateBoards: (subject) ->
+    subject?.get 'project'
+      .then (project) =>
+        talkClient.type 'boards'
+          .get
+            section: projectSection(project)
+            subject_default: false
+          .then (boards) =>
+            @setState {boards}

   discussionValidations: (commentBody) ->
     discussionTitle = ReactDOM.findDOMNode(@).querySelector('.new-discussion-title').value
@@ -72,14 +88,10 @@

Create a discussion +

         {if not @props.boardId
-          <PromiseRenderer promise={@props.subject.get('project')}>{(project) =>
-            <PromiseRenderer promise={talkClient.type('boards').get(section: projectSection(project), subject_default: false)}>{(boards) =>
-

-
Board

-                {boards.map @boardRadio}
-

-            }</PromiseRenderer>
-          }</PromiseRenderer>
+

+
Board

+            {@state.boards.map @boardRadio}
+

           }
         <input
           className="new-discussion-title"


[PUBDEV-5133] Fixed plotting of Scoring History for GLM.

--- src/ext/components/model-output.coffee
+++ src/ext/components/model-output.coffee
@@ -184,11 +184,11 @@
           renderPlot 'Scoring History', no, _.plot (g) ->
             g(
               g.path(
-                g.position 'iteration', 'within_cluster_sum_of_squares'
+                g.position 'iterations', 'within_cluster_sum_of_squares'
                 g.strokeColor g.value '#1f77b4'
               )
               g.point(
-                g.position 'iteration', 'within_cluster_sum_of_squares'
+                g.position 'iterations', 'within_cluster_sum_of_squares'
                 g.strokeColor g.value '#1f77b4'
               )
               g.from table
@@ -223,11 +223,11 @@
             renderPlot 'Scoring History', no, _.plot (g) ->
               g(
                 g.path(
-                  g.position 'iteration', 'objective'
+                  g.position 'iterations', 'objective'
                   g.strokeColor g.value '#1f77b4'
                 )
                 g.point(
-                  g.position 'iteration', 'objective'
+                  g.position 'iterations', 'objective'
                   g.strokeColor g.value '#1f77b4'
                 )
                 g.from table


steroids-copy-www is just a flat out copy of everything

--- tasks/steroids-copy-www.coffee
+++ tasks/steroids-copy-www.coffee
@@ -4,14 +4,14 @@
   grunt.loadNpmTasks "grunt-contrib-copy"
   grunt.loadNpmTasks "grunt-extend-config"

-  grunt.registerTask "steroids-copy-www", "Copy files from www/ to dist/ (except for .scss and .coffee)", ->
+  grunt.registerTask "steroids-copy-www", "Copy files from www/ to dist/ unprocessed", ->

     grunt.extendConfig
       copy:
         www:
-          expand:true
+          expand: true
           cwd: 'www/'
-          src: ['**/*.*', '!**/*.coffee', '!**/*.scss']
+          src: '**/*'
           dest: 'dist/'

     grunt.task.run "copy:www"


Rename _start (used for time) to _startTime to avoid confusion

--- framer/Extras/Preloader.coffee
+++ framer/Extras/Preloader.coffee
@@ -50,7 +50,7 @@
 		get: -> @_mediaLoaded.length / @_media.length or 0

 	@define "time",
-		get: -> (Date.now() - @_start) / 1000
+		get: -> (Date.now() - @_startTime) / 1000

 	@define "isLoading",
 		get: -> @_isLoading
@@ -98,7 +98,7 @@
 		@_isLoading = true
 		@_media = []
 		@_mediaLoaded = []
-		@_start = Date.now()
+		@_startTime = Date.now()

 		@emit("start")


Add missing paren.

Add missing paren.

--- src/application.coffee
+++ src/application.coffee
@@ -42,7 +42,7 @@
 					stream.pipe(JSONStream.parse('error'))
 					.pipe(es.mapSync(reject))
 				else
-					stream.pipe(es.wait((error, text) -> reject(text))
+					stream.pipe(es.wait((error, text) -> reject(text)))

 				stream.on('end', resolve)
 	.then ->


Make sure the PORT env var is a valid port number before trying to bind it.

--- src/application.coffee
+++ src/application.coffee
@@ -65,7 +65,11 @@
 		console.log("Creating container:", app.imageId)
 		ports = {}
 		if app.env.PORT?
-			ports[app.env.PORT + '/tcp'] = {}
+			maybePort = parseInt(app.env.PORT, 10)
+			if parseFloat(app.env.PORT) is maybePort and maybePort > 0 and maybePort < 65535
+				port = maybePort
+		if port?
+			ports[port + '/tcp'] = {}
 		docker.createContainerAsync(
 			Image: app.imageId
 			Cmd: ['/bin/bash', '-c', '/start']
@@ -74,19 +78,19 @@
 			Env: _.map app.env, (v, k) -> k + '=' + v
 			ExposedPorts: ports
 		)
-	.then (container) ->
-		console.log('Starting container:', app.imageId)
-		ports = {}
-		if app.env.PORT?
-			ports[app.env.PORT + '/tcp'] = [ HostPort: app.env.PORT ]
-		container.startAsync(
-			Privileged: true
-			PortBindings: ports
-			Binds: [
-				'/dev:/dev'
-				'/var/run/docker.sock:/run/docker.sock'
-			]
-		)
+		.then (container) ->
+			console.log('Starting container:', app.imageId)
+			ports = {}
+			if port?
+				ports[port + '/tcp'] = [ HostPort: port ]
+			container.startAsync(
+				Privileged: true
+				PortBindings: ports
+				Binds: [
+					'/dev:/dev'
+					'/var/run/docker.sock:/run/docker.sock'
+				]
+			)
 	.tap ->
 		console.log('Started container:', app.imageId)


direct call to journal abbreviator rather than going through citeproc

--- chrome/content/zotero-better-bibtex/keymanager.coffee
+++ chrome/content/zotero-better-bibtex/keymanager.coffee
@@ -1,8 +1,7 @@
 Zotero.BetterBibTeX.keymanager = new class
   constructor: ->
     @log = Zotero.BetterBibTeX.log
-    @journalAbbrevs = Object.create(null)
-    @journalAbbrevsStore = {}
+    @resetJournalAbbrevs()

     @keys = Zotero.BetterBibTeX.Cache.addCollection('keys', {disableChangesApi: false, indices: 'itemID libraryID citekey citekeyFormat'.split(/\s+/) })
     @keys.on('insert', (key) ->
@@ -72,14 +71,29 @@

   reset: ->
     Zotero.DB.query('delete from betterbibtex.keys')
-    @journalAbbrevs = Object.create(null)
+    @resetJournalAbbrevs()
     @keys.removeDataOnly()
     @keys.removeWhere((obj) -> true) # causes cache drop
     @keys.flushChanges()
     @scan()

   resetJournalAbbrevs: ->
-    @journalAbbrevs = Object.create(null)
+    @journalAbbrevs = {
+      default: {
+        "container-title": { },
+        "collection-title": { },
+        "institution-entire": { },
+        "institution-part": { },
+        "nickname": { },
+        "number": { },
+        "title": { },
+        "place": { },
+        "hereinafter": { },
+        "classic": { },
+        "container-phrase": { },
+        "title-phrase": { }
+      }
+    }

   clearDynamic: ->
     @keys.removeWhere((obj) -> obj.citekeyFormat && obj.citekeyFormat != Zotero.BetterBibTeX.citekeyFormat)
@@ -107,8 +121,10 @@
     return unless key
     return unless Zotero.BetterBibTeX.pref.get('autoAbbrev')

-    @journalAbbrevs[key] ?= Zotero.Cite.getAbbreviation(null, @journalAbbrevsStore, 'default', 'container-title', key)
-    return @journalAbbrevs[key]
+    style = Zotero.BetterBibTeX.pref.get('autoAbbrevStyle') || (style for style in Zotero.Styles.getVisible() when style.usesAbbreviation)[0].styleID
+
+    @journalAbbrevs['default']?['container-title']?[key] || Zotero.Cite.getAbbreviation(style, @journalAbbrevs, 'default', 'container-title', key)
+    return @journalAbbrevs['default']?['container-title']?[key] || key

   extract: ->
     [item, insitu] = (if arguments[0]._sandboxManager then Array.slice(arguments, 1) else arguments)


test(autocomplete-picker): Add regression test for useCache bug

--- modules/autocomplete-picker/test/spec.coffee
+++ modules/autocomplete-picker/test/spec.coffee
@@ -288,7 +288,9 @@
       ap._.selection.select('.hx-autocomplete-picker-text').text().should.equal(hx.userFacingText('autocompletePicker', 'chooseValue'))
       should.not.exist(ap.value())

-
+  it 'should pass the "useCache" option to the autocomplete feed', ->
+    testClosedAutocomplete trivialItems, { useCache: false }, (ap) ->
+      ap._.feed._.options.useCache.should.equal(false)

   describe 'api', ->


Add paran to constructor examples

Add paran to constructor examples

--- src/bijous.coffee
+++ src/bijous.coffee
@@ -64,7 +64,7 @@
 # # We define a new {Bijous} instance, using the default bundle configuration.
 # # This causes klect to use the {Bijous.defaultBundleName}, and bijous to
 # # store results without a namespace
-# bijous = new Bijous
+# bijous = new Bijous()
 #
 # # We assume there is a module called *module1*
 # bijous.load (err, modules) ->
@@ -134,7 +134,7 @@
 # Bijous = require 'bijous'
 #
 # # Loads all modules
-# bijous = new Bijous
+# bijous = new Bijous()
 #
 # bijous.load (err, modules) ->
 #   # Access the results of module1
@@ -272,7 +272,7 @@
   # Bijous = require 'bijous'
   #
   # # Loads all modules
-  # bijous = new Bijous
+  # bijous = new Bijous()
   # bijous.load()
   #
   # # Loads only modules belonging to the module1 bundle
@@ -283,7 +283,7 @@
   # bijous.load 'module1'
   #
   # # Loads all modules and executes a callback once all are loaded
-  # bijous = new Bijous
+  # bijous = new Bijous()
   #
   # bijous.load (error, results) ->
   #   throw error if error


fix(unread-icon): Show the correct icon for the action

--- internal_packages/thread-list/lib/thread-buttons.cjsx
+++ internal_packages/thread-list/lib/thread-buttons.cjsx
@@ -89,8 +89,8 @@
     selection: React.PropTypes.object.isRequired

   render: =>
-    canMarkUnread = not @props.selection.items().every (s) -> s.unread is true
-    fragment = if canMarkUnread then "unread" else "read"
+    postClickUnreadState = _.every @props.selection.items(), (t) -> _.isMatch(t, {unread: false})
+    fragment = if postClickUnreadState then "unread" else "read"

     <button style={order:-105}
             className="btn btn-toolbar"


Remove stopAnnotating from public api

Remove stopAnnotating from public api

--- js-library/app/js/loader/loader_common.coffee
+++ js-library/app/js/loader/loader_common.coffee
@@ -12,7 +12,7 @@

   # Create proxy object that stores all calls
   # proxies calls from external content page to the js-library "jail" iframe's "FactlinkJailRoot"..
-  methods = 'on,triggerClick,highlightAdditionalFactlinks,stopAnnotating,showLoadedNotification,scrollTo,openFactlinkModal,initializeFactlinkButton,showProxyMessage'.split(',')
+  methods = 'on,triggerClick,highlightAdditionalFactlinks,showLoadedNotification,scrollTo,openFactlinkModal,initializeFactlinkButton,showProxyMessage'.split(',')

   storedMethodCalls = []


handling signing up after integration global/store changes

--- app/assets/javascripts/admin/routers/integrations.js.coffee
+++ app/assets/javascripts/admin/routers/integrations.js.coffee
@@ -43,8 +43,12 @@
   signup: (integration_id) ->
     Augury.update_nav('integrations')

-    integration = _.findWhere Augury.integrations.models,
+    integration = _.findWhere Augury.global_integrations.models,
       id: integration_id
+
+    unless integration?
+      integration = _.findWhere Augury.store_integrations.models,
+        id: integration_id

     view = new Augury.Views.Integrations.Signup(model: integration)
     $("#integration_main").html view.render().el


今日を押した時にカレンダーがロードされない不具合を修正

今日を押した時にカレンダーがロードされない不具合を修正

--- app/assets/javascripts/components/logs/log_calendar.js.cjsx
+++ app/assets/javascripts/components/logs/log_calendar.js.cjsx
@@ -36,6 +36,8 @@
       @onClickPrevNext()
     $('.fc-next-button').on 'click', =>
       @onClickPrevNext()
+    $('.fc-today-button').on 'click', =>
+      @onClickPrevNext()

   onClickPrevNext: ->
     dates = $('.fc-left').text().replace(/年/g, '').replace('月', '').split(' ')


#309 - copy paste typo

#309 - copy paste typo

--- jsapp/xlform_model_view/view.surveyApp.coffee
+++ jsapp/xlform_model_view/view.surveyApp.coffee
@@ -142,10 +142,10 @@
         @$(".js-expand-multioptions--all").click () ->
           if @expand_all_multioptions
             @expand_all_multioptions = false
-            $(".card card--selectquestion").removeClass("card--expandedchoices")
+            $(".card.card--selectquestion").removeClass("card--expandedchoices")
           else
             @expand_all_multioptions = true
-            $(".card card--selectquestion").addClass("card--expandedchoices")
+            $(".card.card--selectquestion").addClass("card--expandedchoices")

       @


OX-248 fixed typo

OX-248 fixed typo

--- app/assets/javascripts/oxalis/model/binary/pullqueue.coffee
+++ app/assets/javascripts/oxalis/model/binary/pullqueue.coffee
@@ -205,7 +205,7 @@
     @socket8Bit = new ArrayBufferSocket(
       senders : [
         new ArrayBufferSocket.WebWorker("ws://#{document.location.host}/binary/ws?dataSetId=#{@dataSetId}&halfByte=0&cubeSize=#{1 << @cube.BUCKET_SIZE_P}")
-        <new ArrayBufferSocket.WebSocket("ws://#{document.location.host}/binary/ws?dataSetId=#{@dataSetId}&halfByte=0&cubeSize=#{1 << @cube.BUCKET_SIZE_P}")
+        new ArrayBufferSocket.WebSocket("ws://#{document.location.host}/binary/ws?dataSetId=#{@dataSetId}&halfByte=0&cubeSize=#{1 << @cube.BUCKET_SIZE_P}")
         new ArrayBufferSocket.XmlHttpRequest("/binary/ajax?dataSetId=#{@dataSetId}&halfByte=0&cubeSize=#{1 << @cube.BUCKET_SIZE_P}")
       ]
       requestBufferType : Float32Array


enable fourbit mode for all layers, #569

--- app/assets/javascripts/oxalis/model/binary/pullqueue.coffee
+++ app/assets/javascripts/oxalis/model/binary/pullqueue.coffee
@@ -128,7 +128,7 @@

   shouldRequestFourBit : (zoomStep) ->

-    return @fourBit and zoomStep == 0 and @layer.category == "color"
+    return @fourBit and @layer.category == "color"

   getLoadSocket : ->


Fix exceeded line length

Fix exceeded line length

--- lib/linter/clang.coffee
+++ lib/linter/clang.coffee
@@ -77,7 +77,7 @@

     userClangPath = atom.config.get('atom-lint.clang.path')
     userHeaderSearchPaths = atom.config.get('atom-lint.clang.headerSearchPaths')
-    addConfigToDiscoveredFlags = atom.config.get('atom-lint.clang.mergeAtomLintConfigIntoAutoDiscoveredFlags')
+    alwaysUseConfig = atom.config.get('atom-lint.clang.mergeAtomLintConfigIntoAutoDiscoveredFlags')

     if userClangPath?
       command.push(userClangPath)
@@ -91,7 +91,7 @@

     if currentFileFlags.length > 0
       command = command.concat currentFileFlags
-    if currentFileFlags.length == 0 || addConfigToDiscoveredFlags
+    if currentFileFlags.length == 0 || alwaysUseConfig
       command.push('-Wall')

       if userHeaderSearchPaths?


inital injector

inital injector

--- lib/local_server.coffee
+++ lib/local_server.coffee
@@ -6,13 +6,40 @@
   constructor: (@root) ->

   start: (port) ->
-    app     = connect().use(connect.static(@root + "/public"))
+    app     = connect()
+    app.use(@reloadInjector).use(connect.static(@root + "/public"))
     @server = http.createServer(app)
     nodefn.call(@server.listen.bind(@server), port).yield(@server)

   close: ->
     @server.close()
     delete @server
+
+  reloadInjector: (req, res, next) =>
+    w         = res.write
+    e         = res.end
+    injected  = false
+
+    res.write = (buffer, encoding) =>
+      res.write = w
+      if buffer?
+        string = buffer.toString(encoding)
+        if ~string.indexOf('<body>')
+          @injectLocalScript(string, encoding, res)
+          return injected = true
+
+        return res.write(buffer, encoding)
+      true
+
+    res.end = (string, encoding) ->
+      res.end = e
+      res.setHeader('content-length', Buffer.byteLength(res.data, encoding)) if injected
+      res.end res.data, encoding
+
+    next()
+
+  injectLocalScript: (string, encoding, res) ->
+    res.data = (res.data || '') + string.replace(/<\/body>/, (w) -> "<script>console.log('hello from the server');</script> #{w}")

 ###