Merge branch 'release/0.6.2.0'

This commit is contained in:
Dennis Schubert 2016-12-14 00:10:01 +01:00
commit 241c17a814
No known key found for this signature in database
GPG key ID: 5A0304BEA7966D7E
522 changed files with 7040 additions and 5318 deletions

1
.rspec
View file

@ -3,3 +3,4 @@
--color --color
--tag ~performance --tag ~performance
--order random --order random
--require spec_helper

View file

@ -1,3 +1,38 @@
# 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 # 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. 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.

View file

@ -25,7 +25,6 @@ gem "json-schema", "2.7.0"
gem "devise", "4.2.0" gem "devise", "4.2.0"
gem "devise_lastseenable", "0.0.6" gem "devise_lastseenable", "0.0.6"
gem "devise-token_authenticatable", "0.5.2"
# Captcha # Captcha
@ -73,8 +72,8 @@ gem "activerecord-import", "0.15.0"
# File uploading # File uploading
gem "fog", "1.38.0", require: "fog/aws"
gem "carrierwave", "0.11.2" gem "carrierwave", "0.11.2"
gem "fog", "1.38.0"
gem "mini_magick", "4.5.1" gem "mini_magick", "4.5.1"
# GUID generation # GUID generation
@ -105,6 +104,7 @@ source "https://rails-assets.org" do
gem "rails-assets-markdown-it-sup", "1.0.0" gem "rails-assets-markdown-it-sup", "1.0.0"
gem "rails-assets-highlightjs", "9.7.0" gem "rails-assets-highlightjs", "9.7.0"
gem "rails-assets-bootstrap-markdown", "2.10.0" gem "rails-assets-bootstrap-markdown", "2.10.0"
gem "rails-assets-corejs-typeahead", "1.0.1"
# jQuery plugins # jQuery plugins
@ -136,6 +136,10 @@ gem "twitter-text", "1.14.0"
gem "ruby-oembed", "0.10.1" gem "ruby-oembed", "0.10.1"
gem "open_graph_reader", "0.6.1" gem "open_graph_reader", "0.6.1"
# RTL support
gem "string-direction", "1.2.0"
# Security Headers # Security Headers
gem "secure_headers", "3.5.0" gem "secure_headers", "3.5.0"

View file

@ -171,8 +171,6 @@ GEM
railties (>= 4.1.0, < 5.1) railties (>= 4.1.0, < 5.1)
responders responders
warden (~> 1.2.3) warden (~> 1.2.3)
devise-token_authenticatable (0.5.2)
devise (>= 4.0.0, < 4.3.0)
devise_lastseenable (0.0.6) devise_lastseenable (0.0.6)
devise devise
rails (>= 3.0.4) rails (>= 3.0.4)
@ -648,6 +646,8 @@ GEM
rails-assets-jquery (>= 1.9.1, < 4) rails-assets-jquery (>= 1.9.1, < 4)
rails-assets-bootstrap-markdown (2.10.0) rails-assets-bootstrap-markdown (2.10.0)
rails-assets-bootstrap (~> 3) 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-diaspora_jsxc (0.1.5.develop.7)
rails-assets-emojione (~> 2.0.1) rails-assets-emojione (~> 2.0.1)
rails-assets-favico.js (>= 0.3.10, < 0.4) rails-assets-favico.js (>= 0.3.10, < 0.4)
@ -821,6 +821,8 @@ GEM
activesupport (>= 3.0) activesupport (>= 3.0)
sprockets (>= 2.8, < 4.0) sprockets (>= 2.8, < 4.0)
state_machine (1.2.0) state_machine (1.2.0)
string-direction (1.2.0)
yard (~> 0.8)
swd (1.0.1) swd (1.0.1)
activesupport (>= 3) activesupport (>= 3)
attr_required (>= 0.0.5) attr_required (>= 0.0.5)
@ -931,7 +933,6 @@ DEPENDENCIES
cucumber-rails (= 1.4.5) cucumber-rails (= 1.4.5)
database_cleaner (= 1.5.3) database_cleaner (= 1.5.3)
devise (= 4.2.0) devise (= 4.2.0)
devise-token_authenticatable (= 0.5.2)
devise_lastseenable (= 0.0.6) devise_lastseenable (= 0.0.6)
diaspora-prosody-config (= 0.0.7) diaspora-prosody-config (= 0.0.7)
diaspora_federation-rails (= 0.1.5) diaspora_federation-rails (= 0.1.5)
@ -998,6 +999,7 @@ DEPENDENCIES
rails-assets-autosize (= 3.0.17)! rails-assets-autosize (= 3.0.17)!
rails-assets-blueimp-gallery (= 2.21.3)! rails-assets-blueimp-gallery (= 2.21.3)!
rails-assets-bootstrap-markdown (= 2.10.0)! 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-diaspora_jsxc (= 0.1.5.develop.7)!
rails-assets-highlightjs (= 9.7.0)! rails-assets-highlightjs (= 9.7.0)!
rails-assets-jasmine-ajax (= 3.2.0)! rails-assets-jasmine-ajax (= 3.2.0)!
@ -1034,6 +1036,7 @@ DEPENDENCIES
spring (= 2.0.0) spring (= 2.0.0)
spring-commands-cucumber (= 1.0.1) spring-commands-cucumber (= 1.0.1)
spring-commands-rspec (= 1.0.4) spring-commands-rspec (= 1.0.4)
string-direction (= 1.2.0)
test_after_commit (= 1.1.0) test_after_commit (= 1.1.0)
timecop (= 0.8.1) timecop (= 0.8.1)
turbo_dev_assets (= 0.0.2) turbo_dev_assets (= 0.0.2)
@ -1049,4 +1052,4 @@ DEPENDENCIES
will_paginate (= 3.1.5) will_paginate (= 3.1.5)
BUNDLED WITH BUNDLED WITH
1.13.5 1.13.6

View file

@ -90,6 +90,7 @@ var app = {
setupHeader: function() { setupHeader: function() {
if(app.currentUser.authenticated()) { if(app.currentUser.authenticated()) {
app.notificationsCollection = new app.collections.Notifications();
app.header = new app.views.Header(); app.header = new app.views.Header();
$("header").prepend(app.header.el); $("header").prepend(app.header.el);
app.header.render(); app.header.render();
@ -114,6 +115,7 @@ var app = {
// so we use Backbone.history.navigate instead. // so we use Backbone.history.navigate instead.
var change = Backbone.history.navigate(link.attr("href").substring(1) ,true); var change = Backbone.history.navigate(link.attr("href").substring(1) ,true);
if(change === undefined) { Backbone.history.loadUrl(link.attr("href").substring(1)); } if(change === undefined) { Backbone.history.loadUrl(link.attr("href").substring(1)); }
app.notificationsCollection.fetch();
}); });
}, },

View file

@ -0,0 +1,114 @@
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");
}
});

View file

@ -2,6 +2,9 @@
app.collections.Reshares = Backbone.Collection.extend({ app.collections.Reshares = Backbone.Collection.extend({
model: app.models.Reshare, model: app.models.Reshare,
url : "/reshares"
initialize: function(models, options) {
this.url = "/posts/" + options.post.id + "/reshares";
}
}); });
// @license-end // @license-end

View file

@ -0,0 +1,69 @@
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); }
});
}
}
});

View file

@ -70,9 +70,10 @@ app.models.Post.Interactions = Backbone.Model.extend({
self.post.set({participation: true}); self.post.set({participation: true});
self.trigger("change"); self.trigger("change");
self.set({"likes_count" : self.get("likes_count") + 1}); self.set({"likes_count" : self.get("likes_count") + 1});
self.likes.trigger("change");
}, },
error: function() { error: function(model, response) {
app.flashMessages.error(Diaspora.I18n.t("failed_to_like")); app.flashMessages.handleAjaxError(response);
} }
}); });
@ -84,23 +85,26 @@ app.models.Post.Interactions = Backbone.Model.extend({
this.userLike().destroy({success : function() { this.userLike().destroy({success : function() {
self.trigger('change'); self.trigger('change');
self.set({"likes_count" : self.get("likes_count") - 1}); self.set({"likes_count" : self.get("likes_count") - 1});
self.likes.trigger("change");
}}); }});
app.instrument("track", "Unlike"); app.instrument("track", "Unlike");
}, },
comment : function (text) { comment: function(text, options) {
var self = this; var self = this;
options = options || {};
this.comments.make(text).fail(function () { this.comments.make(text).fail(function(response) {
app.flashMessages.error(Diaspora.I18n.t("failed_to_comment")); app.flashMessages.handleAjaxError(response);
if (options.error) { options.error(); }
}).done(function() { }).done(function() {
self.post.set({participation: true}); self.post.set({participation: true});
self.set({"comments_count": self.get("comments_count") + 1});
self.trigger('change'); //updates after sync self.trigger('change'); //updates after sync
if (options.success) { options.success(); }
}); });
this.trigger("change"); //updates count in an eager manner
app.instrument("track", "Comment"); app.instrument("track", "Comment");
}, },
@ -116,9 +120,11 @@ app.models.Post.Interactions = Backbone.Model.extend({
app.stream.addNow(reshare); app.stream.addNow(reshare);
} }
interactions.trigger("change"); interactions.trigger("change");
interactions.set({"reshares_count": interactions.get("reshares_count") + 1});
interactions.reshares.trigger("change");
}) })
.fail(function(){ .fail(function(response) {
app.flashMessages.error(Diaspora.I18n.t("reshares.duplicate")); app.flashMessages.handleAjaxError(response);
}); });
app.instrument("track", "Reshare"); app.instrument("track", "Reshare");

View file

@ -79,6 +79,9 @@ app.pages.Contacts = Backbone.View.extend({
}, },
showMessageModal: function(){ showMessageModal: function(){
$("#conversationModal").on("modal:loaded", function() {
new app.views.ConversationsForm({prefill: gon.conversationPrefill});
});
app.helpers.showModal("#conversationModal"); app.helpers.showModal("#conversationModal");
}, },

View file

@ -31,7 +31,6 @@ app.pages.Profile = app.views.Base.extend({
this.streamCollection = _.has(opts, "streamCollection") ? opts.streamCollection : null; this.streamCollection = _.has(opts, "streamCollection") ? opts.streamCollection : null;
this.streamViewClass = _.has(opts, "streamView") ? opts.streamView : null; this.streamViewClass = _.has(opts, "streamView") ? opts.streamView : null;
this.model.on("change", this.render, this);
this.model.on("sync", this._done, this); this.model.on("sync", this._done, this);
// bind to global events // bind to global events

View file

@ -139,7 +139,7 @@ app.Router = Backbone.Router.extend({
notifications: function() { notifications: function() {
this._loadContacts(); this._loadContacts();
this.renderAspectMembershipDropdowns($(document)); this.renderAspectMembershipDropdowns($(document));
new app.views.Notifications({el: "#notifications_container"}); new app.views.Notifications({el: "#notifications_container", collection: app.notificationsCollection});
}, },
peopleSearch: function() { peopleSearch: function() {

View file

@ -125,7 +125,7 @@ app.views.AspectMembership = app.views.Base.extend({
_displayError: function(model, resp) { _displayError: function(model, resp) {
this._done(); this._done();
this.dropdown.closest(".aspect_membership_dropdown").removeClass("open"); // close the dropdown 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 // 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, "sync", this._successDestroyCb);
this.listenToOnce(membership, "error", this._displayError); this.listenToOnce(membership, "error", this._displayError);
return membership.destroy(); return membership.destroy({wait: true});
}, },
_successDestroyCb: function(aspectMembership) { _successDestroyCb: function(aspectMembership) {

View file

@ -25,6 +25,8 @@ app.views.CommentStream = app.views.Base.extend({
postRenderTemplate : function() { postRenderTemplate : function() {
this.model.comments.each(this.appendComment, this); this.model.comments.each(this.appendComment, this);
this.commentBox = this.$(".comment_box");
this.commentSubmitButton = this.$("input[name='commit']");
}, },
presenter: function(){ presenter: function(){
@ -38,15 +40,35 @@ app.views.CommentStream = app.views.Base.extend({
createComment: function(evt) { createComment: function(evt) {
if(evt){ evt.preventDefault(); } if(evt){ evt.preventDefault(); }
var commentText = $.trim(this.$('.comment_box').val()); var commentText = $.trim(this.commentBox.val());
this.$(".comment_box").val(""); if (commentText === "") {
this.$(".comment_box").css("height", ""); this.commentBox.focus();
if(commentText) { return;
this.model.comment(commentText);
return this;
} else {
this.$(".comment_box").focus();
} }
this.disableCommentBox();
this.model.comment(commentText, {
success: function() {
this.commentBox.val("");
this.enableCommentBox();
autosize.update(this.commentBox);
}.bind(this),
error: function() {
this.enableCommentBox();
this.commentBox.focus();
}.bind(this)
});
},
disableCommentBox: function() {
this.commentBox.prop("disabled", true);
this.commentSubmitButton.prop("disabled", true);
},
enableCommentBox: function() {
this.commentBox.removeAttr("disabled");
this.commentSubmitButton.removeAttr("disabled");
}, },
keyDownOnCommentBox: function(evt) { keyDownOnCommentBox: function(evt) {
@ -104,10 +126,12 @@ app.views.CommentStream = app.views.Base.extend({
}, },
expandComments: function(evt){ expandComments: function(evt){
this.$(".loading-comments").removeClass("hidden");
if(evt){ evt.preventDefault(); } if(evt){ evt.preventDefault(); }
this.model.comments.fetch({ this.model.comments.fetch({
success: function() { success: function() {
this.$("div.comment.show_comments").addClass("hidden"); this.$("div.comment.show_comments").addClass("hidden");
this.$(".loading-comments").addClass("hidden");
}.bind(this) }.bind(this)
}); });
} }

View file

@ -5,40 +5,83 @@ app.views.ConversationsForm = Backbone.View.extend({
events: { events: {
"keydown .conversation-message-text": "keyDown", "keydown .conversation-message-text": "keyDown",
"click .conversation-recipient-tag .remove": "removeRecipient"
}, },
initialize: function(opts) { initialize: function(opts) {
this.contacts = _.has(opts, "contacts") ? opts.contacts : null; opts = opts || {};
this.prefill = []; this.conversationRecipients = [];
if (_.has(opts, "prefillName") && _.has(opts, "prefillValue")) {
this.prefill = [{name: opts.prefillName, value: opts.prefillValue}]; this.typeaheadElement = this.$el.find("#contacts-search-input");
this.contactsIdsListInput = this.$el.find("#contact-ids");
this.tagListElement = this.$("#recipients-tag-list");
this.search = new app.views.SearchBase({
el: this.$el.find("#new-conversation"),
typeaheadInput: this.typeaheadElement,
customSearch: true,
autoselect: true,
remoteRoute: {url: "/contacts", extraParameters: "mutual=true"}
});
this.bindTypeaheadEvents();
this.tagListElement.empty();
if (opts.prefill) {
this.prefill(opts.prefill);
} }
this.prepareAutocomplete(this.contacts);
this.$("form#new-conversation").on("ajax:success", this.conversationCreateSuccess); this.$("form#new-conversation").on("ajax:success", this.conversationCreateSuccess);
this.$("form#new-conversation").on("ajax:error", this.conversationCreateError); this.$("form#new-conversation").on("ajax:error", this.conversationCreateError);
}, },
prepareAutocomplete: function(data){ addRecipient: function(person) {
this.$("#contact-autocomplete").autoSuggest(data, { this.conversationRecipients.push(person);
selectedItemProp: "name", this.updateContactIdsListInput();
searchObjProps: "name", /* eslint-disable camelcase */
asHtmlID: "contact_ids", this.tagListElement.append(HandlebarsTemplates.conversation_recipient_tag_tpl(person));
retrieveLimit: 10, /* eslint-enable camelcase */
minChars: 1,
keyDelay: 0,
startText: '',
emptyText: Diaspora.I18n.t("no_results"),
preFill: this.prefill
});
$("#contact_ids").attr("aria-labelledby", "toLabel").focus();
}, },
keyDown : function(evt) { prefill: function(handles) {
if(evt.which === Keycodes.ENTER && evt.ctrlKey) { handles.forEach(this.addRecipient.bind(this));
},
updateContactIdsListInput: function() {
this.contactsIdsListInput.val(_(this.conversationRecipients).pluck("id").join(","));
this.search.ignoreDiasporaIds.length = 0;
this.conversationRecipients.forEach(this.search.ignorePersonForSuggestions.bind(this.search));
},
bindTypeaheadEvents: function() {
this.typeaheadElement.on("typeahead:select", function(evt, person) {
this.onSuggestionSelection(person);
}.bind(this));
},
onSuggestionSelection: function(person) {
this.addRecipient(person);
this.typeaheadElement.typeahead("val", "");
},
keyDown: function(evt) {
if (evt.which === Keycodes.ENTER && evt.ctrlKey) {
$(evt.target).parents("form").submit(); $(evt.target).parents("form").submit();
} }
}, },
removeRecipient: function(evt) {
var $recipientTagEl = $(evt.target).parents(".conversation-recipient-tag");
var diasporaHandle = $recipientTagEl.data("diaspora-handle");
this.conversationRecipients = this.conversationRecipients.filter(function(person) {
return diasporaHandle !== person.handle;
});
this.updateContactIdsListInput();
$recipientTagEl.remove();
},
conversationCreateSuccess: function(evt, data) { conversationCreateSuccess: function(evt, data) {
app._changeLocation(Routes.conversation(data.id)); app._changeLocation(Routes.conversation(data.id));
}, },

View file

@ -9,7 +9,7 @@ app.views.ConversationsInbox = Backbone.View.extend({
}, },
initialize: function() { initialize: function() {
new app.views.ConversationsForm({contacts: gon.contacts}); new app.views.ConversationsForm();
this.setupConversation(); this.setupConversation();
}, },

View file

@ -16,5 +16,13 @@ app.views.FlashMessages = app.views.Base.extend({
error: function(message){ error: function(message){
this._flash(message, true); this._flash(message, true);
},
handleAjaxError: function(response) {
if (response.status === 0) {
this.error(Diaspora.I18n.t("errors.connection"));
} else {
this.error(response.responseText);
}
} }
}); });

View file

@ -12,12 +12,12 @@ app.views.Header = app.views.Base.extend({
}); });
}, },
postRenderTemplate: function(){ postRenderTemplate: function() {
new app.views.Notifications({ el: "#notification-dropdown" }); new app.views.Notifications({el: "#notification-dropdown", collection: app.notificationsCollection});
this.notificationDropdown = new app.views.NotificationDropdown({ el: "#notification-dropdown" }); new app.views.NotificationDropdown({el: "#notification-dropdown", collection: app.notificationsCollection});
new app.views.Search({ el: "#header-search-form" }); new app.views.Search({el: "#header-search-form"});
}, },
menuElement: function(){ return this.$("ul.dropdown"); }, menuElement: function() { return this.$("ul.dropdown"); }
}); });
// @license-end // @license-end

View file

@ -11,7 +11,7 @@ app.views.LikesInfo = app.views.Base.extend({
tooltipSelector : ".avatar", tooltipSelector : ".avatar",
initialize : function() { initialize : function() {
this.model.interactions.bind('change', this.render, this); this.model.interactions.likes.on("change", this.render, this);
this.displayAvatars = false; this.displayAvatars = false;
}, },
@ -19,18 +19,16 @@ app.views.LikesInfo = app.views.Base.extend({
return _.extend(this.defaultPresenter(), { return _.extend(this.defaultPresenter(), {
likes : this.model.interactions.likes.toJSON(), likes : this.model.interactions.likes.toJSON(),
likesCount : this.model.interactions.likesCount(), likesCount : this.model.interactions.likesCount(),
displayAvatars : this.model.interactions.get("fetched") && this.displayAvatars displayAvatars: this.displayAvatars
}); });
}, },
showAvatars : function(evt){ showAvatars : function(evt){
if(evt) { evt.preventDefault() } if(evt) { evt.preventDefault() }
this.displayAvatars = true; this.displayAvatars = true;
if(!this.model.interactions.get("fetched")){ this.model.interactions.likes.fetch({success: function() {
this.model.interactions.fetch(); this.model.interactions.likes.trigger("change");
} else { }.bind(this)});
this.model.interactions.trigger("change");
}
} }
}); });
// @license-end // @license-end

View file

@ -6,16 +6,21 @@ app.views.NotificationDropdown = app.views.Base.extend({
}, },
initialize: function(){ initialize: function(){
$(document.body).click($.proxy(this.hideDropdown, this)); $(document.body).click(this.hideDropdown.bind(this));
this.notifications = [];
this.perPage = 5;
this.hasMoreNotifs = true;
this.badge = this.$el; this.badge = this.$el;
this.dropdown = $("#notification-dropdown"); this.dropdown = $("#notification-dropdown");
this.dropdownNotifications = this.dropdown.find(".notifications"); this.dropdownNotifications = this.dropdown.find(".notifications");
this.ajaxLoader = this.dropdown.find(".ajax-loader"); this.ajaxLoader = this.dropdown.find(".ajax-loader");
this.perfectScrollbarInitialized = false; this.perfectScrollbarInitialized = false;
this.dropdownNotifications.scroll(this.dropdownScroll.bind(this));
this.bindCollectionEvents();
},
bindCollectionEvents: function() {
this.collection.on("pushFront", this.onPushFront.bind(this));
this.collection.on("pushBack", this.onPushBack.bind(this));
this.collection.on("finishedLoading", this.finishLoading.bind(this));
}, },
toggleDropdown: function(evt){ toggleDropdown: function(evt){
@ -31,12 +36,11 @@ app.views.NotificationDropdown = app.views.Base.extend({
}, },
showDropdown: function(){ showDropdown: function(){
this.resetParams();
this.ajaxLoader.show(); this.ajaxLoader.show();
this.dropdown.addClass("dropdown-open"); this.dropdown.addClass("dropdown-open");
this.updateScrollbar(); this.updateScrollbar();
this.dropdownNotifications.addClass("loading"); this.dropdownNotifications.addClass("loading");
this.getNotifications(); this.collection.fetch();
}, },
hideDropdown: function(evt){ hideDropdown: function(evt){
@ -50,40 +54,18 @@ app.views.NotificationDropdown = app.views.Base.extend({
dropdownScroll: function(){ dropdownScroll: function(){
var isLoading = ($(".loading").length === 1); var isLoading = ($(".loading").length === 1);
if (this.isBottom() && this.hasMoreNotifs && !isLoading){ if (this.isBottom() && !isLoading) {
this.dropdownNotifications.addClass("loading"); this.dropdownNotifications.addClass("loading");
this.getNotifications(); this.collection.fetchMore();
} }
}, },
getParams: function(){
if(this.notifications.length === 0){ return{ per_page: 10, page: 1 }; }
else{ return{ per_page: this.perPage, page: this.nextPage }; }
},
resetParams: function(){
this.notifications.length = 0;
this.hasMoreNotifs = true;
delete this.nextPage;
},
isBottom: function(){ isBottom: function(){
var bottom = this.dropdownNotifications.prop("scrollHeight") - this.dropdownNotifications.height(); var bottom = this.dropdownNotifications.prop("scrollHeight") - this.dropdownNotifications.height();
var currentPosition = this.dropdownNotifications.scrollTop(); var currentPosition = this.dropdownNotifications.scrollTop();
return currentPosition + 50 >= bottom; return currentPosition + 50 >= bottom;
}, },
getNotifications: function(){
var self = this;
$.getJSON(Routes.notifications(this.getParams()), function(notifications){
$.each(notifications, function(){ self.notifications.push(this); });
self.hasMoreNotifs = notifications.length >= self.perPage;
if(self.nextPage){ self.nextPage++; }
else { self.nextPage = 3; }
self.renderNotifications();
});
},
hideAjaxLoader: function(){ hideAjaxLoader: function(){
var self = this; var self = this;
this.ajaxLoader.find(".spinner").fadeTo(200, 0, function(){ this.ajaxLoader.find(".spinner").fadeTo(200, 0, function(){
@ -93,28 +75,23 @@ app.views.NotificationDropdown = app.views.Base.extend({
}); });
}, },
renderNotifications: function(){ onPushBack: function(notification) {
var self = this; var node = this.dropdownNotifications.append(notification.get("note_html"));
this.dropdownNotifications.find(".media.stream-element").remove(); $(node).find(".unread-toggle .entypo-eye").tooltip("destroy").tooltip();
$.each(self.notifications, function(index, notifications){ $(node).find(this.avatars.selector).error(this.avatars.fallback);
$.each(notifications, function(index, notification){ },
if($.inArray(notification, notifications) === -1){
var node = self.dropdownNotifications.append(notification.note_html);
$(node).find(".unread-toggle .entypo-eye").tooltip("destroy").tooltip();
$(node).find(self.avatars.selector).error(self.avatars.fallback);
}
});
});
this.hideAjaxLoader(); onPushFront: function(notification) {
var node = this.dropdownNotifications.prepend(notification.get("note_html"));
$(node).find(".unread-toggle .entypo-eye").tooltip("destroy").tooltip();
$(node).find(this.avatars.selector).error(this.avatars.fallback);
},
finishLoading: function() {
app.helpers.timeago(this.dropdownNotifications); app.helpers.timeago(this.dropdownNotifications);
this.updateScrollbar(); this.updateScrollbar();
this.hideAjaxLoader();
this.dropdownNotifications.removeClass("loading"); this.dropdownNotifications.removeClass("loading");
this.dropdownNotifications.scroll(function(){
self.dropdownScroll();
});
}, },
updateScrollbar: function() { updateScrollbar: function() {

View file

@ -1,96 +1,85 @@
// @license magnet:?xt=urn:btih:0b31508aeb0634b347b8270c7bee4d411b5d4109&dn=agpl-3.0.txt AGPL-v3-or-Later // @license magnet:?xt=urn:btih:0b31508aeb0634b347b8270c7bee4d411b5d4109&dn=agpl-3.0.txt AGPL-v3-or-Later
app.views.Notifications = Backbone.View.extend({ app.views.Notifications = Backbone.View.extend({
events: { events: {
"click .unread-toggle" : "toggleUnread", "click .unread-toggle": "toggleUnread",
"click #mark_all_read_link": "markAllRead" "click #mark-all-read-link": "markAllRead"
}, },
initialize: function() { initialize: function() {
$(".unread-toggle .entypo-eye").tooltip(); $(".unread-toggle .entypo-eye").tooltip();
app.helpers.timeago($(document)); app.helpers.timeago($(document));
this.bindCollectionEvents();
},
bindCollectionEvents: function() {
this.collection.on("change", this.onChangedUnreadStatus.bind(this));
this.collection.on("update", this.updateView.bind(this));
}, },
toggleUnread: function(evt) { toggleUnread: function(evt) {
var note = $(evt.target).closest(".stream-element"); var note = $(evt.target).closest(".stream-element");
var unread = note.hasClass("unread"); var unread = note.hasClass("unread");
var guid = note.data("guid"); var guid = note.data("guid");
if (unread){ this.setRead(guid); } if (unread) {
else { this.setUnread(guid); } this.collection.setRead(guid);
}, } else {
this.collection.setUnread(guid);
getAllUnread: function() { return $(".media.stream-element.unread"); },
setRead: function(guid) { this.setUnreadStatus(guid, false); },
setUnread: function(guid){ this.setUnreadStatus(guid, true); },
setUnreadStatus: function(guid, state){
$.ajax({
url: "/notifications/" + guid,
data: { set_unread: state },
type: "PUT",
context: this,
success: this.clickSuccess
});
},
clickSuccess: function(data) {
var guid = data.guid;
var type = $(".stream-element[data-guid=" + guid + "]").data("type");
this.updateView(guid, type, data.unread);
},
markAllRead: function(evt){
if(evt) { evt.preventDefault(); }
var self = this;
this.getAllUnread().each(function(i, el){
self.setRead($(el).data("guid"));
});
},
updateView: function(guid, type, unread) {
var change = unread ? 1 : -1,
allNotes = $("#notifications_container .list-group > a:eq(0) .badge"),
typeNotes = $("#notifications_container .list-group > a[data-type=" + type + "] .badge"),
headerBadge = $(".notifications-link .badge"),
note = $(".notifications .stream-element[data-guid=" + guid + "]"),
markAllReadLink = $("a#mark_all_read_link"),
translationKey = unread ? "notifications.mark_read" : "notifications.mark_unread";
if(unread){ note.removeClass("read").addClass("unread"); }
else { note.removeClass("unread").addClass("read"); }
$(".unread-toggle .entypo-eye", note)
.tooltip("destroy")
.removeAttr("data-original-title")
.attr("title",Diaspora.I18n.t(translationKey))
.tooltip();
[allNotes, typeNotes, headerBadge].forEach(function(element){
element.text(function(i, text){
return parseInt(text) + change;
});
});
[allNotes, typeNotes].forEach(function(badge) {
if(badge.text() > 0) {
badge.removeClass("hidden");
}
else {
badge.addClass("hidden");
}
});
if(headerBadge.text() > 0){
headerBadge.removeClass("hidden");
markAllReadLink.removeClass("disabled");
} }
else{ },
headerBadge.addClass("hidden");
markAllRead: function() {
this.collection.setAllRead();
},
onChangedUnreadStatus: function(model) {
var unread = model.get("unread");
var translationKey = unread ? "notifications.mark_read" : "notifications.mark_unread";
var note = $(".stream-element[data-guid=" + model.guid + "]");
note.find(".entypo-eye")
.tooltip("destroy")
.removeAttr("data-original-title")
.attr("title", Diaspora.I18n.t(translationKey))
.tooltip();
if (unread) {
note.removeClass("read").addClass("unread");
} else {
note.removeClass("unread").addClass("read");
}
},
updateView: function() {
var notificationsContainer = $("#notifications_container");
// update notification counts in the sidebar
Object.keys(this.collection.unreadCountByType).forEach(function(notificationType) {
var count = this.collection.unreadCountByType[notificationType];
this.updateBadge(notificationsContainer.find("a[data-type=" + notificationType + "] .badge"), count);
}.bind(this));
this.updateBadge(notificationsContainer.find("a[data-type=all] .badge"), this.collection.unreadCount);
// update notification count in the header
this.updateBadge($(".notifications-link .badge"), this.collection.unreadCount);
var markAllReadLink = $("a#mark-all-read-link");
if (this.collection.unreadCount > 0) {
markAllReadLink.removeClass("disabled");
} else {
markAllReadLink.addClass("disabled"); markAllReadLink.addClass("disabled");
} }
},
updateBadge: function(badge, count) {
badge.text(count);
if (count > 0) {
badge.removeClass("hidden");
} else {
badge.addClass("hidden");
}
} }
}); });
// @license-end // @license-end

View file

@ -15,6 +15,7 @@ app.views.ProfileHeader = app.views.Base.extend({
initialize: function(opts) { initialize: function(opts) {
this.photos = _.has(opts, 'photos') ? opts.photos : null; this.photos = _.has(opts, 'photos') ? opts.photos : null;
this.contacts = _.has(opts, 'contacts') ? opts.contacts : null; this.contacts = _.has(opts, 'contacts') ? opts.contacts : null;
this.model.on("change", this.render, this);
$("#mentionModal").on("modal:loaded", this.mentionModalLoaded.bind(this)); $("#mentionModal").on("modal:loaded", this.mentionModalLoaded.bind(this));
$("#mentionModal").on("hidden.bs.modal", this.mentionModalHidden); $("#mentionModal").on("hidden.bs.modal", this.mentionModalHidden);
}, },
@ -79,8 +80,11 @@ app.views.ProfileHeader = app.views.Base.extend({
}, },
showMessageModal: function(){ showMessageModal: function(){
$("#conversationModal").on("modal:loaded", function() {
new app.views.ConversationsForm({prefill: gon.conversationPrefill});
});
app.helpers.showModal("#conversationModal"); app.helpers.showModal("#conversationModal");
}, }
}); });
// @license-end // @license-end

View file

@ -32,7 +32,7 @@ app.views.PublisherMention = app.views.SearchBase.extend({
typeaheadInput: this.typeaheadInput, typeaheadInput: this.typeaheadInput,
customSearch: true, customSearch: true,
autoselect: true, autoselect: true,
remoteRoute: "/contacts" remoteRoute: {url: "/contacts"}
}); });
}, },

View file

@ -11,7 +11,7 @@ app.views.ResharesInfo = app.views.Base.extend({
tooltipSelector : ".avatar", tooltipSelector : ".avatar",
initialize : function() { initialize : function() {
this.model.interactions.bind("change", this.render, this); this.model.interactions.reshares.bind("change", this.render, this);
this.displayAvatars = false; this.displayAvatars = false;
}, },
@ -19,18 +19,16 @@ app.views.ResharesInfo = app.views.Base.extend({
return _.extend(this.defaultPresenter(), { return _.extend(this.defaultPresenter(), {
reshares : this.model.interactions.reshares.toJSON(), reshares : this.model.interactions.reshares.toJSON(),
resharesCount : this.model.interactions.resharesCount(), resharesCount : this.model.interactions.resharesCount(),
displayAvatars : this.model.interactions.get("fetched") && this.displayAvatars displayAvatars: this.displayAvatars
}); });
}, },
showAvatars : function(evt){ showAvatars : function(evt){
if(evt) { evt.preventDefault() } if(evt) { evt.preventDefault() }
this.displayAvatars = true; this.displayAvatars = true;
if(!this.model.interactions.get("fetched")){ this.model.interactions.reshares.fetch({success: function() {
this.model.interactions.fetch(); this.model.interactions.reshares.trigger("change");
} else { }.bind(this)});
this.model.interactions.trigger("change");
}
} }
}); });
// @license-end // @license-end

View file

@ -28,9 +28,13 @@ app.views.SearchBase = app.views.Base.extend({
}; };
// Allow bloodhound to look for remote results if there is a route given in the options // Allow bloodhound to look for remote results if there is a route given in the options
if(options.remoteRoute) { if (options.remoteRoute && options.remoteRoute.url) {
var extraParameters = "";
if (options.remoteRoute.extraParameters) {
extraParameters += "&" + options.remoteRoute.extraParameters;
}
bloodhoundOptions.remote = { bloodhoundOptions.remote = {
url: options.remoteRoute + ".json?q=%QUERY", url: options.remoteRoute.url + ".json?q=%QUERY" + extraParameters,
wildcard: "%QUERY", wildcard: "%QUERY",
transform: this.transformBloodhoundResponse.bind(this) transform: this.transformBloodhoundResponse.bind(this)
}; };

View file

@ -10,7 +10,7 @@ app.views.Search = app.views.SearchBase.extend({
this.searchInput = this.$("#q"); this.searchInput = this.$("#q");
app.views.SearchBase.prototype.initialize.call(this, { app.views.SearchBase.prototype.initialize.call(this, {
typeaheadInput: this.searchInput, typeaheadInput: this.searchInput,
remoteRoute: this.$el.attr("action"), remoteRoute: {url: this.$el.attr("action")},
suggestionLink: true suggestionLink: true
}); });
this.searchInput.on("typeahead:select", this.suggestionSelected); this.searchInput.on("typeahead:select", this.suggestionSelected);

View file

@ -5,6 +5,8 @@ app.views.Tags = Backbone.View.extend({
if(app.publisher) { if(app.publisher) {
app.publisher.setText("#"+ opts.hashtagName + " "); app.publisher.setText("#"+ opts.hashtagName + " ");
} }
// add avatar fallback if it can't be loaded
$(app.views.Base.prototype.avatars.selector).error(app.views.Base.prototype.avatars.fallback);
} }
}); });
// @license-end // @license-end

View file

@ -0,0 +1,22 @@
Diaspora.BrowserNotification = {
requestPermission: function() {
if ("Notification" in window && Notification.permission !== "granted" && Notification.permission !== "denied") {
Notification.requestPermission();
}
},
spawnNotification: function(title, summary) {
if ("Notification" in window && Notification.permission === "granted") {
if (!_.isString(title)) {
throw new Error("No notification title given.");
}
summary = summary || "";
new Notification(title, {
body: summary,
icon: ImagePaths.get("branding/logos/asterisk_white_mobile.png")
});
}
}
};

View file

@ -18,7 +18,7 @@ Diaspora.I18n = {
}, },
updateLocale: function(locale, data) { updateLocale: function(locale, data) {
locale.data = $.extend(locale.data, data); locale.data = $.extend({}, locale.data, data);
var rule = locale.data.pluralization_rule; var rule = locale.data.pluralization_rule;
if (typeof rule !== "undefined") { if (typeof rule !== "undefined") {

View file

@ -12,7 +12,7 @@
// initialize jsxc xmpp client // initialize jsxc xmpp client
$(document).ready(function() { $(document).ready(function() {
if (app.currentUser.authenticated()) { if (app.currentUser.authenticated()) {
$.post('api/v1/tokens', null, function(data) { $.post("/user/auth_token", null, function(data) {
if (jsxc && data['token']) { if (jsxc && data['token']) {
var jid = app.currentUser.get('diaspora_id'); var jid = app.currentUser.get('diaspora_id');
jsxc.init({ jsxc.init({

View file

@ -30,7 +30,7 @@
//= require markdown-it-sup //= require markdown-it-sup
//= require highlightjs //= require highlightjs
//= require clear-form //= require clear-form
//= require typeahead.bundle.js //= require corejs-typeahead
//= require app/app //= require app/app
//= require diaspora //= require diaspora
//= require_tree ./helpers //= require_tree ./helpers

View file

@ -98,8 +98,12 @@
success: function() { success: function() {
Diaspora.Mobile.PostActions.toggleActive(link); Diaspora.Mobile.PostActions.toggleActive(link);
}, },
error: function() { error: function(response) {
alert(Diaspora.I18n.t("failed_to_reshare")); if (response.status === 0) {
alert(Diaspora.I18n.t("errors.connection"));
} else {
alert(response.responseText);
}
}, },
complete: function() { complete: function() {
Diaspora.Mobile.PostActions.hideLoader(link); Diaspora.Mobile.PostActions.hideLoader(link);

View file

@ -187,9 +187,9 @@ $btn-success-color: #333 !default;
// $input-bg-disabled: $gray-lighter // $input-bg-disabled: $gray-lighter
//** Text color for `<input>`s //** Text color for `<input>`s
// $input-color: $gray $input-color: $text-dark-grey !default;
//** `<input>` border color //** `<input>` border color
// $input-border: #ccc $input-border: $border-grey !default;
// TODO: Rename `$input-border-radius` to `$input-border-radius-base` in v4 // TODO: Rename `$input-border-radius` to `$input-border-radius-base` in v4
//** Default `.form-control` border radius //** Default `.form-control` border radius
@ -202,6 +202,7 @@ $btn-success-color: #333 !default;
//** Border color for inputs on focus //** Border color for inputs on focus
// $input-border-focus: #66afe9 // $input-border-focus: #66afe9
$input-border-focus: $input-border !default;
//** Placeholder text color //** Placeholder text color
// $input-color-placeholder: #999 // $input-color-placeholder: #999
@ -668,7 +669,7 @@ $navbar-collapse-max-height: 480px;
// //
//## //##
//** Background color on `.list-group-item` //** Background color on `.list-group-item`
$list-group-bg: $white; $list-group-bg: $white !default;
//** `.list-group-item` border color //** `.list-group-item` border color
$list-group-border: transparent; $list-group-border: transparent;
//** List group border radius //** List group border radius

View file

@ -3,31 +3,32 @@ $black: #000;
$text-grey: #999; $text-grey: #999;
$text-dark-grey: #666; $text-dark-grey: #666;
$text: #333; $text-color-pale: $text-grey !default;
$text-color-active: $black !default;
$background-white: $white; $background-grey: #eee !default;
$background-grey: #eee; $background-blue: #e7f2f7 !default;
$background-blue: #e7f2f7;
$grey: #2b2b2b; $grey: #2b2b2b;
$medium-gray: #ccc; $medium-gray: #ccc;
$light-grey: #ddd; $light-grey: #ddd;
$border-grey: $light-grey; $border-grey: $light-grey !default;
$border-medium-grey: $medium-gray; $border-medium-grey: $medium-gray !default;
$border-dark-grey: $text-grey; $border-dark-grey: $text-grey !default;
$border-medium-grey: #ccc;
$link-grey: #777; $link-grey: #777;
$link-disabled-grey: $text-grey;
$green: #8ede3d; $icon-color: $black !default;
$green: #8ede3d !default;
$light-green: lighten($green, 20%); $light-green: lighten($green, 20%);
$red: #a80000; $red: #a80000 !default;
$blue: #3f8fba; $blue: #3f8fba !default;
$main-background: #f0f0f0 !default; $main-background: darken($white, 6%) !default;
$sidebars-background: $background-white !default; $framed-background: $white !default;
$left-navbar-drawer-background: darken($sidebars-background, 6%); $left-navbar-drawer-background: darken($white, 6%) !default;
$hovercard-background: $white !default;
$card-shadow: 0 1px 2px 0 rgba(0, 0, 0, .16), 0 2px 10px 0 rgba(0, 0, 0, .12) !default; $card-shadow: 0 1px 2px 0 rgba(0, 0, 0, .16), 0 2px 10px 0 rgba(0, 0, 0, .12) !default;

View file

@ -0,0 +1,170 @@
// Only overriding existing selectors here, so disable some lint rules
// scss-lint:disable IdSelector, SelectorFormat, NestingDepth, SelectorDepth, QualifyingElement
body {
.navbar.navbar-fixed-top #user_menu .dropdown-menu > li > a {
color: $text-color;
&:hover { color: $white; }
}
.publisher {
.mentions-input-box { background-color: $gray; }
form {
#publisher_textarea_wrapper { background-color: $gray; }
.btn.btn-link.question_mark:hover .entypo-cog { color: $gray-light; }
}
.write-preview-tabs > li.active * { color: $text-color; }
.md-preview { background-color: $gray; }
.md-cancel:hover .entypo-cross { color: $gray-light; }
.publisher-buttonbar .btn.btn-link:hover i { color: $gray-light; }
}
.aspect_dropdown li a .text { color: $dropdown-link-color; }
.info .tag { background-color: $gray-light; }
.poll_form .progress {
background-color: $gray-dark;
.bar { background-color: $gray-light; }
}
.stream-element .collapsible {
.markdown-content hr { border-top: 1px solid $hr-border; }
.expander {
@include linear-gradient(transparent, $gray-light, 0%, 95%);
border-bottom: 2px solid $gray-light;
color: $text-color;
text-shadow: 0 0 7px $black;
}
}
code,
pre {
background-color: $gray-dark;
border: 1px solid $border-medium-grey;
color: $text-color;
}
pre code { border: 0; }
@import 'highlightjs/darcula';
#single-post-content .head {
#post-info .author { color: lighten($gray-lighter, 27%); }
#single-post-actions i.entypo-heart.red:hover { color: $red; }
}
.opengraph a { color: lighten($gray-lighter, 27%); }
.tag:hover { background-color: desaturate(darken($blue, 35%), 20%); }
#profile_container .profile_header {
#author_info #sharing_message.entypo-check { color: lighten($green, 10%); }
}
#invitationsModal #email_invitation { border-top: 1px dashed $gray-light; }
#contacts_container #people_stream.contacts .stream-element.in_aspect {
background-color: $state-success-bg;
border-left: 3px solid darken($state-success-bg, 10%);
}
.left-navbar #tags_list {
.as-list {
color: $text-color;
em {
background-color: lighten($background-blue, 10%);
color: $text-color;
}
}
.as-result-item.active { color: $text-color; }
}
#faq .question {
background-color: $gray-dark;
a.toggle { color: $gray-lighter; }
&.collapsed { border: 2px solid $gray-dark; }
&.opened {
border: 2px solid darken($green, 10%);
h4 { background-color: darken($green, 10%); }
}
.answer { background-color: $gray; }
}
#welcome-to-diaspora { background: $orange; }
.block-form fieldset .form-control:focus { border-color: $input-border; }
&.page-registrations.action-new,
&.page-registrations.action-create {
.ball { filter: invert(100%); }
}
.spinner { border-color: $gray-light transparent $gray-light $gray-light; }
// AutoSuggest CSS
ul.as-selections {
background-color: $framed-background;
li.as-selection-item,
li.as-selection-item.blur {
background-color: $gray-dark;
border: 1px solid $gray-darker;
box-shadow: 0 1px 1px $gray-darker;
color: $text-color;
text-shadow: 0 1px 1px $gray-darker;
}
li.as-selection-item a.as-close,
li.as-selection-item.blur a.as-close {
color: $text-color;
text-shadow: 0 1px 1px $gray-darker;
}
li:hover.as-selection-item {
background-color: $light-blue;
border-color: $brand-primary;
color: $white;
a.as-close { color: $gray-light; }
}
li.as-selection-item.selected { border-color: $brand-primary; }
li.as-selection-item a:hover.as-close { color: $white; }
li.as-selection-item a:active.as-close { color: $gray-lighter; }
}
ul.as-list {
background-color: $gray-dark;
box-shadow: 0 2px 12px $gray-light;
color: $text-color;
}
li.as-result-item,
li.as-message {
border: 1px solid $gray-dark;
}
li.as-result-item.active {
background-color: $brand-primary;
border-color: $brand-primary;
text-shadow: none;
em { background: darken($brand-primary, 10%); }
}
// End AutoSuggest CSS
// Bootstrap Switch CSS
.bootstrap-switch {
border-color: $border-grey;
.bootstrap-switch-label { background: $framed-background; }
.bootstrap-switch-handle-on.bootstrap-switch-default,
.bootstrap-switch-handle-off.bootstrap-switch-default {
background: $gray-dark;
color: $text-color;
}
}
// End Bootstrap Switch CSS
}

View file

@ -17,10 +17,4 @@ body {
.left-navbar { .left-navbar {
border-right: 1px solid $border-grey; border-right: 1px solid $border-grey;
} }
.right-sidebar-fixed-background,
.right-sidebar-fixed-background,
.rightbar {
border-left: 1px solid $sidebars-background;
}
} }

View file

@ -0,0 +1,153 @@
// Main color(s)
$white: #fff;
$black: #000;
$gray-base: $black;
$gray-darker: lighten($gray-base, 6%);
$gray-dark: lighten($gray-base, 9.5%);
$gray: lighten($gray-base, 13.5%);
$gray-light: lighten($gray-base, 28%);
$gray-lighter: lighten($gray-base, 58%);
$green: #346535;
$red: #622;
$blue: #4183c4;
$yellow: #645a1b;
$orange: #664100;
$light-blue: lighten($blue, 5%);
$brand-primary: darken($blue, 5%);
$brand-success: $green;
$brand-info: darken(adjust-hue($brand-primary, -30), 15%);
$brand-danger: lighten($red, 10%);
// Bootstrap Variables
//== Scaffolding
$body-bg: $gray;
$text-color: lighten($gray-lighter, 17%);
$link-color: $blue;
//== Tables
$table-bg-accent: $gray-dark;
$table-border-color: $gray-light;
//== Buttons
$btn-default-color: $gray-lighter;
$btn-default-bg: $gray-light;
$btn-default-border: $gray-darker;
$btn-success-color: $white;
//== Forms
$input-bg: $gray-dark;
$input-color: $text-color;
$input-border: $gray-light;
$input-border-focus: $brand-primary;
$input-color-placeholder: lighten($gray-light, 7%);
$legend-color: $text-color;
$legend-border-color: $gray-light;
//== Dropdowns
$dropdown-bg: lighten($gray-base, 15%);
$dropdown-divider-bg: $gray-darker;
$dropdown-link-color: $text-color;
$dropdown-link-hover-color: $dropdown-link-color;
//== Navbar
$navbar-inverse-bg: $gray-darker;
$navbar-inverse-link-hover-color: $text-color;
$navbar-inverse-brand-hover-color: $navbar-inverse-link-hover-color;
//== Tabs
$nav-tabs-active-link-hover-bg: $gray;
$nav-tabs-active-link-hover-border-color: $gray-darker;
//== Navs
$nav-link-hover-bg: $gray-darker;
//== Pagination
$pagination-color: $light-blue;
$pagination-bg: $gray-light;
$pagination-border: $gray-darker;
$pagination-hover-color: $gray-dark;
$pagination-hover-bg: $light-blue;
$pagination-hover-border: $pagination-border;
$pagination-active-border: $pagination-border;
$pagination-disabled-color: $gray-dark;
$pagination-disabled-bg: $gray-light;
$pagination-disabled-border: $pagination-border;
//== Form states and alerts
$state-success-text: lighten($green, 30%);
$state-success-bg: darken($green, 10%);
$state-success-border: darken($state-success-bg, 20%);
$state-info-text: lighten($blue, 20%);
$state-info-bg: darken($blue, 20%);
$state-info-border: darken($state-info-bg, 20%);
$state-warning-text: lighten($yellow, 30%);
$state-warning-bg: $yellow;
$state-warning-border: darken($state-warning-bg, 20%);
$state-danger-text: lighten($red, 40%);
$state-danger-bg: $red;
$state-danger-border: darken($state-danger-bg, 20%);
//== Popovers
$popover-bg: lighten($gray, 5%);
$popover-border-color: $gray-darker;
//== Modals
$modal-content-bg: $gray;
$modal-header-border-color: $gray-light;
//== List group
$list-group-bg: $gray;
$list-group-link-color: $text-color;
//== Panels
$panel-bg: $gray;
$panel-default-text: $text-color;
$panel-default-border: $gray-darker;
$panel-default-heading-bg: $gray-dark;
//== Thumbnails
$thumbnail-border: $gray-darker;
//== Wells
$well-bg: $gray-dark;
//== Close
$close-color: $gray-lighter;
//== Type
$hr-border: $gray-light;
// Variables
$text-color-pale: $gray-light;
$text-color-active: lighten($gray-lighter, 27%);
$background-grey: $gray-dark;
$background-blue: desaturate(darken($blue, 25%), 15%);
$border-grey: $gray-darker;
$border-medium-grey: $gray-light;
$border-dark-grey: darken($border-grey, 4.5%);
$icon-color: $text-color;
$main-background: $gray-dark;
$framed-background: $gray;
$left-navbar-drawer-background: $main-background;
$hovercard-background: $gray;
@import 'color_themes/color_theme_override_dark';

View file

@ -0,0 +1,3 @@
@import 'mixins';
@import 'color_themes/dark/style';
@import 'application';

View file

@ -0,0 +1,63 @@
@import 'mixins';
@import 'color_themes/dark/style';
// Only overriding existing selectors here, so disable some lint rules
// scss-lint:disable SelectorFormat, NestingDepth, SelectorDepth
body {
.settings-container,
.stream-element,
.login-form {
border: 1px solid $border-grey;
}
.stream-element,
.comments {
.from a { color: $text-color; }
.info { color: lighten($gray-light, 12%); }
.nsfw-shield { background-color: $gray-light; }
.bottom-bar {
background: lighten($framed-background, 4.5%);
.post-action .disabled { color: $text-color-pale; }
.post-stats .count { background-color: lighten($framed-background, 4.5%); }
}
.reshare {
border-bottom: 1px solid $border-medium-grey;
.reshare_via span { color: $border-medium-grey; }
}
}
.more-link,
.no-more-posts {
background: { color: $btn-default-bg; }
border: 1px solid $gray;
h1,
h2 {
color: $text-color;
text-shadow: 0 2px 0 $gray;
}
}
.stream-element.unread { background-color: $gray; }
.stream-element.read { background-color: $gray-darker; }
.header-full-width { border-bottom: 1px solid $border-grey; }
.user_aspects {
&,
&:focus,
&:active {
border-color: $gray-light;
}
&.has_connection {
background-color: $green;
color: $white;
}
}
}
// scss-lint:enable IdSelector, SelectorFormat, NestingDepth, SelectorDepth
@import 'mobile/mobile';

View file

@ -3,7 +3,6 @@ $background: #fff;
// Variables // Variables
$main-background: $background; $main-background: $background;
$sidebars-background: $background;
$card-shadow: none; $card-shadow: none;
@import 'color_themes/color_theme_override_origwhite'; @import 'color_themes/color_theme_override_origwhite';

View file

@ -1,13 +1,27 @@
.comment_stream { .comment_stream {
.show_comments { .show_comments {
margin-top: 5px;
border-top: 1px solid $border-grey; border-top: 1px solid $border-grey;
line-height: $line-height-computed;
margin-top: 5px;
a { a {
color: $text-grey; color: $text-grey;
font-size: 13px; font-size: 13px;
} }
.media { margin-top: 10px; } .media { margin-top: 10px; }
} }
.loading-comments {
height: $line-height-computed + 11px; // height of .show_comments: line-height, 10px margin, 1px border
margin-top: -$line-height-computed - 11px;
.loader {
height: 20px;
width: 20px;
}
.media { margin: 5px; }
}
.comments > .comment, .comments > .comment,
.comment.new-comment-form-wrapper { .comment.new-comment-form-wrapper {
.avatar { .avatar {

View file

@ -38,13 +38,13 @@
margin-right: 25px; margin-right: 25px;
} }
#chat_privilege_toggle > .enabled { #chat_privilege_toggle > .enabled {
color: #000; color: $text-color-active;
} }
.contacts-header-icon { .contacts-header-icon {
font-size: 24.5px; font-size: 24.5px;
line-height: 40px; line-height: 40px;
color: lighten($black,75%); color: $text-color-pale;
&:hover { color: $black; } &:hover { color: $text-color; }
} }
#suggest_member.btn { margin-top: 8px; } #suggest_member.btn { margin-top: 8px; }
} }
@ -56,14 +56,14 @@
font-size: 20px; font-size: 20px;
line-height: 50px; line-height: 50px;
margin: 0 10px; margin: 0 10px;
color: lighten($black,75%); color: $text-color-pale;
&:hover { color: $black; } &:hover { color: $text-color; }
} }
&.in_aspect { &.in_aspect {
border-left: 3px solid $brand-success; border-left: 3px solid $brand-success;
background-color: lighten($brand-success,35%); background-color: lighten($brand-success,35%);
} }
&:not(.in_aspect) { border-left: 3px solid $white; } &:not(.in_aspect) { border-left: 3px solid $framed-background; }
} }
.no_contacts { .no_contacts {

View file

@ -17,7 +17,7 @@
} }
.stream-element { .stream-element {
background-color: $white; background-color: $framed-background;
padding: 10px; padding: 10px;
.avatar { .avatar {
@ -30,7 +30,7 @@
.stream-element.message, .stream-element.message,
.stream-element.new-message { .stream-element.new-message {
border: 1px solid $light-grey; border: 1px solid $border-grey;
box-shadow: $card-shadow; box-shadow: $card-shadow;
margin-bottom: 20px; margin-bottom: 20px;
@ -83,7 +83,7 @@
} }
} }
&.unread { background-color: darken($background-white, 5%); } &.unread { background-color: $background-grey; }
&.selected { background-color: $blue; } &.selected { background-color: $blue; }
.last_author, .last_message { .last_author, .last_message {
@ -183,10 +183,60 @@
} }
// scss-lint:enable SelectorDepth // scss-lint:enable SelectorDepth
#new_conversation_pane { .new-conversation {
ul.as-selections { width: 100% !important; } ul.as-selections { width: 100% !important; }
input#contact_ids { box-shadow: none; } input#contact_ids { box-shadow: none; }
label { font-weight: bold; } label { font-weight: bold; }
.twitter-typeahead,
.tt-menu {
width: 100%;
}
}
.recipients-tag-list {
.conversation-recipient-tag {
background-color: $brand-primary;
border-radius: $btn-border-radius-base;
display: inline-flex;
margin: 0 2px $form-group-margin-bottom;
padding: 8px;
&:first-child { margin-left: 0; }
&:last-child { margin-right: 0; }
div {
align-self: center;
justify-content: flex-start;
}
}
.avatar {
height: 40px;
margin-right: 8px;
width: 40px;
}
.name-and-handle {
color: $white;
margin-right: 8px;
text-align: left;
.diaspora-id { font-size: $font-size-small; }
}
.entypo-circled-cross {
color: $white;
cursor: pointer;
font-size: 20px;
height: 22px;
line-height: 22px;
&:hover { color: $light-grey; }
}
} }
.new-conversation.form-horizontal .form-group:last-of-type { margin-bottom: 0; } .new-conversation.form-horizontal .form-group:last-of-type { margin-bottom: 0; }

View file

@ -13,9 +13,7 @@ textarea {
&:active:focus, &:active:focus,
&:invalid:focus, &:invalid:focus,
&:invalid:required:focus { &:invalid:required:focus {
border-color: $border-grey;
box-shadow: none; box-shadow: none;
color: $text-dark-grey;
} }
} }
// scss-lint:enable QualifyingElement // scss-lint:enable QualifyingElement
@ -29,7 +27,6 @@ textarea {
margin: 20px auto; margin: 20px auto;
fieldset { fieldset {
background-color: $white;
margin-bottom: 1em; margin-bottom: 1em;
position: relative; // To correctly place the entypo icon position: relative; // To correctly place the entypo icon

View file

@ -106,7 +106,7 @@
} }
.view_all { .view_all {
background-color: $link-color; background-color: $link-color;
border-top: 3px solid $white; border-top: 3px solid $dropdown-bg;
text-align: center; text-align: center;
a { a {
color: $white; color: $white;

View file

@ -98,9 +98,9 @@ ul#help_nav {
line-height: 70px; line-height: 70px;
[class^="entypo-"], [class*="entypo-"] { [class^="entypo-"], [class*="entypo-"] {
color: #bfbfbf; color: $text-color-pale;
&.entypo-chat{ color: #000000; } &.entypo-chat { color: $text-color-active; }
} }
} }
} }

View file

@ -33,8 +33,8 @@
} }
.landing-info-card { .landing-info-card {
background-color: $white; background-color: $framed-background;
border: 1px solid $light-grey; border: 1px solid $border-grey;
box-shadow: $card-shadow; box-shadow: $card-shadow;
margin-bottom: 25px; margin-bottom: 25px;
margin-top: 25px; margin-top: 25px;

View file

@ -7,7 +7,7 @@
min-width: 250px; min-width: 250px;
max-width: 400px; max-width: 400px;
background-color: $background-white; background-color: $hovercard-background;
border: 1px solid $border-dark-grey; border: 1px solid $border-dark-grey;
font-size: small; font-size: small;

View file

@ -1,6 +1,6 @@
[class^="entypo-"], [class*="entypo-"] { [class^="entypo-"], [class*="entypo-"] {
font-style: normal; font-style: normal;
color: black; color: $icon-color;
&.red { color: #A40802; } &.red { color: #A40802; }
&.white { color: white; } &.white { color: white; }

View file

@ -4,7 +4,7 @@
[class^="entypo-"], [class^="entypo-"],
[class*="entypo-"] { [class*="entypo-"] {
color: $text-grey; color: $text-color-pale;
font-size: $font-size-base; font-size: $font-size-base;
line-height: $line-height-base; line-height: $line-height-base;
vertical-align: middle; vertical-align: middle;
@ -12,12 +12,12 @@
[class^="entypo-"]:hover, [class^="entypo-"]:hover,
[class*="entypo-"]:hover { [class*="entypo-"]:hover {
color: $text; color: $text-color;
} }
&.hide_conversation i { font-size: $line-height-computed * 1.5; } &.hide_conversation i { font-size: $line-height-computed * 1.5; }
&.delete_conversation i { font-size: $font-size-base * 1.5; } &.delete_conversation i { font-size: $font-size-base * 1.5; }
&.destroy_participation i { color: $black; } &.destroy_participation i { color: $text-color-active; }
&.destroy_participation i:hover { color: $text-dark-grey; } &.destroy_participation i:hover { color: $text-dark-grey; }
} }
} }

View file

@ -1,5 +1,5 @@
#invite_code { #invite_code {
background-color: $white; background-color: $framed-background;
cursor: text; cursor: text;
display: block; display: block;
margin-top: 5px; margin-top: 5px;
@ -7,13 +7,13 @@
#invitationsModal { #invitationsModal {
.modal-header, .modal-body { .modal-header, .modal-body {
color: $text; color: $text-color;
font-size: $font-size-base; font-size: $font-size-base;
text-align: initial; text-align: initial;
} }
#paste_link { font-weight: 700; } #paste_link { font-weight: 700; }
#invite_code { margin-top: 10px; } #invite_code { margin-top: 10px; }
#codes_left { color: $text-grey; } #codes_left { color: $text-color-pale; }
.controls { margin-left: 140px; } .controls { margin-left: 140px; }
#email_invitation { #email_invitation {
padding-top: 10px; padding-top: 10px;
@ -21,7 +21,7 @@
border-top: 1px dashed $border-grey; border-top: 1px dashed $border-grey;
label { font-weight: 700; } label { font-weight: 700; }
#already_sent { #already_sent {
color: $text-grey; color: $text-color-pale;
font-size: 12px; font-size: 12px;
} }
} }

View file

@ -1,6 +1,6 @@
.md-footer, .md-footer,
.md-header { .md-header {
background: $sidebars-background; background: $white;
border: 0; border: 0;
display: block; display: block;
height: 42px; height: 42px;
@ -10,7 +10,7 @@
[class^="entypo-"], [class^="entypo-"],
[class*="entypo-"], [class*="entypo-"],
.glyphicon { .glyphicon {
color: $black; color: $icon-color;
} }
} }
@ -72,7 +72,7 @@
width: 18px; width: 18px;
} }
&:hover .entypo-cross { color: $text; } &:hover .entypo-cross { color: $text-color; }
} }

View file

@ -75,7 +75,7 @@
} }
.mentions { .mentions {
color: white; color: transparent;
font-size: $font-size-base; font-size: $font-size-base;
font-family: Arial, Helvetica, sans-serif; font-family: Arial, Helvetica, sans-serif;
overflow: hidden; overflow: hidden;

View file

@ -33,7 +33,7 @@
&.active:not(.bottom_collapse), &.active:not(.bottom_collapse),
&.active:not(.bottom_collapse) > [class^="entypo"] { &.active:not(.bottom_collapse) > [class^="entypo"] {
color: $text; color: $text-color;
} }
} }

View file

@ -61,3 +61,5 @@
.subject { padding: 0 10px; } .subject { padding: 0 10px; }
.message-count, .unread-message-count { margin: 10px 2px; } .message-count, .unread-message-count { margin: 10px 2px; }
.new-conversation .as-selections { background-color: transparent; }

View file

@ -150,7 +150,7 @@ footer {
border-radius: 5px; border-radius: 5px;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2); box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2);
background-color: #fff; background-color: $framed-background;
margin-bottom: 10px; margin-bottom: 10px;
border: 1px solid #bbb; border: 1px solid #bbb;
@ -176,7 +176,7 @@ footer {
border-radius: 3px 3px 0 0; border-radius: 3px 3px 0 0;
border: { border: {
bottom: 1px solid #ccc; bottom: 1px solid $border-medium-grey;
} }
img.big-stream-photo { img.big-stream-photo {
@ -322,7 +322,7 @@ footer {
} }
.header-full-width { .header-full-width {
background-color: #fff; background-color: $framed-background;
border-bottom: 1px solid #aaa; border-bottom: 1px solid #aaa;
margin: -10px; // Counter the #main padding margin: -10px; // Counter the #main padding
margin-bottom: 10px; margin-bottom: 10px;
@ -462,7 +462,7 @@ select {
font-weight: bold; font-weight: bold;
color: $text-grey; color: $text-grey;
background: { background: {
color: #fff; color: $framed-background;
} }
} }
} }

View file

@ -3,7 +3,7 @@ ul.followed_tags {
margin: 0px; margin: 0px;
> li { > li {
background-color: $white; background-color: $framed-background;
border: 1px solid $border-grey; border: 1px solid $border-grey;
border-radius: 5px; border-radius: 5px;
box-shadow: 0 1px 2px rgba($border-dark-grey, 0.5); box-shadow: 0 1px 2px rgba($border-dark-grey, 0.5);

View file

@ -73,7 +73,7 @@
background-color: $background-grey; background-color: $background-grey;
.unread-toggle { .unread-toggle {
opacity: 1 !important; opacity: 1 !important;
.entypo-eye { color: $black; } .entypo-eye { color: $text-color-active; }
} }
} }
@ -90,7 +90,7 @@
padding: 9px 5px; padding: 9px 5px;
.entypo-eye { .entypo-eye {
cursor: pointer; cursor: pointer;
color: lighten($black,75%); color: $text-color-pale;
font-size: 17px; font-size: 17px;
line-height: 17px; line-height: 17px;
} }

View file

@ -1,7 +1,7 @@
#profile_container { #profile_container {
.profile_header { .profile_header {
margin-bottom: 15px; margin-bottom: 15px;
background-color: $white; background-color: $framed-background;
padding-left: 10px; padding-left: 10px;
padding-right: 10px; padding-right: 10px;
padding-top: 20px; padding-top: 20px;
@ -22,7 +22,7 @@
font-weight: 700; font-weight: 700;
} }
#diaspora_handle { #diaspora_handle {
color: $text-grey; color: $text-color-pale;
font-size: 20px; font-size: 20px;
} }
#sharing_message { #sharing_message {
@ -64,8 +64,8 @@
.profile-header-icon { .profile-header-icon {
font-size: 24.5px; font-size: 24.5px;
line-height: 30px; line-height: 30px;
color: lighten($black,75%); color: $text-color-pale;
&:hover { color: $black; } &:hover { color: $text-color; }
} }
#mention_button { font-weight: 700; } #mention_button { font-weight: 700; }
} }
@ -80,8 +80,12 @@
&.active { &.active {
border-bottom: 3px solid $brand-primary; border-bottom: 3px solid $brand-primary;
a { a {
color: $black; color: $text-color-active;
[class^="entypo-"], [class*="entypo-"] { color: $black; }
[class^="entypo-"],
[class*="entypo-"] {
color: $text-color-active;
}
} }
} }
a { a {
@ -94,9 +98,13 @@
margin-right: 2px; margin-right: 2px;
} }
&:hover { &:hover {
color: $black; color: $text-color-active;
[class^="entypo-"], [class*="entypo-"] { color: $black; }
text-decoration: none; text-decoration: none;
[class^="entypo-"],
[class*="entypo-"] {
color: $text-color-active;
}
} }
} }
} }

View file

@ -35,7 +35,7 @@
} }
.captcha-input { .captcha-input {
border-bottom: 1px solid $border-grey; border-bottom: 1px solid $input-border;
border-bottom-left-radius: 5px; border-bottom-left-radius: 5px;
border-bottom-right-radius: 5px; border-bottom-right-radius: 5px;
box-sizing: border-box; box-sizing: border-box;

View file

@ -1,7 +1,7 @@
.sidebar, .sidebar,
.framed-content { .framed-content {
background-color: $white; background-color: $framed-background;
border: 1px solid $light-grey; border: 1px solid $border-grey;
border-top: 0; border-top: 0;
box-shadow: $card-shadow; box-shadow: $card-shadow;

View file

@ -15,7 +15,7 @@
position: relative; position: relative;
.control-icons { .control-icons {
background: $white; background: $framed-background;
border-radius: 4px; border-radius: 4px;
padding-left: 4px; padding-left: 4px;
position: absolute; position: absolute;
@ -26,7 +26,7 @@
} }
.thumbnail { .thumbnail {
background: $white; background: $framed-background;
border-radius: 0; border-radius: 0;
box-shadow: $card-shadow; box-shadow: $card-shadow;
height: 240px; height: 240px;
@ -40,7 +40,7 @@
&:hover, &:hover,
&:focus, &:focus,
&:active { &:active {
border-color: $light-grey; border-color: $border-grey;
text-decoration: none; text-decoration: none;
} }
@ -62,7 +62,7 @@
#main_stream .stream-element { #main_stream .stream-element {
margin-bottom: 20px; margin-bottom: 20px;
border: 1px solid $light-grey; border: 1px solid $border-grey;
box-shadow: $card-shadow; box-shadow: $card-shadow;
&.highlighted { &.highlighted {
@ -72,7 +72,7 @@
} }
.stream-element { .stream-element {
background-color: $white; background-color: $framed-background;
padding: 10px; padding: 10px;
& > .media { & > .media {

View file

@ -6,6 +6,14 @@
</div> </div>
</div> </div>
<div class="loading-comments comment text-center hidden">
<div class="media">
<div class="loader">
<div class="spinner"></div>
</div>
</div>
</div>
<div class="comments"> </div> <div class="comments"> </div>
{{#if loggedIn}} {{#if loggedIn}}

View file

@ -0,0 +1,12 @@
<div class="conversation-recipient-tag clearfix" data-diaspora-handle="{{ handle }}">
<div href="{{ url }}">
<img src="{{ avatar }}" class="avatar img-responsive center-block">
</div>
<div class="pull-left clearfix name-and-handle" href="{{ url }}">
<div class="name">{{ name }}</div>
<div class="diaspora-id">{{ handle }}</div>
</div>
<div class="remove pull-right clearfix">
<i class="entypo-circled-cross"></i>
</div>
</div>

View file

@ -53,7 +53,7 @@
<ul class="dropdown-menu" role="menu"> <ul class="dropdown-menu" role="menu">
<div class="header"> <div class="header">
<div class="pull-right"> <div class="pull-right">
<a href="#" id="mark_all_read_link" class="btn btn-default btn-sm {{#unless current_user.notifications_count}}disabled{{/unless}}"> <a href="#" id="mark-all-read-link" class="btn btn-default btn-sm {{#unless current_user.notifications_count}}disabled{{/unless}}">
{{t "header.mark_all_as_read"}} {{t "header.mark_all_as_read"}}
</a> </a>
</div> </div>
@ -61,11 +61,10 @@
{{t "header.recent_notifications"}} {{t "header.recent_notifications"}}
</h4> </h4>
</div> </div>
<div class="notifications"> <div class="ajax-loader">
<div class="ajax-loader"> <div class="spinner"></div>
<div class="spinner"></div>
</div>
</div> </div>
<div class="notifications"></div>
<div class="view_all"> <div class="view_all">
<a href="/notifications" id="view_all_notifications"> <a href="/notifications" id="view_all_notifications">
{{t "header.view_all"}} {{t "header.view_all"}}

View file

@ -125,6 +125,7 @@ module Api
session[:response_type] = @response_type session[:response_type] = @response_type
session[:redirect_uri] = @redirect_uri session[:redirect_uri] = @redirect_uri
session[:scopes] = scopes_as_space_seperated_values session[:scopes] = scopes_as_space_seperated_values
session[:state] = params[:state]
session[:nonce] = params[:nonce] session[:nonce] = params[:nonce]
end end
@ -149,6 +150,7 @@ module Api
session.delete(:response_type) session.delete(:response_type)
session.delete(:redirect_uri) session.delete(:redirect_uri)
session.delete(:scopes) session.delete(:scopes)
session.delete(:state)
session.delete(:nonce) session.delete(:nonce)
end end
@ -162,6 +164,7 @@ module Api
req.update_param("redirect_uri", session[:redirect_uri]) req.update_param("redirect_uri", session[:redirect_uri])
req.update_param("response_type", response_type_as_space_seperated_values) req.update_param("response_type", response_type_as_space_seperated_values)
req.update_param("scope", session[:scopes]) req.update_param("scope", session[:scopes])
req.update_param("state", session[:state])
req.update_param("nonce", session[:nonce]) req.update_param("nonce", session[:nonce])
end end

View file

@ -1,16 +0,0 @@
class Api::V1::TokensController < ApplicationController
skip_before_filter :verify_authenticity_token
before_filter :authenticate_user!
respond_to :json
def create
current_user.ensure_authentication_token!
render :status => 200, :json => { :token => current_user.authentication_token }
end
def destroy
current_user.reset_authentication_token!
render :json => true, :status => 200
end
end

View file

@ -12,11 +12,17 @@ class CommentsController < ApplicationController
end end
def create def create
comment = comment_service.create(params[:post_id], params[:text]) begin
comment = comment_service.create(params[:post_id], params[:text])
rescue ActiveRecord::RecordNotFound
render text: I18n.t("comments.create.error"), status: 404
return
end
if comment if comment
respond_create_success(comment) respond_create_success(comment)
else else
render nothing: true, status: 404 render text: I18n.t("comments.create.error"), status: 422
end end
end end

View file

@ -17,7 +17,8 @@ class ContactsController < ApplicationController
# Used for mentions in the publisher and pagination on the contacts page # Used for mentions in the publisher and pagination on the contacts page
format.json { format.json {
@people = if params[:q].present? @people = if params[:q].present?
Person.search(params[:q], current_user, only_contacts: true).limit(15) mutual = params[:mutual].present? && params[:mutual]
Person.search(params[:q], current_user, only_contacts: true, mutual: mutual).limit(15)
else else
set_up_contacts_json set_up_contacts_json
end end

View file

@ -30,12 +30,17 @@ class ConversationsController < ApplicationController
end end
def create def create
contact_ids = params[:contact_ids] # Contacts autocomplete does not work the same way on mobile and desktop
# Mobile returns contact ids array while desktop returns person id
# This will have to be removed when mobile autocomplete is ported to Typeahead
recipients_param, column = [%i(contact_ids id), %i(person_ids person_id)].find {|param, _| params[param].present? }
if recipients_param
person_ids = current_user.contacts.mutual.where(column => params[recipients_param].split(",")).pluck(:person_id)
end
# Can't split nil unless person_ids.present?
if contact_ids render text: I18n.t("javascripts.conversation.create.no_recipient"), status: 422
contact_ids = contact_ids.split(',') if contact_ids.is_a? String return
person_ids = current_user.contacts.where(id: contact_ids).pluck(:person_id)
end end
opts = params.require(:conversation).permit(:subject) opts = params.require(:conversation).permit(:subject)
@ -43,16 +48,12 @@ class ConversationsController < ApplicationController
opts[:message] = { text: params[:conversation][:text] } opts[:message] = { text: params[:conversation][:text] }
@conversation = current_user.build_conversation(opts) @conversation = current_user.build_conversation(opts)
if person_ids.present? && @conversation.save if @conversation.save
Diaspora::Federation::Dispatcher.defer_dispatch(current_user, @conversation) Diaspora::Federation::Dispatcher.defer_dispatch(current_user, @conversation)
flash[:notice] = I18n.t("conversations.create.sent") flash[:notice] = I18n.t("conversations.create.sent")
render json: {id: @conversation.id} render json: {id: @conversation.id}
else else
message = I18n.t("conversations.create.fail") render text: I18n.t("conversations.create.fail"), status: 422
if person_ids.blank?
message = I18n.t("javascripts.conversation.create.no_recipient")
end
render text: message, status: 422
end end
end end
@ -91,17 +92,23 @@ class ConversationsController < ApplicationController
return return
end end
@contacts_json = contacts_data.to_json
@contact_ids = ""
if params[:contact_id]
@contact_ids = current_user.contacts.find(params[:contact_id]).id
elsif params[:aspect_id]
@contact_ids = current_user.aspects.find(params[:aspect_id]).contacts.map{|c| c.id}.join(',')
end
if session[:mobile_view] == true && request.format.html? if session[:mobile_view] == true && request.format.html?
@contacts_json = contacts_data.to_json
@contact_ids = if params[:contact_id]
current_user.contacts.find(params[:contact_id]).id
elsif params[:aspect_id]
current_user.aspects.find(params[:aspect_id]).contacts.pluck(:id).join(",")
end
render :layout => true render :layout => true
else else
if params[:contact_id]
gon.push conversation_prefill: [current_user.contacts.find(params[:contact_id]).person.as_json]
elsif params[:aspect_id]
gon.push conversation_prefill: current_user.aspects
.find(params[:aspect_id]).contacts.map {|c| c.person.as_json }
end
render :layout => false render :layout => false
end end
end end

View file

@ -26,7 +26,7 @@ class LikesController < ApplicationController
format.json { render :json => @like.as_api_response(:backbone), :status => 201 } format.json { render :json => @like.as_api_response(:backbone), :status => 201 }
end end
else else
render :nothing => true, :status => 422 render text: I18n.t("likes.create.error"), status: 422
end end
end end

View file

@ -52,7 +52,7 @@ class NotificationsController < ApplicationController
format.html format.html
format.xml { render :xml => @notifications.to_xml } format.xml { render :xml => @notifications.to_xml }
format.json { format.json {
render json: @notifications, each_serializer: NotificationSerializer render json: render_as_json(@unread_notification_count, @grouped_unread_notification_counts, @notifications)
} }
end end
end end
@ -82,4 +82,15 @@ class NotificationsController < ApplicationController
end end
end end
private
def render_as_json(unread_count, unread_count_by_type, notification_list)
{
unread_count: unread_count,
unread_count_by_type: unread_count_by_type,
notification_list: notification_list.map {|note|
NotificationSerializer.new(note, default_serializer_options).as_json
}
}.as_json
end
end end

View file

@ -14,7 +14,19 @@ class ResharesController < ApplicationController
current_user.dispatch_post(@reshare) current_user.dispatch_post(@reshare)
render :json => ExtremePostPresenter.new(@reshare, current_user), :status => 201 render :json => ExtremePostPresenter.new(@reshare, current_user), :status => 201
else else
render :nothing => true, :status => 422 render text: I18n.t("reshares.create.error"), status: 422
end end
end end
def index
@reshares = target.reshares.includes(author: :profile)
render json: @reshares.as_api_response(:backbone)
end
private
def target
@target ||= current_user.find_visible_shareable_by_id(Post, params[:post_id]) ||
raise(ActiveRecord::RecordNotFound.new)
end
end end

View file

@ -7,17 +7,14 @@ class ShareVisibilitiesController < ApplicationController
before_action :authenticate_user! before_action :authenticate_user!
def update def update
#note :id references a postvisibility post = post_service.find!(params[:post_id])
params[:shareable_id] ||= params[:post_id] current_user.toggle_hidden_shareable(post)
params[:shareable_type] ||= 'Post' head :ok
vis = current_user.toggle_hidden_shareable(accessible_post)
render :nothing => true, :status => 200
end end
private private
def accessible_post def post_service
@post ||= params[:shareable_type].constantize.where(:id => params[:post_id]).select("id, guid, author_id, created_at").first @post_service ||= PostService.new(current_user)
end end
end end

View file

@ -128,6 +128,11 @@ class UsersController < ApplicationController
redirect_to edit_user_path redirect_to edit_user_path
end end
def auth_token
current_user.ensure_authentication_token!
render status: 200, json: {token: current_user.authentication_token}
end
private private
# rubocop:disable Metrics/MethodLength # rubocop:disable Metrics/MethodLength

View file

@ -12,6 +12,8 @@ module ApplicationHelper
end end
def changelog_url def changelog_url
return AppConfig.settings.changelog_url.get if AppConfig.settings.changelog_url.present?
url = "https://github.com/diaspora/diaspora/blob/master/Changelog.md" url = "https://github.com/diaspora/diaspora/blob/master/Changelog.md"
url.sub!('/master/', "/#{AppConfig.git_revision}/") if AppConfig.git_revision.present? url.sub!('/master/', "/#{AppConfig.git_revision}/") if AppConfig.git_revision.present?
url url
@ -31,14 +33,6 @@ module ApplicationHelper
"bookmarklet('#{bookmarklet_url}', #{width}, #{height});" "bookmarklet('#{bookmarklet_url}', #{width}, #{height});"
end end
def contacts_link
if current_user.contacts.size > 0
contacts_path
else
community_spotlight_path
end
end
def all_services_connected? def all_services_connected?
current_user.services.size == AppConfig.configured_services.size current_user.services.size == AppConfig.configured_services.size
end end

View file

@ -33,7 +33,7 @@ module LayoutHelper
def current_user_atom_tag def current_user_atom_tag
return unless @person.present? return unless @person.present?
content_tag(:link, "", rel: "alternate", href: @person.atom_url, type: "application/atom+xml", content_tag(:link, "", rel: "alternate", href: @person.atom_url, type: "application/atom+xml",
title: t(".public_feed", name: @person.name)) title: t("layouts.application.public_feed", name: @person.name))
end end
def translation_missing_warnings def translation_missing_warnings

View file

@ -13,7 +13,7 @@ module SessionsHelper
end end
def display_password_reset_link? def display_password_reset_link?
devise_mapping.recoverable? && controller_name != "passwords" AppConfig.mail.enable? && devise_mapping.recoverable? && controller_name != "passwords"
end end
def flash_class(name) def flash_class(name)

View file

@ -30,6 +30,10 @@ module StreamHelper
aspects_stream_path(max_time: time_for_scroll(@stream), a_ids: session[:a_ids]) aspects_stream_path(max_time: time_for_scroll(@stream), a_ids: session[:a_ids])
elsif current_page?(:public_stream) elsif current_page?(:public_stream)
public_stream_path(max_time: time_for_scroll(@stream)) public_stream_path(max_time: time_for_scroll(@stream))
elsif current_page?(:commented_stream)
commented_stream_path(max_time: time_for_scroll(@stream))
elsif current_page?(:liked_stream)
liked_stream_path(max_time: time_for_scroll(@stream))
elsif current_page?(:mentioned_stream) elsif current_page?(:mentioned_stream)
mentioned_stream_path(max_time: time_for_scroll(@stream)) mentioned_stream_path(max_time: time_for_scroll(@stream))
elsif current_page?(:followed_tags_stream) elsif current_page?(:followed_tags_stream)

View file

@ -145,7 +145,7 @@ class Person < ActiveRecord::Base
[where_clause, q_tokens] [where_clause, q_tokens]
end end
def self.search(search_str, user, only_contacts: false) def self.search(search_str, user, only_contacts: false, mutual: false)
search_str.strip! search_str.strip!
return none if search_str.blank? || search_str.size < 2 return none if search_str.blank? || search_str.size < 2
@ -159,6 +159,8 @@ class Person < ActiveRecord::Base
).searchable(user) ).searchable(user)
end end
query = query.where(contacts: {sharing: true, receiving: true}) if mutual
query.where(closed_account: false) query.where(closed_account: false)
.where(sql, *tokens) .where(sql, *tokens)
.includes(:profile) .includes(:profile)

View file

@ -61,6 +61,10 @@ class Pod < ActiveRecord::Base
def check_all! def check_all!
Pod.find_in_batches(batch_size: 20) {|batch| batch.each(&:test_connection!) } Pod.find_in_batches(batch_size: 20) {|batch| batch.each(&:test_connection!) }
end end
def check_scheduled!
Pod.where(scheduled_check: true).find_each(&:test_connection!)
end
end end
def offline? def offline?
@ -76,6 +80,10 @@ class Pod < ActiveRecord::Base
"#{id}:#{host}" "#{id}:#{host}"
end end
def schedule_check_if_needed
update_column(:scheduled_check, true) if offline? && !scheduled_check
end
def test_connection! def test_connection!
result = ConnectionTester.check uri.to_s result = ConnectionTester.check uri.to_s
logger.debug "tested pod: '#{uri}' - #{result.inspect}" logger.debug "tested pod: '#{uri}' - #{result.inspect}"
@ -108,6 +116,7 @@ class Pod < ActiveRecord::Base
attributes_from_result(result) attributes_from_result(result)
touch(:checked_at) touch(:checked_at)
self.scheduled_check = false
save save
end end

View file

@ -21,6 +21,14 @@ class Reshare < Post
self.root.update_reshares_counter if self.root.present? self.root.update_reshares_counter if self.root.present?
end end
acts_as_api
api_accessible :backbone do |t|
t.add :id
t.add :guid
t.add :author
t.add :created_at
end
def root_diaspora_id def root_diaspora_id
root.try(:author).try(:diaspora_handle) root.try(:author).try(:diaspora_handle)
end end

View file

@ -3,6 +3,7 @@
# the COPYRIGHT file. # the COPYRIGHT file.
class User < ActiveRecord::Base class User < ActiveRecord::Base
include AuthenticationToken
include Connecting include Connecting
include Querying include Querying
include SocialActions include SocialActions
@ -16,7 +17,7 @@ class User < ActiveRecord::Base
scope :halfyear_actives, ->(time = Time.now) { logged_in_since(time - 6.month) } scope :halfyear_actives, ->(time = Time.now) { logged_in_since(time - 6.month) }
scope :active, -> { joins(:person).where(people: {closed_account: false}) } scope :active, -> { joins(:person).where(people: {closed_account: false}) }
devise :token_authenticatable, :database_authenticatable, :registerable, devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable, :recoverable, :rememberable, :trackable, :validatable,
:lockable, :lastseenable, :lock_strategy => :none, :unlock_strategy => :none :lockable, :lastseenable, :lock_strategy => :none, :unlock_strategy => :none

View file

@ -0,0 +1,26 @@
class User
module AuthenticationToken
extend ActiveSupport::Concern
# Generate new authentication token and save the record.
def reset_authentication_token!
self.authentication_token = self.class.authentication_token
save(validate: false)
end
# Generate authentication token unless already exists and save the record.
def ensure_authentication_token!
reset_authentication_token! if authentication_token.blank?
end
module ClassMethods
# Generate a token checking if one does not already exist in the database.
def authentication_token
loop do
token = Devise.friendly_token(30)
break token unless User.exists?(authentication_token: token)
end
end
end
end
end

View file

@ -104,7 +104,7 @@ class PostPresenter < BasePresenter
end end
def user_reshare def user_reshare
@post.reshare_for(current_user) @post.reshare_for(current_user).try(:as_api_response, :backbone)
end end
def already_participated_in_poll def already_participated_in_poll

View file

@ -0,0 +1,30 @@
%title
= page_title yield(:page_title)
%meta{charset: "utf-8"}/
= content_for?(:meta_data) ? yield(:meta_data) : metas_tags
/ favicon
/ For Apple devices
%link{rel: "apple-touch-icon", href: image_path("apple-touch-icon.png")}
/ For Nokia devices
%link{rel: "shortcut icon", href: image_path("apple-touch-icon.png")}
/ All others
%link{rel: "shortcut icon", href: image_path("favicon.png")}
- if rtl?
= stylesheet_link_tag :rtl, media: "all"
- if Rails.env.test?
= stylesheet_link_tag :poltergeist_disable_transition, media: "all"
= jquery_include_tag
= include_gon(camel_case: true, nonce: content_security_policy_nonce(:script))
= yield(:javascript)
= chartbeat_head_block
= csrf_meta_tag
= current_user_atom_tag
= include_mixpanel
= yield(:head)

View file

@ -1,5 +1,5 @@
- content_for :page_title do - content_for :page_title do
= t('.title') = t(".title")
.container-fluid#contacts_container .container-fluid#contacts_container
.row .row
@ -9,33 +9,34 @@
.col-md-9 .col-md-9
.stream.contacts.framed-content#people_stream .stream.contacts.framed-content#people_stream
= render 'contacts/header' = render "contacts/header"
- if @contacts_size > 0 - if @contacts_size > 0
- if @aspect && @aspect.contacts.length == 0 - if @aspect && @aspect.contacts.length == 0
.well .well
= t('.no_contacts_in_aspect') = t(".no_contacts_in_aspect")
#contact_stream #contact_stream
-# JS -# JS
- else - else
.no_contacts .no_contacts
%h3 %h3
= t('.no_contacts') = t(".no_contacts")
- if AppConfig.settings.community_spotlight.enable?
%p
!= t(".no_contacts_message",
community_spotlight: link_to(t(".community_spotlight"), community_spotlight_path))
%p %p
!= t('.no_contacts_message', .btn.btn-link{"data-toggle" => "modal"}
:community_spotlight => link_to(t('.community_spotlight'), community_spotlight_path)) = t("invitations.new.invite_someone_to_join")
%p
.btn.btn-link{ 'data-toggle' => 'modal' }
= t('invitations.new.invite_someone_to_join')
#paginate #paginate
.loader.hidden .loader.hidden
.spinner .spinner
-if @aspect -if @aspect
#new_conversation_pane .conversations-form-container#new_conversation_pane
= render 'shared/modal', = render "shared/modal",
:path => new_conversation_path(:aspect_id => @aspect.id, :name => @aspect.name, :modal => true), path: new_conversation_path(aspect_id: @aspect.id, name: @aspect.name, modal: true),
:title => t('conversations.index.new_conversation'), title: t("conversations.index.new_conversation"),
:id => 'conversationModal' id: "conversationModal"

View file

@ -2,9 +2,13 @@
= form_for Conversation.new, html: {id: "new-conversation", = form_for Conversation.new, html: {id: "new-conversation",
class: "new-conversation form-horizontal"}, remote: true do |conversation| class: "new-conversation form-horizontal"}, remote: true do |conversation|
.form-group .form-group
%label#toLabel{for: "contact_ids"} %label#to-label{for: "contacts-search-input"}= t(".to")
= t(".to") .recipients-tag-list.clearfix#recipients-tag-list
= text_field_tag "contact_autocomplete", nil, id: "contact-autocomplete", class: "form-control" = text_field_tag "contact_autocomplete", nil, id: "contacts-search-input", class: "form-control"
- unless defined?(mobile) && mobile
= text_field_tag "person_ids", nil, id: "contact-ids", type: "hidden",
aria: {labelledby: "to-label"}
.form-group .form-group
%label#subject-label{for: "conversation-subject"} %label#subject-label{for: "conversation-subject"}
= t(".subject") = t(".subject")
@ -14,12 +18,14 @@
aria: {labelledby: "subject-label"}, aria: {labelledby: "subject-label"},
value: "", value: "",
placeholder: t("conversations.new.subject_default") placeholder: t("conversations.new.subject_default")
.form-group .form-group
%label.sr-only#message-label{for: "new-message-text"} = t(".message") %label.sr-only#message-label{for: "new-message-text"}= t(".message")
= text_area_tag "conversation[text]", "", = text_area_tag "conversation[text]", "",
rows: 5, rows: 5,
id: "new-message-text", id: "new-message-text",
class: "conversation-message-text input-block-level form-control", class: "conversation-message-text input-block-level form-control",
aria: {labelledby: "message-label"} aria: {labelledby: "message-label"}
.form-group .form-group
= conversation.submit t(".send"), "data-disable-with" => t(".sending"), :class => "btn btn-primary pull-right" = conversation.submit t(".send"), "data-disable-with" => t(".sending"), :class => "btn btn-primary pull-right"

View file

@ -25,5 +25,4 @@
- for participant in conversation.participants - for participant in conversation.participants
= person_image_link(participant, :size => :thumb_small) = person_image_link(participant, :size => :thumb_small)
.stream = render partial: "messages", locals: {conversation: conversation}
= render partial: 'messages', locals: { conversation: conversation }

View file

@ -1,12 +1,2 @@
:javascript = include_gon camel_case: true
$(document).ready(function () {
var data = $.parseJSON( "#{escape_javascript(@contacts_json)}" );
new app.views.ConversationsForm({
el: $("form#new-conversation").parent(),
contacts: data,
prefillName: "#{h params[:name]}",
prefillValue: "#{@contact_ids}"
});
});
= render 'conversations/new' = render 'conversations/new'

View file

@ -6,7 +6,7 @@
:plain :plain
$(document).ready(function () { $(document).ready(function () {
var data = $.parseJSON( "#{escape_javascript(@contacts_json).html_safe}" ), var data = $.parseJSON( "#{escape_javascript(@contacts_json).html_safe}" ),
autocompleteInput = $("#contact-autocomplete"); autocompleteInput = $("#contacts-search-input");
autocompleteInput.autoSuggest(data, { autocompleteInput.autoSuggest(data, {
selectedItemProp: "name", selectedItemProp: "name",
@ -15,7 +15,7 @@
retrieveLimit: 10, retrieveLimit: 10,
minChars: 1, minChars: 1,
keyDelay: 0, keyDelay: 0,
startText: '', startText: "",
emptyText: "#{t("no_results")}", emptyText: "#{t("no_results")}",
preFill: [{name : "#{h params[:name]}", preFill: [{name : "#{h params[:name]}",
value : "#{@contact_ids}"}] value : "#{@contact_ids}"}]
@ -27,6 +27,6 @@
#flash-messages #flash-messages
.container-fluid.row .container-fluid.row
%h3 %h3
= t('conversations.index.new_conversation') = t("conversations.index.new_conversation")
= render 'conversations/new' = render "conversations/new", mobile: true

View file

@ -1,13 +1,13 @@
- if controller_name != 'sessions' - if controller_name != 'sessions'
= link_to t('.sign_in'), new_session_path(resource_name) = link_to t('.sign_in'), new_session_path(resource_name)
%br/ %br/
- if AppConfig.settings.enable_registrations? && devise_mapping.registerable? && controller_name != 'registrations' - if display_registration_link?
= link_to t('.sign_up'), new_registration_path(resource_name) = link_to t('.sign_up'), new_registration_path(resource_name)
%br/ %br/
- else - else
%b= t('.sign_up_closed') %b= t('.sign_up_closed')
%br/ %br/
- if devise_mapping.recoverable? && controller_name != 'passwords' - if display_password_reset_link?
= link_to t('.forgot_your_password'), new_password_path(resource_name) = link_to t('.forgot_your_password'), new_password_path(resource_name)
%br/ %br/
- if devise_mapping.confirmable? && controller_name != 'confirmations' - if devise_mapping.confirmable? && controller_name != 'confirmations'

View file

@ -1,32 +1,33 @@
#paste_link #paste_link
= t('.paste_link') = t(".paste_link")
%span#codes_left %span#codes_left
= "(" + t(".codes_left", count: @invite_code.count) + ")" unless AppConfig.settings.enable_registrations? = "(" + t(".codes_left", count: @invite_code.count) + ")" unless AppConfig.settings.enable_registrations?
.form-horizontal .form-horizontal
.control-group .control-group
= invite_link(@invite_code) = invite_link(@invite_code)
#email_invitation - if AppConfig.mail.enable?
= form_tag new_user_invitation_path, class: 'form-horizontal' do #email_invitation
= form_tag new_user_invitation_path, class: "form-horizontal" do
.form-group .form-group
%label.col-sm-2.control-label{ for: 'email_inviter_emails' } %label.col-sm-2.control-label{for: "email_inviter_emails"}
= t('email') = t("email")
.col-sm-10 .col-sm-10
= text_field_tag 'email_inviter[emails]', @invalid_emails, title: t('.comma_separated_plz'), = text_field_tag "email_inviter[emails]", @invalid_emails, title: t(".comma_separated_plz"),
placeholder: 'foo@bar.com, max@foo.com...', class: "form-control" placeholder: "foo@bar.com, max@foo.com...", class: "form-control"
#already_sent #already_sent
= t("invitations.create.note_already_sent", emails: @valid_emails) unless @valid_emails.empty? = t("invitations.create.note_already_sent", emails: @valid_emails) unless @valid_emails.empty?
.form-group .form-group
%label.col-sm-2.control-label{ for: 'email_inviter_locale' } %label.col-sm-2.control-label{for: "email_inviter_locale"}
= t('.language') = t(".language")
.col-sm-10 .col-sm-10
= select_tag 'email_inviter[locale]', options_from_collection_for_select(available_language_options, = select_tag "email_inviter[locale]", options_from_collection_for_select(available_language_options,
"second", "first", selected: current_user.language), class: "form-control" "second", "first", selected: current_user.language), class: "form-control"
.form-group .form-group
.pull-right.col-md-12 .pull-right.col-md-12
= submit_tag t('.send_an_invitation'), class: 'btn btn-primary pull-right', = submit_tag t(".send_an_invitation"), class: "btn btn-primary pull-right",
data: {disable_with: t('.sending_invitation')} data: {disable_with: t(".sending_invitation")}
.clearfix .clearfix

View file

@ -3,47 +3,25 @@
-# the COPYRIGHT file. -# the COPYRIGHT file.
!!! !!!
%html{lang: I18n.locale.to_s, dir: (rtl?) ? 'rtl' : 'ltr'} %html{lang: I18n.locale.to_s, dir: (rtl? ? "rtl" : "ltr")}
%head{prefix: og_prefix} %head{prefix: og_prefix}
%title %meta{name: "viewport", content: "width=device-width, initial-scale=1"}/
= page_title yield(:page_title)
%meta{charset: 'utf-8'}/ - content_for :javascript do
%meta{name: "viewport", content: "width=device-width, initial-scale=1"}/ = old_browser_js_support
= content_for?(:meta_data) ? yield(:meta_data) : metas_tags
%link{rel: 'shortcut icon', href: "#{image_path('favicon.png')}" } <!--[if IE]>
= javascript_include_tag :ie
<![endif]-->
= chartbeat_head_block = javascript_include_tag :main, :templates
= include_mixpanel = load_javascript_locales
= render "head"
= include_color_theme = include_color_theme
- if rtl?
= stylesheet_link_tag :rtl, media: 'all'
- if Rails.env.test?
= stylesheet_link_tag :poltergeist_disable_transition, media: "all"
= old_browser_js_support
<!--[if IE]>
= javascript_include_tag :ie
<![endif]-->
= jquery_include_tag
= javascript_include_tag :main, :templates
= load_javascript_locales
= translation_missing_warnings = translation_missing_warnings
= current_user_atom_tag
= yield(:head) %body{class: "page-#{controller_name} action-#{action_name}"}
= csrf_meta_tag
= include_gon(camel_case: true, nonce: content_security_policy_nonce(:script))
%body{ class: "page-#{controller_name} action-#{action_name}" }
= yield :before_content = yield :before_content
%noscript %noscript

View file

@ -3,56 +3,25 @@
-# the COPYRIGHT file. -# the COPYRIGHT file.
!!! !!!
%html{:lang => I18n.locale.to_s, :dir => (rtl?) ? 'rtl' : 'ltr'} %html{lang: I18n.locale.to_s, dir: (rtl? ? "rtl" : "ltr")}
%head %head{prefix: og_prefix}
%title - content_for :javascript do
= pod_name = javascript_include_tag "mobile/mobile"
= load_javascript_locales
%meta{:name => "description", :content => "diaspora* mobile"}/ = render "head"
%meta{:name => "author", :content => "Diaspora, Inc."}/ = include_color_theme "mobile"
%meta{:charset => 'utf-8'}/
/ Viewport scale %meta{name: "viewport", content: "width=device-width, minimum-scale=1 maximum-scale=1"}/
%meta{:name =>'viewport', :content => "width=device-width, minimum-scale=1 maximum-scale=1"}/ %meta{name: "HandheldFriendly", content: "True"}/
%meta{:name => "HandheldFriendly", :content => "True"}/ %meta{name: "MobileOptimized", content: "320"}/
%meta{:name => "MobileOptimized", :content => "320"}/ %meta{"http-equiv" => "cleartype", :content => "on"}/
/ Force cleartype on WP7
%meta{'http-equiv' => "cleartype", :content => 'on'}/
/ Home screen icon (sized for retina displays)
%link{rel: "apple-touch-icon", href: image_path("apple-touch-icon.png")}
/ For Nokia devices
%link{rel: "shortcut icon", href: image_path("apple-touch-icon.png")}
/ For desktop
%link{rel: 'shortcut icon', href: image_path("favicon.png")}
/ iOS mobile web app indicator / iOS mobile web app indicator
/ NOTE(we will enable these once we don't have to rely on back/forward buttons anymore) / NOTE(we will enable these once we don't have to rely on back/forward buttons anymore)
/%meta{:name => "apple-mobile-web-app-capable", :content => "yes"} /%meta{:name => "apple-mobile-web-app-capable", :content => "yes"}
/%link{:rel => "apple-touch-startup-image", :href => "/images/apple-splash.png"} /%link{:rel => "apple-touch-startup-image", :href => "/images/apple-splash.png"}
= yield :meta_data
= chartbeat_head_block
/ Stylesheets
= include_color_theme "mobile"
= yield(:custom_css)
= csrf_meta_tag
- if rtl?
= stylesheet_link_tag :rtl, :media => 'all'
- if Rails.env.test?
= stylesheet_link_tag :poltergeist_disable_transition, media: "all"
= jquery_include_tag
= yield(:head)
= include_gon(camel_case: true, nonce: content_security_policy_nonce(:script))
%body %body
#app #app
= render "layouts/header" = render "layouts/header"
@ -62,10 +31,8 @@
#main{:role => "main"} #main{:role => "main"}
- if current_page?(:activity_stream) - if current_page?(:activity_stream)
%h3 %h3
= t('streams.activity.title') = t("streams.activity.title")
= yield = yield
/ javascripts at the bottom
= javascript_include_tag "mobile/mobile"
= load_javascript_locales
= include_chartbeat = include_chartbeat
= include_mixpanel_guid

View file

@ -7,7 +7,8 @@
= t(".notifications") = t(".notifications")
.list-group .list-group
%a.list-group-item{href: "/notifications" + (params[:show] == "unread" ? "?show=unread" : ""), %a.list-group-item{href: "/notifications" + (params[:show] == "unread" ? "?show=unread" : ""),
class: ("active" unless params[:type] && @grouped_unread_notification_counts.has_key?(params[:type]))} class: ("active" unless params[:type] && @grouped_unread_notification_counts.has_key?(params[:type])),
data: {type: "all"}}
%span.pull-right.badge{class: ("hidden" unless @unread_notification_count > 0)} %span.pull-right.badge{class: ("hidden" unless @unread_notification_count > 0)}
= @unread_notification_count = @unread_notification_count
= t(".all_notifications") = t(".all_notifications")

View file

@ -40,7 +40,7 @@
id: 'mentionModal' id: 'mentionModal'
-if @contact -if @contact
#new_conversation_pane .conversations-form-container#new_conversation_pane
= render 'shared/modal', = render 'shared/modal',
path: new_conversation_path(:contact_id => @contact.id, name: @contact.person.name, modal: true), path: new_conversation_path(:contact_id => @contact.id, name: @contact.person.name, modal: true),
title: t('conversations.index.new_conversation'), title: t('conversations.index.new_conversation'),

View file

@ -1,21 +0,0 @@
-# Copyright (c) 2010-2011, Diaspora Inc. This file is
-# licensed under the Affero General Public License version 3 or later. See
-# the COPYRIGHT file.
- content_for :head do
= javascript_include_tag :photos
#author_info
= person_image_link(post.author, :size => :thumb_small)
.from
%h2
= post.author_name
#show_photo{:data=>{:guid=>post.id}}
= image_tag post.url(:scaled_full)
#caption
= post.text
%br
= link_to t('photos.show.show_original_post'), post_path(post.status_message)

View file

@ -1,9 +1,12 @@
= t(".share_this") = t(".share_this")
= invite_link(current_user.invitation_code) = invite_link(current_user.invitation_code)
.invitations-link.btn.btn-link#invitations-button{"data-toggle" => "modal"}
= t(".by_email")
= render "shared/modal", - if AppConfig.mail.enable?
path: new_user_invitation_path, .invitations-link.btn.btn-link#invitations-button{"data-toggle" => "modal"}
id: "invitationsModal", = t(".by_email")
title: t("invitations.new.invite_someone_to_join")
- content_for :after_content do
= render "shared/modal",
path: new_user_invitation_path,
id: "invitationsModal",
title: t("invitations.new.invite_someone_to_join")

View file

@ -117,58 +117,59 @@
= render partial: "post_default" = render partial: "post_default"
.row - if AppConfig.mail.enable?
.col-md-12 .row
%h3 .col-md-12
= t(".receive_email_notifications") %h3
= form_for "user", url: edit_user_path, html: {method: :put} do |f| = t(".receive_email_notifications")
= f.fields_for :email_preferences do |type| = form_for "user", url: edit_user_path, html: {method: :put} do |f|
#email_prefs = f.fields_for :email_preferences do |type|
- if current_user.admin? #email_prefs
= type.label :someone_reported, class: "checkbox-inline" do - if current_user.admin?
= type.check_box :someone_reported, {checked: @email_prefs["someone_reported"]}, false, true = type.label :someone_reported, class: "checkbox-inline" do
= t(".someone_reported") = type.check_box :someone_reported, {checked: @email_prefs["someone_reported"]}, false, true
= t(".someone_reported")
.small-horizontal-spacer .small-horizontal-spacer
= type.label :started_sharing, class: "checkbox-inline" do = type.label :started_sharing, class: "checkbox-inline" do
= type.check_box :started_sharing, {checked: @email_prefs["started_sharing"]}, false, true = type.check_box :started_sharing, {checked: @email_prefs["started_sharing"]}, false, true
= t(".started_sharing") = t(".started_sharing")
.small-horizontal-spacer .small-horizontal-spacer
= type.label :mentioned, class: "checkbox-inline" do = type.label :mentioned, class: "checkbox-inline" do
= type.check_box :mentioned, {checked: @email_prefs["mentioned"]}, false, true = type.check_box :mentioned, {checked: @email_prefs["mentioned"]}, false, true
= t(".mentioned") = t(".mentioned")
.small-horizontal-spacer .small-horizontal-spacer
= type.label :liked, class: "checkbox-inline" do = type.label :liked, class: "checkbox-inline" do
= type.check_box :liked, {checked: @email_prefs["liked"]}, false, true = type.check_box :liked, {checked: @email_prefs["liked"]}, false, true
= t(".liked") = t(".liked")
.small-horizontal-spacer .small-horizontal-spacer
= type.label :reshared, class: "checkbox-inline" do = type.label :reshared, class: "checkbox-inline" do
= type.check_box :reshared, {checked: @email_prefs["reshared"]}, false, true = type.check_box :reshared, {checked: @email_prefs["reshared"]}, false, true
= t(".reshared") = t(".reshared")
.small-horizontal-spacer .small-horizontal-spacer
= type.label :comment_on_post, class: "checkbox-inline" do = type.label :comment_on_post, class: "checkbox-inline" do
= type.check_box :comment_on_post, {checked: @email_prefs["comment_on_post"]}, false, true = type.check_box :comment_on_post, {checked: @email_prefs["comment_on_post"]}, false, true
= t(".comment_on_post") = t(".comment_on_post")
.small-horizontal-spacer .small-horizontal-spacer
= type.label :also_commented, class: "checkbox-inline" do = type.label :also_commented, class: "checkbox-inline" do
= type.check_box :also_commented, {checked: @email_prefs["also_commented"]}, false, true = type.check_box :also_commented, {checked: @email_prefs["also_commented"]}, false, true
= t(".also_commented") = t(".also_commented")
.small-horizontal-spacer .small-horizontal-spacer
= type.label :private_message, class: "checkbox-inline" do = type.label :private_message, class: "checkbox-inline" do
= type.check_box :private_message, {checked: @email_prefs["private_message"]}, false, true = type.check_box :private_message, {checked: @email_prefs["private_message"]}, false, true
= t(".private_message") = t(".private_message")
.small-horizontal-spacer .small-horizontal-spacer
.clearfix= f.submit t(".change"), class: "btn btn-primary pull-right", id: "change_email_preferences" .clearfix= f.submit t(".change"), class: "btn btn-primary pull-right", id: "change_email_preferences"
%hr %hr
.row .row
.col-md-6#account_data .col-md-6#account_data

Some files were not shown because too many files have changed in this diff Show more