Commit 241c17a8 authored by Dennis Schubert's avatar Dennis Schubert

Merge branch 'release/0.6.2.0'

parents b88f53a3 ea35061f
......@@ -3,3 +3,4 @@
--color
--tag ~performance
--order random
--require spec_helper
# 0.6.2.0
## Refactor
* Use string-direction gem for rtl detection [#7181](https://github.com/diaspora/diaspora/pull/7181)
* Reduce i18n.load side effects [#7184](https://github.com/diaspora/diaspora/pull/7184)
* Force jasmine fails on syntax errors [#7185](https://github.com/diaspora/diaspora/pull/7185)
* Don't display mail-related view content if it is disabled in the pod's config [#7190](https://github.com/diaspora/diaspora/pull/7190)
* Use typeahead.js from rails-assets.org [#7192](https://github.com/diaspora/diaspora/pull/7192)
* Refactor ShareVisibilitesController to use PostService [#7196](https://github.com/diaspora/diaspora/pull/7196)
* Unify desktop and mobile head elements [#7194](https://github.com/diaspora/diaspora/pull/7194) [#7209](https://github.com/diaspora/diaspora/pull/7209)
* Refactor flash messages on ajax errors for comments, likes, reshares and aspect memberships [#7202](https://github.com/diaspora/diaspora/pull/7202)
* Only require AWS-module for fog [#7201](https://github.com/diaspora/diaspora/pull/7201)
* Only show community spotlight links on the contacts page if community spotlight is enabled [#7213](https://github.com/diaspora/diaspora/pull/7213)
* Require spec\_helper in .rspec [#7223](https://github.com/diaspora/diaspora/pull/7223)
* Make the CSRF mail a bit more friendly [#7238](https://github.com/diaspora/diaspora/pull/7238) [#7241](https://github.com/diaspora/diaspora/pull/7241)
## Bug fixes
* Fix fetching comments after fetching likes [#7167](https://github.com/diaspora/diaspora/pull/7167)
* Hide 'reshare' button on already reshared posts [#7169](https://github.com/diaspora/diaspora/pull/7169)
* Only reload profile header when changing aspect memberships [#7183](https://github.com/diaspora/diaspora/pull/7183)
* Fix visiblity on invitation modal when opening it from the stream [#7191](https://github.com/diaspora/diaspora/pull/7191)
* Add avatar fallback on tags page [#7198](https://github.com/diaspora/diaspora/pull/7198)
* Update notifications when changing the stream [#7199](https://github.com/diaspora/diaspora/pull/7199)
* Fix 500 on mobile commented and liked streams [#7219](https://github.com/diaspora/diaspora/pull/7219)
## Features
* Show spinner when loading comments in the stream [#7170](https://github.com/diaspora/diaspora/pull/7170)
* Add a dark color theme [#7152](https://github.com/diaspora/diaspora/pull/7152)
* Added setting for custom changelog URL [#7166](https://github.com/diaspora/diaspora/pull/7166)
* Show more information of recipients on conversation creation [#7129](https://github.com/diaspora/diaspora/pull/7129)
* Update notifications every 5 minutes and when opening the notification dropdown [#6952](https://github.com/diaspora/diaspora/pull/6952)
* Show browser notifications when receiving new unread notifications [#6952](https://github.com/diaspora/diaspora/pull/6952)
* Only clear comment textarea when comment submission was successful [#7186](https://github.com/diaspora/diaspora/pull/7186)
* Add support for graceful unicorn restarts [#7217](https://github.com/diaspora/diaspora/pull/7217)
# 0.6.1.0
Note: Although this is a minor release, the configuration file changed because the old Mapbox implementation is no longer valid, and the current implementation requires additional fields. Chances are high that if you're using the old integration, it will be broken anyway. If you do use Mapbox, please check out the `diaspora.yml.example` for new parameters.
......
......@@ -25,7 +25,6 @@ gem "json-schema", "2.7.0"
gem "devise", "4.2.0"
gem "devise_lastseenable", "0.0.6"
gem "devise-token_authenticatable", "0.5.2"
# Captcha
......@@ -73,8 +72,8 @@ gem "activerecord-import", "0.15.0"
# File uploading
gem "fog", "1.38.0", require: "fog/aws"
gem "carrierwave", "0.11.2"
gem "fog", "1.38.0"
gem "mini_magick", "4.5.1"
# GUID generation
......@@ -105,6 +104,7 @@ source "https://rails-assets.org" do
gem "rails-assets-markdown-it-sup", "1.0.0"
gem "rails-assets-highlightjs", "9.7.0"
gem "rails-assets-bootstrap-markdown", "2.10.0"
gem "rails-assets-corejs-typeahead", "1.0.1"
# jQuery plugins
......@@ -136,6 +136,10 @@ gem "twitter-text", "1.14.0"
gem "ruby-oembed", "0.10.1"
gem "open_graph_reader", "0.6.1"
# RTL support
gem "string-direction", "1.2.0"
# Security Headers
gem "secure_headers", "3.5.0"
......
......@@ -171,8 +171,6 @@ GEM
railties (>= 4.1.0, < 5.1)
responders
warden (~> 1.2.3)
devise-token_authenticatable (0.5.2)
devise (>= 4.0.0, < 4.3.0)
devise_lastseenable (0.0.6)
devise
rails (>= 3.0.4)
......@@ -648,6 +646,8 @@ GEM
rails-assets-jquery (>= 1.9.1, < 4)
rails-assets-bootstrap-markdown (2.10.0)
rails-assets-bootstrap (~> 3)
rails-assets-corejs-typeahead (1.0.1)
rails-assets-jquery (>= 1.7)
rails-assets-diaspora_jsxc (0.1.5.develop.7)
rails-assets-emojione (~> 2.0.1)
rails-assets-favico.js (>= 0.3.10, < 0.4)
......@@ -821,6 +821,8 @@ GEM
activesupport (>= 3.0)
sprockets (>= 2.8, < 4.0)
state_machine (1.2.0)
string-direction (1.2.0)
yard (~> 0.8)
swd (1.0.1)
activesupport (>= 3)
attr_required (>= 0.0.5)
......@@ -931,7 +933,6 @@ DEPENDENCIES
cucumber-rails (= 1.4.5)
database_cleaner (= 1.5.3)
devise (= 4.2.0)
devise-token_authenticatable (= 0.5.2)
devise_lastseenable (= 0.0.6)
diaspora-prosody-config (= 0.0.7)
diaspora_federation-rails (= 0.1.5)
......@@ -998,6 +999,7 @@ DEPENDENCIES
rails-assets-autosize (= 3.0.17)!
rails-assets-blueimp-gallery (= 2.21.3)!
rails-assets-bootstrap-markdown (= 2.10.0)!
rails-assets-corejs-typeahead (= 1.0.1)!
rails-assets-diaspora_jsxc (= 0.1.5.develop.7)!
rails-assets-highlightjs (= 9.7.0)!
rails-assets-jasmine-ajax (= 3.2.0)!
......@@ -1034,6 +1036,7 @@ DEPENDENCIES
spring (= 2.0.0)
spring-commands-cucumber (= 1.0.1)
spring-commands-rspec (= 1.0.4)
string-direction (= 1.2.0)
test_after_commit (= 1.1.0)
timecop (= 0.8.1)
turbo_dev_assets (= 0.0.2)
......@@ -1049,4 +1052,4 @@ DEPENDENCIES
will_paginate (= 3.1.5)
BUNDLED WITH
1.13.5
1.13.6
......@@ -90,6 +90,7 @@ var app = {
setupHeader: function() {
if(app.currentUser.authenticated()) {
app.notificationsCollection = new app.collections.Notifications();
app.header = new app.views.Header();
$("header").prepend(app.header.el);
app.header.render();
......@@ -114,6 +115,7 @@ var app = {
// so we use Backbone.history.navigate instead.
var change = Backbone.history.navigate(link.attr("href").substring(1) ,true);
if(change === undefined) { Backbone.history.loadUrl(link.attr("href").substring(1)); }
app.notificationsCollection.fetch();
});
},
......
app.collections.Notifications = Backbone.Collection.extend({
model: app.models.Notification,
// URL parameter
/* eslint-disable camelcase */
url: Routes.notifications({per_page: 10, page: 1}),
/* eslint-enable camelcase */
page: 2,
perPage: 5,
unreadCount: 0,
unreadCountByType: {},
timeout: 300000, // 5 minutes
initialize: function() {
this.fetch();
setInterval(this.pollNotifications.bind(this), this.timeout);
Diaspora.BrowserNotification.requestPermission();
},
pollNotifications: function() {
var unreadCountBefore = this.unreadCount;
this.fetch();
this.once("finishedLoading", function() {
if (unreadCountBefore < this.unreadCount) {
Diaspora.BrowserNotification.spawnNotification(
Diaspora.I18n.t("notifications.new_notifications", {count: this.unreadCount}));
}
}, this);
},
fetch: function(options) {
options = options || {};
options.remove = false;
options.merge = true;
options.parse = true;
Backbone.Collection.prototype.fetch.apply(this, [options]);
},
fetchMore: function() {
var hasMoreNotifications = (this.page * this.perPage) <= this.length;
// There are more notifications to load on the current page
if (hasMoreNotifications) {
this.page++;
// URL parameter
/* eslint-disable camelcase */
var route = Routes.notifications({per_page: this.perPage, page: this.page});
/* eslint-enable camelcase */
this.fetch({url: route, pushBack: true});
}
},
/**
* Adds new models to the collection at the end or at the beginning of the collection and
* then fires an event for each model of the collection. It will fire a different event
* based on whether the models were added at the end (typically when the scroll triggers to load more
* notifications) or at the beginning (new notifications have been added to the front of the list).
*/
set: function(items, options) {
options = options || {};
options.at = options.pushBack ? this.length : 0;
// Retreive back the new created models
var models = [];
var accu = function(model) { models.push(model); };
this.on("add", accu);
Backbone.Collection.prototype.set.apply(this, [items, options]);
this.off("add", accu);
if (options.pushBack) {
models.forEach(function(model) { this.trigger("pushBack", model); }.bind(this));
} else {
// Fires events in the reverse order so that the first event is prepended in first position
models.reverse();
models.forEach(function(model) { this.trigger("pushFront", model); }.bind(this));
}
this.trigger("finishedLoading");
},
parse: function(response) {
this.unreadCount = response.unread_count;
this.unreadCountByType = response.unread_count_by_type;
return _.map(response.notification_list, function(item) {
/* eslint-disable new-cap */
var model = new this.model(item);
/* eslint-enable new-cap */
model.on("change:unread", this.onChangedUnreadStatus.bind(this));
return model;
}.bind(this));
},
setAllRead: function() {
this.forEach(function(model) { model.setRead(); });
},
setRead: function(guid) {
this.find(function(model) { return model.guid === guid; }).setRead();
},
setUnread: function(guid) {
this.find(function(model) { return model.guid === guid; }).setUnread();
},
onChangedUnreadStatus: function(model) {
if (model.get("unread") === true) {
this.unreadCount++;
this.unreadCountByType[model.get("type")]++;
} else {
this.unreadCount = Math.max(this.unreadCount - 1, 0);
this.unreadCountByType[model.get("type")] = Math.max(this.unreadCountByType[model.get("type")] - 1, 0);
}
this.trigger("update");
}
});
......@@ -2,6 +2,9 @@
app.collections.Reshares = Backbone.Collection.extend({
model: app.models.Reshare,
url : "/reshares"
initialize: function(models, options) {
this.url = "/posts/" + options.post.id + "/reshares";
}
});
// @license-end
app.models.Notification = Backbone.Model.extend({
constructor: function(attributes, options) {
options = options || {};
options.parse = true;
Backbone.Model.apply(this, [attributes, options]);
this.guid = this.get("id");
},
/**
* Flattens the notification object returned by the server.
*
* The server returns an object that looks like:
*
* {
* "reshared": {
* "id": 45,
* "target_type": "Post",
* "target_id": 11,
* "recipient_id": 1,
* "unread": true,
* "created_at": "2015-10-27T19:56:30.000Z",
* "updated_at": "2015-10-27T19:56:30.000Z",
* "note_html": <html/>
* },
* "type": "reshared"
* }
*
* The returned object looks like:
*
* {
* "type": "reshared",
* "id": 45,
* "target_type": "Post",
* "target_id": 11,
* "recipient_id": 1,
* "unread": true,
* "created_at": "2015-10-27T19:56:30.000Z",
* "updated_at": "2015-10-27T19:56:30.000Z",
* "note_html": <html/>,
* }
*/
parse: function(response) {
var result = {type: response.type};
result = $.extend(result, response[result.type]);
return result;
},
setRead: function() {
this.setUnreadStatus(false);
},
setUnread: function() {
this.setUnreadStatus(true);
},
setUnreadStatus: function(state) {
if (this.get("unread") !== state) {
$.ajax({
url: Routes.notification(this.guid),
/* eslint-disable camelcase */
data: {set_unread: state},
/* eslint-enable camelcase */
type: "PUT",
context: this,
success: function() { this.set("unread", state); }
});
}
}
});
......@@ -70,9 +70,10 @@ app.models.Post.Interactions = Backbone.Model.extend({
self.post.set({participation: true});
self.trigger("change");
self.set({"likes_count" : self.get("likes_count") + 1});
self.likes.trigger("change");
},
error: function() {
app.flashMessages.error(Diaspora.I18n.t("failed_to_like"));
error: function(model, response) {
app.flashMessages.handleAjaxError(response);
}
});
......@@ -84,23 +85,26 @@ app.models.Post.Interactions = Backbone.Model.extend({
this.userLike().destroy({success : function() {
self.trigger('change');
self.set({"likes_count" : self.get("likes_count") - 1});
self.likes.trigger("change");
}});
app.instrument("track", "Unlike");
},
comment : function (text) {
comment: function(text, options) {
var self = this;
options = options || {};
this.comments.make(text).fail(function () {
app.flashMessages.error(Diaspora.I18n.t("failed_to_comment"));
this.comments.make(text).fail(function(response) {
app.flashMessages.handleAjaxError(response);
if (options.error) { options.error(); }
}).done(function() {
self.post.set({participation: true});
self.set({"comments_count": self.get("comments_count") + 1});
self.trigger('change'); //updates after sync
if (options.success) { options.success(); }
});
this.trigger("change"); //updates count in an eager manner
app.instrument("track", "Comment");
},
......@@ -116,9 +120,11 @@ app.models.Post.Interactions = Backbone.Model.extend({
app.stream.addNow(reshare);
}
interactions.trigger("change");
interactions.set({"reshares_count": interactions.get("reshares_count") + 1});
interactions.reshares.trigger("change");
})
.fail(function(){
app.flashMessages.error(Diaspora.I18n.t("reshares.duplicate"));
.fail(function(response) {
app.flashMessages.handleAjaxError(response);
});
app.instrument("track", "Reshare");
......
......@@ -79,6 +79,9 @@ app.pages.Contacts = Backbone.View.extend({
},
showMessageModal: function(){
$("#conversationModal").on("modal:loaded", function() {
new app.views.ConversationsForm({prefill: gon.conversationPrefill});
});
app.helpers.showModal("#conversationModal");
},
......
......@@ -31,7 +31,6 @@ app.pages.Profile = app.views.Base.extend({
this.streamCollection = _.has(opts, "streamCollection") ? opts.streamCollection : null;
this.streamViewClass = _.has(opts, "streamView") ? opts.streamView : null;
this.model.on("change", this.render, this);
this.model.on("sync", this._done, this);
// bind to global events
......
......@@ -139,7 +139,7 @@ app.Router = Backbone.Router.extend({
notifications: function() {
this._loadContacts();
this.renderAspectMembershipDropdowns($(document));
new app.views.Notifications({el: "#notifications_container"});
new app.views.Notifications({el: "#notifications_container", collection: app.notificationsCollection});
},
peopleSearch: function() {
......
......@@ -125,7 +125,7 @@ app.views.AspectMembership = app.views.Base.extend({
_displayError: function(model, resp) {
this._done();
this.dropdown.closest(".aspect_membership_dropdown").removeClass("open"); // close the dropdown
app.flashMessages.error(resp.responseText);
app.flashMessages.handleAjaxError(resp);
},
// remove the membership with the given id
......@@ -134,7 +134,7 @@ app.views.AspectMembership = app.views.Base.extend({
this.listenToOnce(membership, "sync", this._successDestroyCb);
this.listenToOnce(membership, "error", this._displayError);
return membership.destroy();
return membership.destroy({wait: true});
},
_successDestroyCb: function(aspectMembership) {
......
......@@ -25,6 +25,8 @@ app.views.CommentStream = app.views.Base.extend({
postRenderTemplate : function() {
this.model.comments.each(this.appendComment, this);
this.commentBox = this.$(".comment_box");
this.commentSubmitButton = this.$("input[name='commit']");
},
presenter: function(){
......@@ -38,15 +40,35 @@ app.views.CommentStream = app.views.Base.extend({
createComment: function(evt) {
if(evt){ evt.preventDefault(); }