Normalized data in Redux, fix for asset URLs when rendered outside request
This commit is contained in:
parent
7939a216ff
commit
1022d682dc
|
@ -7,9 +7,15 @@ const DisplayName = React.createClass({
|
||||||
},
|
},
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
|
let displayName = this.props.account.get('display_name');
|
||||||
|
|
||||||
|
if (displayName.length === 0) {
|
||||||
|
displayName = this.props.account.get('username');
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<span style={{ display: 'block', maxWidth: '100%', overflow: 'hidden', whiteSpace: 'nowrap', textOverflow: 'ellipsis' }}>
|
<span style={{ display: 'block', maxWidth: '100%', overflow: 'hidden', whiteSpace: 'nowrap', textOverflow: 'ellipsis' }}>
|
||||||
<strong style={{ fontWeight: 'bold' }}>{this.props.account.get('display_name')}</strong> <span style={{ fontSize: '14px' }}>@{this.props.account.get('acct')}</span>
|
<strong style={{ fontWeight: 'bold' }}>{displayName}</strong> <span style={{ fontSize: '14px' }}>@{this.props.account.get('acct')}</span>
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,9 +3,21 @@ import StatusList from '../components/status_list';
|
||||||
import { replyCompose } from '../actions/compose';
|
import { replyCompose } from '../actions/compose';
|
||||||
import { reblog, favourite } from '../actions/interactions';
|
import { reblog, favourite } from '../actions/interactions';
|
||||||
|
|
||||||
|
function selectStatus(state, id) {
|
||||||
|
let status = state.getIn(['timelines', 'statuses', id]);
|
||||||
|
|
||||||
|
status = status.set('account', state.getIn(['timelines', 'accounts', status.get('account')]));
|
||||||
|
|
||||||
|
if (status.get('reblog') !== null) {
|
||||||
|
status = status.set('reblog', selectStatus(state, status.get('reblog')));
|
||||||
|
}
|
||||||
|
|
||||||
|
return status;
|
||||||
|
};
|
||||||
|
|
||||||
const mapStateToProps = function (state, props) {
|
const mapStateToProps = function (state, props) {
|
||||||
return {
|
return {
|
||||||
statuses: state.getIn(['timelines', props.type])
|
statuses: state.getIn(['timelines', props.type]).map(id => selectStatus(state, id))
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -2,31 +2,57 @@ import { TIMELINE_SET, TIMELINE_UPDATE } from '../actions/timelines';
|
||||||
import { REBLOG_SUCCESS, FAVOURITE_SUCCESS } from '../actions/interactions';
|
import { REBLOG_SUCCESS, FAVOURITE_SUCCESS } from '../actions/interactions';
|
||||||
import Immutable from 'immutable';
|
import Immutable from 'immutable';
|
||||||
|
|
||||||
const initialState = Immutable.Map();
|
const initialState = Immutable.Map({
|
||||||
|
home: Immutable.List(),
|
||||||
|
mentions: Immutable.List(),
|
||||||
|
statuses: Immutable.Map(),
|
||||||
|
accounts: Immutable.Map()
|
||||||
|
});
|
||||||
|
|
||||||
function updateMatchingStatuses(state, needle, callback) {
|
function statusToMaps(state, status) {
|
||||||
return state.map(function (list) {
|
// Separate account
|
||||||
return list.map(function (status) {
|
let account = status.get('account');
|
||||||
if (status.get('id') === needle.get('id')) {
|
status = status.set('account', account.get('id'));
|
||||||
return callback(status);
|
|
||||||
} else if (status.getIn(['reblog', 'id'], null) === needle.get('id')) {
|
|
||||||
return status.set('reblog', callback(status.get('reblog')));
|
|
||||||
}
|
|
||||||
|
|
||||||
return status;
|
// Separate reblog, repeat for reblog
|
||||||
});
|
let reblog = status.get('reblog');
|
||||||
|
|
||||||
|
if (reblog !== null) {
|
||||||
|
status = status.set('reblog', reblog.get('id'));
|
||||||
|
state = statusToMaps(state, reblog);
|
||||||
|
}
|
||||||
|
|
||||||
|
return state.withMutations(map => {
|
||||||
|
map.setIn(['accounts', account.get('id')], account);
|
||||||
|
map.setIn(['statuses', status.get('id')], status);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function timelineToMaps(state, timeline, statuses) {
|
||||||
|
statuses.forEach((status, i) => {
|
||||||
|
state = statusToMaps(state, status);
|
||||||
|
state = state.setIn([timeline, i], status.get('id'));
|
||||||
|
});
|
||||||
|
|
||||||
|
return state;
|
||||||
|
};
|
||||||
|
|
||||||
|
function updateTimelineWithMaps(state, timeline, status) {
|
||||||
|
state = statusToMaps(state, status);
|
||||||
|
state = state.update(timeline, list => list.unshift(status.get('id')));
|
||||||
|
|
||||||
|
return state;
|
||||||
|
};
|
||||||
|
|
||||||
export default function timelines(state = initialState, action) {
|
export default function timelines(state = initialState, action) {
|
||||||
switch(action.type) {
|
switch(action.type) {
|
||||||
case TIMELINE_SET:
|
case TIMELINE_SET:
|
||||||
return state.set(action.timeline, Immutable.fromJS(action.statuses));
|
return timelineToMaps(state, action.timeline, Immutable.fromJS(action.statuses));
|
||||||
case TIMELINE_UPDATE:
|
case TIMELINE_UPDATE:
|
||||||
return state.update(action.timeline, list => list.unshift(Immutable.fromJS(action.status)));
|
return updateTimelineWithMaps(state, action.timeline,Immutable.fromJS(action.status));
|
||||||
case REBLOG_SUCCESS:
|
case REBLOG_SUCCESS:
|
||||||
case FAVOURITE_SUCCESS:
|
case FAVOURITE_SUCCESS:
|
||||||
return updateMatchingStatuses(state, action.status, () => Immutable.fromJS(action.response));
|
return statusToMaps(state, Immutable.fromJS(action.response));
|
||||||
default:
|
default:
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
|
|
@ -214,6 +214,6 @@ module AtomBuilderHelper
|
||||||
end
|
end
|
||||||
|
|
||||||
def single_link_avatar(xml, account, size, px)
|
def single_link_avatar(xml, account, size, px)
|
||||||
xml.link('rel' => 'avatar', 'type' => account.avatar_content_type, 'media:width' => px, 'media:height' =>px, 'href' => asset_url(account.avatar.url(size, false)))
|
xml.link('rel' => 'avatar', 'type' => account.avatar_content_type, 'media:width' => px, 'media:height' =>px, 'href' => full_asset_url(account.avatar.url(size, false)))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,11 +1,15 @@
|
||||||
module RoutingHelper
|
module RoutingHelper
|
||||||
extend ActiveSupport::Concern
|
extend ActiveSupport::Concern
|
||||||
include Rails.application.routes.url_helpers
|
include Rails.application.routes.url_helpers
|
||||||
include ActionView::Helpers::AssetUrlHelper
|
include ActionView::Helpers::AssetTagHelper
|
||||||
|
|
||||||
included do
|
included do
|
||||||
def default_url_options
|
def default_url_options
|
||||||
ActionMailer::Base.default_url_options
|
ActionMailer::Base.default_url_options
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def full_asset_url(source)
|
||||||
|
File.join(root_url, ActionController::Base.helpers.asset_url(source))
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -4,7 +4,7 @@ Nokogiri::XML::Builder.new do |xml|
|
||||||
title xml, @account.display_name
|
title xml, @account.display_name
|
||||||
subtitle xml, @account.note
|
subtitle xml, @account.note
|
||||||
updated_at xml, stream_updated_at
|
updated_at xml, stream_updated_at
|
||||||
logo xml, asset_url(@account.avatar.url(:medium, false))
|
logo xml, full_asset_url(@account.avatar.url(:medium, false))
|
||||||
|
|
||||||
author(xml) do
|
author(xml) do
|
||||||
include_author xml, @account
|
include_author xml, @account
|
||||||
|
|
|
@ -3,7 +3,7 @@ object @account
|
||||||
attributes :id, :username, :acct, :display_name, :note
|
attributes :id, :username, :acct, :display_name, :note
|
||||||
|
|
||||||
node(:url) { |account| url_for_target(account) }
|
node(:url) { |account| url_for_target(account) }
|
||||||
node(:avatar) { |account| asset_url(account.avatar.url(:large, false)) }
|
node(:avatar) { |account| full_asset_url(account.avatar.url(:large, false)) }
|
||||||
node(:followers_count) { |account| account.followers.count }
|
node(:followers_count) { |account| account.followers.count }
|
||||||
node(:following_count) { |account| account.following.count }
|
node(:following_count) { |account| account.following.count }
|
||||||
node(:statuses_count) { |account| account.statuses.count }
|
node(:statuses_count) { |account| account.statuses.count }
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
Rails.application.configure do
|
Rails.application.configure do
|
||||||
config.x.local_domain = ENV['LOCAL_DOMAIN'] || 'localhost'
|
config.x.local_domain = ENV['LOCAL_DOMAIN'] || "localhost:#{ENV['PORT'] || 3000}"
|
||||||
config.x.hub_url = ENV['HUB_URL'] || 'https://pubsubhubbub.superfeedr.com'
|
config.x.hub_url = ENV['HUB_URL'] || 'https://pubsubhubbub.superfeedr.com'
|
||||||
config.x.use_https = ENV['LOCAL_HTTPS'] == 'true'
|
config.x.use_https = ENV['LOCAL_HTTPS'] == 'true'
|
||||||
|
|
||||||
config.action_mailer.default_url_options = { host: config.x.local_domain, protocol: config.x.use_https ? 'https://' : 'http://' }
|
config.action_mailer.default_url_options = { host: config.x.local_domain, protocol: config.x.use_https ? 'https://' : 'http://', trailing_slash: false }
|
||||||
|
|
||||||
if Rails.env.production?
|
if Rails.env.production?
|
||||||
config.action_cable.allowed_request_origins = ["http#{config.x.use_https ? 's' : ''}://#{config.x.local_domain}"]
|
config.action_cable.allowed_request_origins = ["http#{config.x.use_https ? 's' : ''}://#{config.x.local_domain}"]
|
||||||
|
|
|
@ -1,11 +1,7 @@
|
||||||
require 'rails_helper'
|
require 'rails_helper'
|
||||||
|
|
||||||
RSpec.describe ApplicationHelper, type: :helper do
|
RSpec.describe ApplicationHelper, type: :helper do
|
||||||
let(:local_domain) { 'local.tld' }
|
let(:local_domain) { Rails.configuration.x.local_domain }
|
||||||
|
|
||||||
before do
|
|
||||||
Rails.configuration.x.local_domain = local_domain
|
|
||||||
end
|
|
||||||
|
|
||||||
describe '#unique_tag' do
|
describe '#unique_tag' do
|
||||||
it 'returns a string' do
|
it 'returns a string' do
|
||||||
|
|
Reference in a new issue