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

View file

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

View file

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

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

View file

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

View file

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

View file

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

View file

@ -125,7 +125,7 @@ app.views.AspectMembership = app.views.Base.extend({
_displayError: function(model, resp) {
this._done();
this.dropdown.closest(".aspect_membership_dropdown").removeClass("open"); // close the dropdown
app.flashMessages.error(resp.responseText);
app.flashMessages.handleAjaxError(resp);
},
// remove the membership with the given id
@ -134,7 +134,7 @@ app.views.AspectMembership = app.views.Base.extend({
this.listenToOnce(membership, "sync", this._successDestroyCb);
this.listenToOnce(membership, "error", this._displayError);
return membership.destroy();
return membership.destroy({wait: true});
},
_successDestroyCb: function(aspectMembership) {

View file

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

View file

@ -5,40 +5,83 @@ app.views.ConversationsForm = Backbone.View.extend({
events: {
"keydown .conversation-message-text": "keyDown",
"click .conversation-recipient-tag .remove": "removeRecipient"
},
initialize: function(opts) {
this.contacts = _.has(opts, "contacts") ? opts.contacts : null;
this.prefill = [];
if (_.has(opts, "prefillName") && _.has(opts, "prefillValue")) {
this.prefill = [{name: opts.prefillName, value: opts.prefillValue}];
opts = opts || {};
this.conversationRecipients = [];
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:error", this.conversationCreateError);
},
prepareAutocomplete: function(data){
this.$("#contact-autocomplete").autoSuggest(data, {
selectedItemProp: "name",
searchObjProps: "name",
asHtmlID: "contact_ids",
retrieveLimit: 10,
minChars: 1,
keyDelay: 0,
startText: '',
emptyText: Diaspora.I18n.t("no_results"),
preFill: this.prefill
});
$("#contact_ids").attr("aria-labelledby", "toLabel").focus();
addRecipient: function(person) {
this.conversationRecipients.push(person);
this.updateContactIdsListInput();
/* eslint-disable camelcase */
this.tagListElement.append(HandlebarsTemplates.conversation_recipient_tag_tpl(person));
/* eslint-enable camelcase */
},
keyDown : function(evt) {
if(evt.which === Keycodes.ENTER && evt.ctrlKey) {
prefill: function(handles) {
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();
}
},
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) {
app._changeLocation(Routes.conversation(data.id));
},

View file

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

View file

@ -16,5 +16,13 @@ app.views.FlashMessages = app.views.Base.extend({
error: function(message){
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(){
new app.views.Notifications({ el: "#notification-dropdown" });
this.notificationDropdown = new app.views.NotificationDropdown({ el: "#notification-dropdown" });
new app.views.Search({ el: "#header-search-form" });
postRenderTemplate: function() {
new app.views.Notifications({el: "#notification-dropdown", collection: app.notificationsCollection});
new app.views.NotificationDropdown({el: "#notification-dropdown", collection: app.notificationsCollection});
new app.views.Search({el: "#header-search-form"});
},
menuElement: function(){ return this.$("ul.dropdown"); },
menuElement: function() { return this.$("ul.dropdown"); }
});
// @license-end

View file

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

View file

@ -6,16 +6,21 @@ app.views.NotificationDropdown = app.views.Base.extend({
},
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.dropdown = $("#notification-dropdown");
this.dropdownNotifications = this.dropdown.find(".notifications");
this.ajaxLoader = this.dropdown.find(".ajax-loader");
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){
@ -31,12 +36,11 @@ app.views.NotificationDropdown = app.views.Base.extend({
},
showDropdown: function(){
this.resetParams();
this.ajaxLoader.show();
this.dropdown.addClass("dropdown-open");
this.updateScrollbar();
this.dropdownNotifications.addClass("loading");
this.getNotifications();
this.collection.fetch();
},
hideDropdown: function(evt){
@ -50,40 +54,18 @@ app.views.NotificationDropdown = app.views.Base.extend({
dropdownScroll: function(){
var isLoading = ($(".loading").length === 1);
if (this.isBottom() && this.hasMoreNotifs && !isLoading){
if (this.isBottom() && !isLoading) {
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(){
var bottom = this.dropdownNotifications.prop("scrollHeight") - this.dropdownNotifications.height();
var currentPosition = this.dropdownNotifications.scrollTop();
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(){
var self = this;
this.ajaxLoader.find(".spinner").fadeTo(200, 0, function(){
@ -93,28 +75,23 @@ app.views.NotificationDropdown = app.views.Base.extend({
});
},
renderNotifications: function(){
var self = this;
this.dropdownNotifications.find(".media.stream-element").remove();
$.each(self.notifications, function(index, notifications){
$.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);
}
});
});
onPushBack: function(notification) {
var node = this.dropdownNotifications.append(notification.get("note_html"));
$(node).find(".unread-toggle .entypo-eye").tooltip("destroy").tooltip();
$(node).find(this.avatars.selector).error(this.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);
this.updateScrollbar();
this.hideAjaxLoader();
this.dropdownNotifications.removeClass("loading");
this.dropdownNotifications.scroll(function(){
self.dropdownScroll();
});
},
updateScrollbar: function() {

View file

@ -1,96 +1,85 @@
// @license magnet:?xt=urn:btih:0b31508aeb0634b347b8270c7bee4d411b5d4109&dn=agpl-3.0.txt AGPL-v3-or-Later
app.views.Notifications = Backbone.View.extend({
events: {
"click .unread-toggle" : "toggleUnread",
"click #mark_all_read_link": "markAllRead"
"click .unread-toggle": "toggleUnread",
"click #mark-all-read-link": "markAllRead"
},
initialize: function() {
$(".unread-toggle .entypo-eye").tooltip();
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) {
var note = $(evt.target).closest(".stream-element");
var unread = note.hasClass("unread");
var guid = note.data("guid");
if (unread){ this.setRead(guid); }
else { this.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");
if (unread) {
this.collection.setRead(guid);
} else {
this.collection.setUnread(guid);
}
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");
}
},
updateBadge: function(badge, count) {
badge.text(count);
if (count > 0) {
badge.removeClass("hidden");
} else {
badge.addClass("hidden");
}
}
});
// @license-end

View file

@ -15,6 +15,7 @@ app.views.ProfileHeader = app.views.Base.extend({
initialize: function(opts) {
this.photos = _.has(opts, 'photos') ? opts.photos : 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("hidden.bs.modal", this.mentionModalHidden);
},
@ -79,8 +80,11 @@ app.views.ProfileHeader = app.views.Base.extend({
},
showMessageModal: function(){
$("#conversationModal").on("modal:loaded", function() {
new app.views.ConversationsForm({prefill: gon.conversationPrefill});
});
app.helpers.showModal("#conversationModal");
},
}
});
// @license-end

View file

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

View file

@ -11,7 +11,7 @@ app.views.ResharesInfo = app.views.Base.extend({
tooltipSelector : ".avatar",
initialize : function() {
this.model.interactions.bind("change", this.render, this);
this.model.interactions.reshares.bind("change", this.render, this);
this.displayAvatars = false;
},
@ -19,18 +19,16 @@ app.views.ResharesInfo = app.views.Base.extend({
return _.extend(this.defaultPresenter(), {
reshares : this.model.interactions.reshares.toJSON(),
resharesCount : this.model.interactions.resharesCount(),
displayAvatars : this.model.interactions.get("fetched") && this.displayAvatars
displayAvatars: this.displayAvatars
});
},
showAvatars : function(evt){
if(evt) { evt.preventDefault() }
this.displayAvatars = true;
if(!this.model.interactions.get("fetched")){
this.model.interactions.fetch();
} else {
this.model.interactions.trigger("change");
}
this.model.interactions.reshares.fetch({success: function() {
this.model.interactions.reshares.trigger("change");
}.bind(this)});
}
});
// @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
if(options.remoteRoute) {
if (options.remoteRoute && options.remoteRoute.url) {
var extraParameters = "";
if (options.remoteRoute.extraParameters) {
extraParameters += "&" + options.remoteRoute.extraParameters;
}
bloodhoundOptions.remote = {
url: options.remoteRoute + ".json?q=%QUERY",
url: options.remoteRoute.url + ".json?q=%QUERY" + extraParameters,
wildcard: "%QUERY",
transform: this.transformBloodhoundResponse.bind(this)
};

View file

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

View file

@ -5,6 +5,8 @@ app.views.Tags = Backbone.View.extend({
if(app.publisher) {
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

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) {
locale.data = $.extend(locale.data, data);
locale.data = $.extend({}, locale.data, data);
var rule = locale.data.pluralization_rule;
if (typeof rule !== "undefined") {

View file

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

View file

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

View file

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

View file

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

View file

@ -3,31 +3,32 @@ $black: #000;
$text-grey: #999;
$text-dark-grey: #666;
$text: #333;
$text-color-pale: $text-grey !default;
$text-color-active: $black !default;
$background-white: $white;
$background-grey: #eee;
$background-blue: #e7f2f7;
$background-grey: #eee !default;
$background-blue: #e7f2f7 !default;
$grey: #2b2b2b;
$medium-gray: #ccc;
$light-grey: #ddd;
$border-grey: $light-grey;
$border-medium-grey: $medium-gray;
$border-dark-grey: $text-grey;
$border-medium-grey: #ccc;
$border-grey: $light-grey !default;
$border-medium-grey: $medium-gray !default;
$border-dark-grey: $text-grey !default;
$link-grey: #777;
$link-disabled-grey: $text-grey;
$green: #8ede3d;
$icon-color: $black !default;
$green: #8ede3d !default;
$light-green: lighten($green, 20%);
$red: #a80000;
$blue: #3f8fba;
$red: #a80000 !default;
$blue: #3f8fba !default;
$main-background: #f0f0f0 !default;
$sidebars-background: $background-white !default;
$left-navbar-drawer-background: darken($sidebars-background, 6%);
$main-background: darken($white, 6%) !default;
$framed-background: $white !default;
$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;

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 {
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
$main-background: $background;
$sidebars-background: $background;
$card-shadow: none;
@import 'color_themes/color_theme_override_origwhite';

View file

@ -1,13 +1,27 @@
.comment_stream {
.show_comments {
margin-top: 5px;
border-top: 1px solid $border-grey;
line-height: $line-height-computed;
margin-top: 5px;
a {
color: $text-grey;
font-size: 13px;
}
.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,
.comment.new-comment-form-wrapper {
.avatar {

View file

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

View file

@ -17,7 +17,7 @@
}
.stream-element {
background-color: $white;
background-color: $framed-background;
padding: 10px;
.avatar {
@ -30,7 +30,7 @@
.stream-element.message,
.stream-element.new-message {
border: 1px solid $light-grey;
border: 1px solid $border-grey;
box-shadow: $card-shadow;
margin-bottom: 20px;
@ -83,7 +83,7 @@
}
}
&.unread { background-color: darken($background-white, 5%); }
&.unread { background-color: $background-grey; }
&.selected { background-color: $blue; }
.last_author, .last_message {
@ -183,10 +183,60 @@
}
// scss-lint:enable SelectorDepth
#new_conversation_pane {
.new-conversation {
ul.as-selections { width: 100% !important; }
input#contact_ids { box-shadow: none; }
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; }

View file

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

View file

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

View file

@ -98,9 +98,9 @@ ul#help_nav {
line-height: 70px;
[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 {
background-color: $white;
border: 1px solid $light-grey;
background-color: $framed-background;
border: 1px solid $border-grey;
box-shadow: $card-shadow;
margin-bottom: 25px;
margin-top: 25px;

View file

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

View file

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

View file

@ -4,7 +4,7 @@
[class^="entypo-"],
[class*="entypo-"] {
color: $text-grey;
color: $text-color-pale;
font-size: $font-size-base;
line-height: $line-height-base;
vertical-align: middle;
@ -12,12 +12,12 @@
[class^="entypo-"]:hover,
[class*="entypo-"]:hover {
color: $text;
color: $text-color;
}
&.hide_conversation i { font-size: $line-height-computed * 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; }
}
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -6,6 +6,14 @@
</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>
{{#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">
<div class="header">
<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"}}
</a>
</div>
@ -61,11 +61,10 @@
{{t "header.recent_notifications"}}
</h4>
</div>
<div class="notifications">
<div class="ajax-loader">
<div class="spinner"></div>
</div>
<div class="ajax-loader">
<div class="spinner"></div>
</div>
<div class="notifications"></div>
<div class="view_all">
<a href="/notifications" id="view_all_notifications">
{{t "header.view_all"}}

View file

@ -125,6 +125,7 @@ module Api
session[:response_type] = @response_type
session[:redirect_uri] = @redirect_uri
session[:scopes] = scopes_as_space_seperated_values
session[:state] = params[:state]
session[:nonce] = params[:nonce]
end
@ -149,6 +150,7 @@ module Api
session.delete(:response_type)
session.delete(:redirect_uri)
session.delete(:scopes)
session.delete(:state)
session.delete(:nonce)
end
@ -162,6 +164,7 @@ module Api
req.update_param("redirect_uri", session[:redirect_uri])
req.update_param("response_type", response_type_as_space_seperated_values)
req.update_param("scope", session[:scopes])
req.update_param("state", session[:state])
req.update_param("nonce", session[:nonce])
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
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
respond_create_success(comment)
else
render nothing: true, status: 404
render text: I18n.t("comments.create.error"), status: 422
end
end

View file

@ -17,7 +17,8 @@ class ContactsController < ApplicationController
# Used for mentions in the publisher and pagination on the contacts page
format.json {
@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
set_up_contacts_json
end

View file

@ -30,12 +30,17 @@ class ConversationsController < ApplicationController
end
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
if contact_ids
contact_ids = contact_ids.split(',') if contact_ids.is_a? String
person_ids = current_user.contacts.where(id: contact_ids).pluck(:person_id)
unless person_ids.present?
render text: I18n.t("javascripts.conversation.create.no_recipient"), status: 422
return
end
opts = params.require(:conversation).permit(:subject)
@ -43,16 +48,12 @@ class ConversationsController < ApplicationController
opts[:message] = { text: params[:conversation][:text] }
@conversation = current_user.build_conversation(opts)
if person_ids.present? && @conversation.save
if @conversation.save
Diaspora::Federation::Dispatcher.defer_dispatch(current_user, @conversation)
flash[:notice] = I18n.t("conversations.create.sent")
render json: {id: @conversation.id}
else
message = I18n.t("conversations.create.fail")
if person_ids.blank?
message = I18n.t("javascripts.conversation.create.no_recipient")
end
render text: message, status: 422
render text: I18n.t("conversations.create.fail"), status: 422
end
end
@ -91,17 +92,23 @@ class ConversationsController < ApplicationController
return
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?
@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
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
end
end

View file

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

View file

@ -52,7 +52,7 @@ class NotificationsController < ApplicationController
format.html
format.xml { render :xml => @notifications.to_xml }
format.json {
render json: @notifications, each_serializer: NotificationSerializer
render json: render_as_json(@unread_notification_count, @grouped_unread_notification_counts, @notifications)
}
end
end
@ -82,4 +82,15 @@ class NotificationsController < ApplicationController
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

View file

@ -14,7 +14,19 @@ class ResharesController < ApplicationController
current_user.dispatch_post(@reshare)
render :json => ExtremePostPresenter.new(@reshare, current_user), :status => 201
else
render :nothing => true, :status => 422
render text: I18n.t("reshares.create.error"), status: 422
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

View file

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

View file

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

View file

@ -12,6 +12,8 @@ module ApplicationHelper
end
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.sub!('/master/', "/#{AppConfig.git_revision}/") if AppConfig.git_revision.present?
url
@ -31,14 +33,6 @@ module ApplicationHelper
"bookmarklet('#{bookmarklet_url}', #{width}, #{height});"
end
def contacts_link
if current_user.contacts.size > 0
contacts_path
else
community_spotlight_path
end
end
def all_services_connected?
current_user.services.size == AppConfig.configured_services.size
end

View file

@ -33,7 +33,7 @@ module LayoutHelper
def current_user_atom_tag
return unless @person.present?
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
def translation_missing_warnings

View file

@ -13,7 +13,7 @@ module SessionsHelper
end
def display_password_reset_link?
devise_mapping.recoverable? && controller_name != "passwords"
AppConfig.mail.enable? && devise_mapping.recoverable? && controller_name != "passwords"
end
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])
elsif current_page?(:public_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)
mentioned_stream_path(max_time: time_for_scroll(@stream))
elsif current_page?(:followed_tags_stream)

View file

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

View file

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

View file

@ -21,6 +21,14 @@ class Reshare < Post
self.root.update_reshares_counter if self.root.present?
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
root.try(:author).try(:diaspora_handle)
end

View file

@ -3,6 +3,7 @@
# the COPYRIGHT file.
class User < ActiveRecord::Base
include AuthenticationToken
include Connecting
include Querying
include SocialActions
@ -16,7 +17,7 @@ class User < ActiveRecord::Base
scope :halfyear_actives, ->(time = Time.now) { logged_in_since(time - 6.month) }
scope :active, -> { joins(:person).where(people: {closed_account: false}) }
devise :token_authenticatable, :database_authenticatable, :registerable,
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable,
: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
def user_reshare
@post.reshare_for(current_user)
@post.reshare_for(current_user).try(:as_api_response, :backbone)
end
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
= t('.title')
= t(".title")
.container-fluid#contacts_container
.row
@ -9,33 +9,34 @@
.col-md-9
.stream.contacts.framed-content#people_stream
= render 'contacts/header'
= render "contacts/header"
- if @contacts_size > 0
- if @aspect && @aspect.contacts.length == 0
.well
= t('.no_contacts_in_aspect')
= t(".no_contacts_in_aspect")
#contact_stream
-# JS
- else
.no_contacts
%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
!= t('.no_contacts_message',
:community_spotlight => link_to(t('.community_spotlight'), community_spotlight_path))
%p
.btn.btn-link{ 'data-toggle' => 'modal' }
= t('invitations.new.invite_someone_to_join')
.btn.btn-link{"data-toggle" => "modal"}
= t("invitations.new.invite_someone_to_join")
#paginate
.loader.hidden
.spinner
-if @aspect
#new_conversation_pane
= render 'shared/modal',
:path => new_conversation_path(:aspect_id => @aspect.id, :name => @aspect.name, :modal => true),
:title => t('conversations.index.new_conversation'),
:id => 'conversationModal'
.conversations-form-container#new_conversation_pane
= render "shared/modal",
path: new_conversation_path(aspect_id: @aspect.id, name: @aspect.name, modal: true),
title: t("conversations.index.new_conversation"),
id: "conversationModal"

View file

@ -2,9 +2,13 @@
= form_for Conversation.new, html: {id: "new-conversation",
class: "new-conversation form-horizontal"}, remote: true do |conversation|
.form-group
%label#toLabel{for: "contact_ids"}
= t(".to")
= text_field_tag "contact_autocomplete", nil, id: "contact-autocomplete", class: "form-control"
%label#to-label{for: "contacts-search-input"}= t(".to")
.recipients-tag-list.clearfix#recipients-tag-list
= 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
%label#subject-label{for: "conversation-subject"}
= t(".subject")
@ -14,12 +18,14 @@
aria: {labelledby: "subject-label"},
value: "",
placeholder: t("conversations.new.subject_default")
.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]", "",
rows: 5,
id: "new-message-text",
class: "conversation-message-text input-block-level form-control",
aria: {labelledby: "message-label"}
.form-group
= 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
= 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
$(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}"
});
});
= include_gon camel_case: true
= render 'conversations/new'

View file

@ -6,7 +6,7 @@
:plain
$(document).ready(function () {
var data = $.parseJSON( "#{escape_javascript(@contacts_json).html_safe}" ),
autocompleteInput = $("#contact-autocomplete");
autocompleteInput = $("#contacts-search-input");
autocompleteInput.autoSuggest(data, {
selectedItemProp: "name",
@ -15,7 +15,7 @@
retrieveLimit: 10,
minChars: 1,
keyDelay: 0,
startText: '',
startText: "",
emptyText: "#{t("no_results")}",
preFill: [{name : "#{h params[:name]}",
value : "#{@contact_ids}"}]
@ -27,6 +27,6 @@
#flash-messages
.container-fluid.row
%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'
= link_to t('.sign_in'), new_session_path(resource_name)
%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)
%br/
- else
%b= t('.sign_up_closed')
%br/
- if devise_mapping.recoverable? && controller_name != 'passwords'
- if display_password_reset_link?
= link_to t('.forgot_your_password'), new_password_path(resource_name)
%br/
- if devise_mapping.confirmable? && controller_name != 'confirmations'

View file

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

View file

@ -3,47 +3,25 @@
-# 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}
%title
= page_title yield(:page_title)
%meta{name: "viewport", content: "width=device-width, initial-scale=1"}/
%meta{charset: 'utf-8'}/
%meta{name: "viewport", content: "width=device-width, initial-scale=1"}/
= content_for?(:meta_data) ? yield(:meta_data) : metas_tags
- content_for :javascript do
= old_browser_js_support
%link{rel: 'shortcut icon', href: "#{image_path('favicon.png')}" }
<!--[if IE]>
= javascript_include_tag :ie
<![endif]-->
= chartbeat_head_block
= include_mixpanel
= javascript_include_tag :main, :templates
= load_javascript_locales
= render "head"
= 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
= current_user_atom_tag
= yield(:head)
= csrf_meta_tag
= include_gon(camel_case: true, nonce: content_security_policy_nonce(:script))
%body{ class: "page-#{controller_name} action-#{action_name}" }
%body{class: "page-#{controller_name} action-#{action_name}"}
= yield :before_content
%noscript

View file

@ -3,56 +3,25 @@
-# the COPYRIGHT file.
!!!
%html{:lang => I18n.locale.to_s, :dir => (rtl?) ? 'rtl' : 'ltr'}
%head
%title
= pod_name
%html{lang: I18n.locale.to_s, dir: (rtl? ? "rtl" : "ltr")}
%head{prefix: og_prefix}
- content_for :javascript do
= javascript_include_tag "mobile/mobile"
= load_javascript_locales
%meta{:name => "description", :content => "diaspora* mobile"}/
%meta{:name => "author", :content => "Diaspora, Inc."}/
%meta{:charset => 'utf-8'}/
= render "head"
= include_color_theme "mobile"
/ Viewport scale
%meta{:name =>'viewport', :content => "width=device-width, minimum-scale=1 maximum-scale=1"}/
%meta{:name => "HandheldFriendly", :content => "True"}/
%meta{:name => "MobileOptimized", :content => "320"}/
/ 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")}
%meta{name: "viewport", content: "width=device-width, minimum-scale=1 maximum-scale=1"}/
%meta{name: "HandheldFriendly", content: "True"}/
%meta{name: "MobileOptimized", content: "320"}/
%meta{"http-equiv" => "cleartype", :content => "on"}/
/ iOS mobile web app indicator
/ 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"}
/%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
#app
= render "layouts/header"
@ -62,10 +31,8 @@
#main{:role => "main"}
- if current_page?(:activity_stream)
%h3
= t('streams.activity.title')
= t("streams.activity.title")
= yield
/ javascripts at the bottom
= javascript_include_tag "mobile/mobile"
= load_javascript_locales
= include_chartbeat
= include_mixpanel_guid

View file

@ -7,7 +7,8 @@
= t(".notifications")
.list-group
%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)}
= @unread_notification_count
= t(".all_notifications")

View file

@ -40,7 +40,7 @@
id: 'mentionModal'
-if @contact
#new_conversation_pane
.conversations-form-container#new_conversation_pane
= render 'shared/modal',
path: new_conversation_path(:contact_id => @contact.id, name: @contact.person.name, modal: true),
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")
= invite_link(current_user.invitation_code)
.invitations-link.btn.btn-link#invitations-button{"data-toggle" => "modal"}
= t(".by_email")
= render "shared/modal",
path: new_user_invitation_path,
id: "invitationsModal",
title: t("invitations.new.invite_someone_to_join")
- if AppConfig.mail.enable?
.invitations-link.btn.btn-link#invitations-button{"data-toggle" => "modal"}
= t(".by_email")
- 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"
.row
.col-md-12
%h3
= t(".receive_email_notifications")
= form_for "user", url: edit_user_path, html: {method: :put} do |f|
= f.fields_for :email_preferences do |type|
#email_prefs
- if current_user.admin?
= type.label :someone_reported, class: "checkbox-inline" do
= type.check_box :someone_reported, {checked: @email_prefs["someone_reported"]}, false, true
= t(".someone_reported")
- if AppConfig.mail.enable?
.row
.col-md-12
%h3
= t(".receive_email_notifications")
= form_for "user", url: edit_user_path, html: {method: :put} do |f|
= f.fields_for :email_preferences do |type|
#email_prefs
- if current_user.admin?
= type.label :someone_reported, class: "checkbox-inline" do
= 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.check_box :started_sharing, {checked: @email_prefs["started_sharing"]}, false, true
= t(".started_sharing")
.small-horizontal-spacer
= type.label :started_sharing, class: "checkbox-inline" do
= type.check_box :started_sharing, {checked: @email_prefs["started_sharing"]}, false, true
= t(".started_sharing")
.small-horizontal-spacer
= type.label :mentioned, class: "checkbox-inline" do
= type.check_box :mentioned, {checked: @email_prefs["mentioned"]}, false, true
= t(".mentioned")
.small-horizontal-spacer
= type.label :mentioned, class: "checkbox-inline" do
= type.check_box :mentioned, {checked: @email_prefs["mentioned"]}, false, true
= t(".mentioned")
.small-horizontal-spacer
= type.label :liked, class: "checkbox-inline" do
= type.check_box :liked, {checked: @email_prefs["liked"]}, false, true
= t(".liked")
.small-horizontal-spacer
= type.label :liked, class: "checkbox-inline" do
= type.check_box :liked, {checked: @email_prefs["liked"]}, false, true
= t(".liked")
.small-horizontal-spacer
= type.label :reshared, class: "checkbox-inline" do
= type.check_box :reshared, {checked: @email_prefs["reshared"]}, false, true
= t(".reshared")
.small-horizontal-spacer
= type.label :reshared, class: "checkbox-inline" do
= type.check_box :reshared, {checked: @email_prefs["reshared"]}, false, true
= t(".reshared")
.small-horizontal-spacer
= type.label :comment_on_post, class: "checkbox-inline" do
= type.check_box :comment_on_post, {checked: @email_prefs["comment_on_post"]}, false, true
= t(".comment_on_post")
.small-horizontal-spacer
= type.label :comment_on_post, class: "checkbox-inline" do
= type.check_box :comment_on_post, {checked: @email_prefs["comment_on_post"]}, false, true
= t(".comment_on_post")
.small-horizontal-spacer
= type.label :also_commented, class: "checkbox-inline" do
= type.check_box :also_commented, {checked: @email_prefs["also_commented"]}, false, true
= t(".also_commented")
.small-horizontal-spacer
= type.label :also_commented, class: "checkbox-inline" do
= type.check_box :also_commented, {checked: @email_prefs["also_commented"]}, false, true
= t(".also_commented")
.small-horizontal-spacer
= type.label :private_message, class: "checkbox-inline" do
= type.check_box :private_message, {checked: @email_prefs["private_message"]}, false, true
= t(".private_message")
= type.label :private_message, class: "checkbox-inline" do
= type.check_box :private_message, {checked: @email_prefs["private_message"]}, false, true
= t(".private_message")
.small-horizontal-spacer
.small-horizontal-spacer
.clearfix= f.submit t(".change"), class: "btn btn-primary pull-right", id: "change_email_preferences"
%hr
.clearfix= f.submit t(".change"), class: "btn btn-primary pull-right", id: "change_email_preferences"
%hr
.row
.col-md-6#account_data

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