diff --git a/app/controllers/api/v1/timelines/local_top_controller.rb b/app/controllers/api/v1/timelines/local_top_controller.rb new file mode 100644 index 0000000000..fd11a3a24a --- /dev/null +++ b/app/controllers/api/v1/timelines/local_top_controller.rb @@ -0,0 +1,10 @@ +module Api::V1::Timelines + class LocalTopController < ApiController + before_action :require_user! + + def show + @statuses = LocalTopPostsService.new.call + render json: @statuses + end + end + end \ No newline at end of file diff --git a/app/javascript/mastodon/components/navigation_portal.tsx b/app/javascript/mastodon/components/navigation_portal.tsx index d3ac8baa6e..9904b70f2b 100644 --- a/app/javascript/mastodon/components/navigation_portal.tsx +++ b/app/javascript/mastodon/components/navigation_portal.tsx @@ -1,6 +1,16 @@ import Trends from 'mastodon/features/getting_started/containers/trends_container'; import { showTrends } from 'mastodon/initial_state'; +import { Link } from 'react-router-dom'; export const NavigationPortal: React.FC = () => ( -
{showTrends && }
-); +
+ {/* Existing Trends section */} + {showTrends && } + + {/* Add Local Top tab */} + + + Local Top + +
+); \ No newline at end of file diff --git a/app/javascript/mastodon/features/local_top_timeline/index.js b/app/javascript/mastodon/features/local_top_timeline/index.js new file mode 100644 index 0000000000..e23334cd9d --- /dev/null +++ b/app/javascript/mastodon/features/local_top_timeline/index.js @@ -0,0 +1,14 @@ +// app/javascript/mastodon/features/local_top_timeline/index.js +import React from 'react'; +import Timeline from '../../components/timeline'; + +export default class LocalTopTimeline extends React.PureComponent { + render() { + return ( + + ); + } +} \ No newline at end of file diff --git a/app/javascript/mastodon/routes.js b/app/javascript/mastodon/routes.js new file mode 100644 index 0000000000..b52f401472 --- /dev/null +++ b/app/javascript/mastodon/routes.js @@ -0,0 +1,6 @@ +// app/javascript/mastodon/routes.js +{ + path: '/timelines/local_top', + component: AsyncComponent(() => import('../features/local_top_timeline')), + exact: true, + } \ No newline at end of file diff --git a/app/services/local_top_posts_service.rb b/app/services/local_top_posts_service.rb new file mode 100644 index 0000000000..530ad15521 --- /dev/null +++ b/app/services/local_top_posts_service.rb @@ -0,0 +1,15 @@ +class LocalTopPostsService < BaseService + # Rank local posts by engagement (boosts > favs > replies) in last 24h + def call + Status + .local + .joins(:status_stat) + .where('statuses.created_at > ?', 24.hours.ago) + .select('statuses.*, + (status_stats.reblogs_count * 0.6 + + status_stats.favourites_count * 0.3 + + status_stats.replies_count * 0.1) AS engagement_score') + .order('engagement_score DESC') + .limit(50) # Adjust as needed + end + end \ No newline at end of file diff --git a/app/services/precompute_feed_service.rb b/app/services/precompute_feed_service.rb index a591c90913..70a8e8afb1 100644 --- a/app/services/precompute_feed_service.rb +++ b/app/services/precompute_feed_service.rb @@ -21,3 +21,13 @@ class PrecomputeFeedService < BaseService @skip_filled_timelines && FeedManager.instance.timeline_size(type, id) * 2 > FeedManager::MAX_ITEMS end end + +# app/services/precompute_feed_service.rb +def call(user) + case user.feed_algorithm + when 'local_top' + LocalTopPostsService.new.call + else + Status.chronological # Default + end +end diff --git a/config/routes.rb b/config/routes.rb index 1f97ddaaa4..bdfb8092d5 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -163,6 +163,17 @@ Rails.application.routes.draw do resources :statuses, only: :show end + # config/routes.rb + namespace :api do + namespace :v1 do + resources :timelines do + collection do + get :local_top + end + end + end +end + resources :media, only: [:show] do get :player end diff --git a/db/migrate/20240612000000_add_local_top_algorithm_support.rb b/db/migrate/20240612000000_add_local_top_algorithm_support.rb new file mode 100644 index 0000000000..1eeec1eda4 --- /dev/null +++ b/db/migrate/20240612000000_add_local_top_algorithm_support.rb @@ -0,0 +1,12 @@ +# db/migrate/20240612000000_add_local_top_algorithm_support.rb +class AddLocalTopAlgorithmSupport < ActiveRecord::Migration[6.1] + def change + # Ensure status_stats exists (Mastodon usually has this) + unless column_exists?(:statuses, :status_stat_id) + add_reference :statuses, :status_stat, foreign_key: { on_delete: :cascade } + end + + # Add user preference for feed algorithm (default: chronological) + add_column :users, :feed_algorithm, :string, default: 'chronological' + end + end \ No newline at end of file