parent
							
								
									35b6c4b36a
								
							
						
					
					
						commit
						0077fc26df
					
				
					 13 changed files with 133 additions and 52 deletions
				
			
		| 
						 | 
				
			
			@ -1,11 +1,10 @@
 | 
			
		|||
import StatusListContainer from '../containers/status_list_container';
 | 
			
		||||
import ColumnHeader        from './column_header';
 | 
			
		||||
import PureRenderMixin     from 'react-addons-pure-render-mixin';
 | 
			
		||||
import ColumnHeader    from './column_header';
 | 
			
		||||
import PureRenderMixin from 'react-addons-pure-render-mixin';
 | 
			
		||||
 | 
			
		||||
const Column = React.createClass({
 | 
			
		||||
 | 
			
		||||
  propTypes: {
 | 
			
		||||
    type: React.PropTypes.string,
 | 
			
		||||
    heading: React.PropTypes.string,
 | 
			
		||||
    icon: React.PropTypes.string
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -17,10 +16,16 @@ const Column = React.createClass({
 | 
			
		|||
  },
 | 
			
		||||
 | 
			
		||||
  render () {
 | 
			
		||||
    let header = '';
 | 
			
		||||
 | 
			
		||||
    if (this.props.heading) {
 | 
			
		||||
      header = <ColumnHeader icon={this.props.icon} type={this.props.heading} onClick={this.handleHeaderClick} />;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return (
 | 
			
		||||
      <div style={{ width: '380px', flex: '0 0 auto', background: '#282c37', margin: '10px', marginRight: '0', display: 'flex', flexDirection: 'column' }}>
 | 
			
		||||
        <ColumnHeader icon={this.props.icon} type={this.props.type} onClick={this.handleHeaderClick} />
 | 
			
		||||
        <StatusListContainer type={this.props.type} />
 | 
			
		||||
        {header}
 | 
			
		||||
        {this.props.children}
 | 
			
		||||
      </div>
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,4 +1,3 @@
 | 
			
		|||
import Column          from './column';
 | 
			
		||||
import PureRenderMixin from 'react-addons-pure-render-mixin';
 | 
			
		||||
 | 
			
		||||
const ColumnsArea = React.createClass({
 | 
			
		||||
| 
						 | 
				
			
			@ -8,8 +7,7 @@ const ColumnsArea = React.createClass({
 | 
			
		|||
  render () {
 | 
			
		||||
    return (
 | 
			
		||||
      <div style={{ display: 'flex', flexDirection: 'row', flex: '1' }}>
 | 
			
		||||
        <Column icon='home' type='home' />
 | 
			
		||||
        <Column icon='at' type='mentions' />
 | 
			
		||||
        {this.props.children}
 | 
			
		||||
      </div>
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,8 +1,10 @@
 | 
			
		|||
import ColumnsArea          from './columns_area';
 | 
			
		||||
import Column               from './column';
 | 
			
		||||
import Drawer               from './drawer';
 | 
			
		||||
import ComposeFormContainer from '../containers/compose_form_container';
 | 
			
		||||
import FollowFormContainer  from '../containers/follow_form_container';
 | 
			
		||||
import UploadFormContainer  from '../containers/upload_form_container';
 | 
			
		||||
import StatusListContainer  from '../containers/status_list_container';
 | 
			
		||||
import PureRenderMixin      from 'react-addons-pure-render-mixin';
 | 
			
		||||
 | 
			
		||||
const Frontend = React.createClass({
 | 
			
		||||
| 
						 | 
				
			
			@ -21,7 +23,15 @@ const Frontend = React.createClass({
 | 
			
		|||
          <FollowFormContainer />
 | 
			
		||||
        </Drawer>
 | 
			
		||||
 | 
			
		||||
        <ColumnsArea />
 | 
			
		||||
        <ColumnsArea>
 | 
			
		||||
          <Column icon='home' heading='Home'>
 | 
			
		||||
            <StatusListContainer type='home' />
 | 
			
		||||
          </Column>
 | 
			
		||||
 | 
			
		||||
          <Column icon='at' heading='Mentions'>
 | 
			
		||||
            <StatusListContainer type='mentions' />
 | 
			
		||||
          </Column>
 | 
			
		||||
        </ColumnsArea>
 | 
			
		||||
      </div>
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -4,8 +4,12 @@ import Frontend                                             from '../components/
 | 
			
		|||
import { setTimeline, updateTimeline, deleteFromTimelines } from '../actions/timelines';
 | 
			
		||||
import { setAccessToken }                                   from '../actions/meta';
 | 
			
		||||
import PureRenderMixin                                      from 'react-addons-pure-render-mixin';
 | 
			
		||||
import { Router, Route, createMemoryHistory }               from 'react-router';
 | 
			
		||||
import AccountRoute                                         from '../routes/account_route';
 | 
			
		||||
import StatusRoute                                          from '../routes/status_route';
 | 
			
		||||
 | 
			
		||||
const store = configureStore();
 | 
			
		||||
const store   = configureStore();
 | 
			
		||||
const history = createMemoryHistory();
 | 
			
		||||
 | 
			
		||||
const Root = React.createClass({
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -45,7 +49,12 @@ const Root = React.createClass({
 | 
			
		|||
  render () {
 | 
			
		||||
    return (
 | 
			
		||||
      <Provider store={store}>
 | 
			
		||||
        <Frontend />
 | 
			
		||||
        <Router history={history}>
 | 
			
		||||
          <Route path="/" component={Frontend}>
 | 
			
		||||
            <Route path="/accounts/:account_id" component={AccountRoute} />
 | 
			
		||||
            <Route path="/statuses/:status_id" component={StatusRoute} />
 | 
			
		||||
          </Route>
 | 
			
		||||
        </Router>
 | 
			
		||||
      </Provider>
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										13
									
								
								app/assets/javascripts/components/routes/account_route.jsx
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								app/assets/javascripts/components/routes/account_route.jsx
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,13 @@
 | 
			
		|||
const AccountRoute = React.createClass({
 | 
			
		||||
 | 
			
		||||
  render() {
 | 
			
		||||
    return (
 | 
			
		||||
      <div>
 | 
			
		||||
        {this.props.params.account_id}
 | 
			
		||||
      </div>
 | 
			
		||||
    )
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
export default AccountRoute;
 | 
			
		||||
							
								
								
									
										13
									
								
								app/assets/javascripts/components/routes/status_route.jsx
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								app/assets/javascripts/components/routes/status_route.jsx
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,13 @@
 | 
			
		|||
const StatusRoute = React.createClass({
 | 
			
		||||
 | 
			
		||||
  render() {
 | 
			
		||||
    return (
 | 
			
		||||
      <div>
 | 
			
		||||
        {this.props.params.status_id}
 | 
			
		||||
      </div>
 | 
			
		||||
    )
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
export default StatusRoute;
 | 
			
		||||
| 
						 | 
				
			
			@ -13,4 +13,42 @@ class FeedManager
 | 
			
		|||
    replied_to_user = status.reply? ? status.thread.account : nil
 | 
			
		||||
    (status.reply? && !(follower.id = replied_to_user.id || follower.following?(replied_to_user)))
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def push(timeline_type, account, status)
 | 
			
		||||
    redis.zadd(key(timeline_type, account.id), status.id, status.id)
 | 
			
		||||
    trim(timeline_type, account.id)
 | 
			
		||||
    ActionCable.server.broadcast("timeline:#{account.id}", type: 'update', timeline: timeline_type, message: inline_render(account, status))
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def trim(type, account_id)
 | 
			
		||||
    return unless redis.zcard(key(type, account_id)) > FeedManager::MAX_ITEMS
 | 
			
		||||
    last = redis.zrevrange(key(type, account_id), FeedManager::MAX_ITEMS - 1, FeedManager::MAX_ITEMS - 1)
 | 
			
		||||
    redis.zremrangebyscore(key(type, account_id), '-inf', "(#{last.last}")
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  private
 | 
			
		||||
 | 
			
		||||
  def redis
 | 
			
		||||
    $redis
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def inline_render(target_account, status)
 | 
			
		||||
    rabl_scope = Class.new do
 | 
			
		||||
      include RoutingHelper
 | 
			
		||||
 | 
			
		||||
      def initialize(account)
 | 
			
		||||
        @account = account
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      def current_user
 | 
			
		||||
        @account.user
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      def current_account
 | 
			
		||||
        @account
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    Rabl::Renderer.new('api/statuses/show', status,  view_path: 'app/views', format: :json, scope: rabl_scope.new(target_account)).render
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -3,6 +3,5 @@ class BaseService
 | 
			
		|||
  include ActionView::Helpers::SanitizeHelper
 | 
			
		||||
 | 
			
		||||
  include RoutingHelper
 | 
			
		||||
  include ApplicationHelper
 | 
			
		||||
  include AtomBuilderHelper
 | 
			
		||||
end
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -10,13 +10,13 @@ class FanOutOnWriteService < BaseService
 | 
			
		|||
  private
 | 
			
		||||
 | 
			
		||||
  def deliver_to_self(status)
 | 
			
		||||
    push(:home, status.account, status)
 | 
			
		||||
    FeedManager.instance.push(:home, status.account, status)
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def deliver_to_followers(status)
 | 
			
		||||
    status.account.followers.each do |follower|
 | 
			
		||||
      next if !follower.local? || FeedManager.instance.filter_status?(status, follower)
 | 
			
		||||
      push(:home, follower, status)
 | 
			
		||||
      FeedManager.instance.push(:home, follower, status)
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -24,42 +24,7 @@ class FanOutOnWriteService < BaseService
 | 
			
		|||
    status.mentions.each do |mention|
 | 
			
		||||
      mentioned_account = mention.account
 | 
			
		||||
      next unless mentioned_account.local?
 | 
			
		||||
      push(:mentions, mentioned_account, status)
 | 
			
		||||
      FeedManager.instance.push(:mentions, mentioned_account, status)
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def push(type, receiver, status)
 | 
			
		||||
    redis.zadd(FeedManager.instance.key(type, receiver.id), status.id, status.id)
 | 
			
		||||
    trim(type, receiver)
 | 
			
		||||
    ActionCable.server.broadcast("timeline:#{receiver.id}", type: 'update', timeline: type, message: inline_render(receiver, status))
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def trim(type, receiver)
 | 
			
		||||
    return unless redis.zcard(FeedManager.instance.key(type, receiver.id)) > FeedManager::MAX_ITEMS
 | 
			
		||||
 | 
			
		||||
    last = redis.zrevrange(FeedManager.instance.key(type, receiver.id), FeedManager::MAX_ITEMS - 1, FeedManager::MAX_ITEMS - 1)
 | 
			
		||||
    redis.zremrangebyscore(FeedManager.instance.key(type, receiver.id), '-inf', "(#{last.last}")
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def redis
 | 
			
		||||
    $redis
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def inline_render(receiver, status)
 | 
			
		||||
    rabl_scope = Class.new(BaseService) do
 | 
			
		||||
      def initialize(account)
 | 
			
		||||
        @account = account
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      def current_user
 | 
			
		||||
        @account.user
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      def current_account
 | 
			
		||||
        @account
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    Rabl::Renderer.new('api/statuses/show', status,  view_path: 'app/views', format: :json, scope: rabl_scope.new(receiver)).render
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -15,12 +15,27 @@ class FollowService < BaseService
 | 
			
		|||
      NotificationWorker.perform_async(follow.stream_entry.id, target_account.id)
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    merge_into_timeline(target_account, source_account)
 | 
			
		||||
    source_account.ping!(account_url(source_account, format: 'atom'), [Rails.configuration.x.hub_url])
 | 
			
		||||
    follow
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  private
 | 
			
		||||
 | 
			
		||||
  def merge_into_timeline(from_account, into_account)
 | 
			
		||||
    timeline_key = FeedManager.instance.key(:home, into_account.id)
 | 
			
		||||
 | 
			
		||||
    from_account.statuses.find_each do |status|
 | 
			
		||||
      redis.zadd(timeline_key, status.id, status.id)
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    FeedManager.instance.trim(:home, into_account.id)
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def redis
 | 
			
		||||
    $redis
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def follow_remote_account_service
 | 
			
		||||
    @follow_remote_account_service ||= FollowRemoteAccountService.new
 | 
			
		||||
  end
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -6,7 +6,7 @@ class PrecomputeFeedService < BaseService
 | 
			
		|||
  def call(type, account, limit)
 | 
			
		||||
    instant_return = []
 | 
			
		||||
 | 
			
		||||
    Status.send("as_#{type}_timeline", account).order('created_at desc').limit(FeedManager::MAX_ITEMS).each do |status|
 | 
			
		||||
    Status.send("as_#{type}_timeline", account).order('created_at desc').limit(FeedManager::MAX_ITEMS).find_each do |status|
 | 
			
		||||
      next if type == :home && FeedManager.instance.filter_status?(status, account)
 | 
			
		||||
      redis.zadd(FeedManager.instance.key(type, account.id), status.id, status.id)
 | 
			
		||||
      instant_return << status unless instant_return.size > limit
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -5,5 +5,20 @@ class UnfollowService < BaseService
 | 
			
		|||
  def call(source_account, target_account)
 | 
			
		||||
    follow = source_account.unfollow!(target_account)
 | 
			
		||||
    NotificationWorker.perform_async(follow.stream_entry.id, target_account.id) unless target_account.local?
 | 
			
		||||
    unmerge_from_timeline(target_account, source_account)
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  private
 | 
			
		||||
 | 
			
		||||
  def unmerge_from_timeline(from_account, into_account)
 | 
			
		||||
    timeline_key = FeedManager.instance.key(:home, into_account.id)
 | 
			
		||||
 | 
			
		||||
    from_account.statuses.find_each do |status|
 | 
			
		||||
      redis.zrem(timeline_key, status.id)
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def redis
 | 
			
		||||
    $redis
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -20,6 +20,7 @@
 | 
			
		|||
    "react-addons-pure-render-mixin": "^15.3.1",
 | 
			
		||||
    "react-immutable-proptypes": "^2.1.0",
 | 
			
		||||
    "react-redux": "^4.4.5",
 | 
			
		||||
    "react-router": "^2.8.0",
 | 
			
		||||
    "redux": "^3.5.2",
 | 
			
		||||
    "redux-immutable": "^3.0.8",
 | 
			
		||||
    "redux-thunk": "^2.1.0"
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		
		Reference in a new issue